From 5f9de24391bd3d1dbe248e70e6845106c5fc312d Mon Sep 17 00:00:00 2001 From: root Date: Tue, 20 Jun 2017 17:42:02 -0400 Subject: [PATCH 001/469] zfs updates --- snapshot_helper/main.cpp | 1342 +++++++++++++++++++------------------- 1 file changed, 657 insertions(+), 685 deletions(-) diff --git a/snapshot_helper/main.cpp b/snapshot_helper/main.cpp index 6731373a6..b4b78132b 100644 --- a/snapshot_helper/main.cpp +++ b/snapshot_helper/main.cpp @@ -1,103 +1,104 @@ -#include -#include -#include +#include +#include +#include +#include #include -#include "../stringtools.h" -#include "../urbackupcommon/os_functions.h" -#include -#ifndef _WIN32 +#include "../stringtools.h" +#include "../urbackupcommon/os_functions.h" +#include +#ifndef _WIN32 #include #include #include -#include -#include +#include +#include #include -#include +#include extern char **environ; #endif #define DEF_Server -#include "../Server.h" - -const int mode_btrfs=0; +#include "../Server.h" + +const int mode_btrfs=0; const int mode_zfs=1; CServer *Server; - -#ifdef _WIN32 -#include - -bool CopyFolder(std::wstring src, std::wstring dst) -{ - if(!os_create_dir(dst)) - return false; - - std::vector curr_files=getFiles(src); - for(size_t i=0;i + +bool CopyFolder(std::wstring src, std::wstring dst) +{ + if(!os_create_dir(dst)) + return false; + + std::vector curr_files=getFiles(src); + for(size_t i=0;i args; + args.push_back(const_cast(path.c_str())); + + while(true) + { + const char* p = va_arg(vl, const char*); + if(p==NULL) break; + args.push_back(const_cast(p)); + } + va_end(vl); + + args.push_back(NULL); + + int pipefd[2]; + if (pipe(pipefd) == -1) + { + return -1; + } + + pid_t child_pid = fork(); + + if(child_pid==0) + { + environ = new char*[1]; + *environ=NULL; + + close(pipefd[0]); + + if(dup2(pipefd[1], 1)==-1) + { + return -1; + } int rc = execvp(path.c_str(), args.data()); exit(rc); } else { + close(pipefd[1]); + + char buf[512]; + int r; + while( (r=read(pipefd[0], buf, 512))>0) + { + stdout.insert(stdout.end(), buf, buf+r); + } + + close(pipefd[0]); + int status; waitpid(child_pid, &status, 0); if(WIFEXITED(status)) @@ -163,73 +231,6 @@ int exec_wait(const std::string& path, bool keep_stdout, ...) return -1; } } -} - -int exec_wait(const std::string& path, std::string& stdout, ...) -{ - va_list vl; - va_start(vl, stdout); - - std::vector args; - args.push_back(const_cast(path.c_str())); - - while(true) - { - const char* p = va_arg(vl, const char*); - if(p==NULL) break; - args.push_back(const_cast(p)); - } - va_end(vl); - - args.push_back(NULL); - - int pipefd[2]; - if (pipe(pipefd) == -1) - { - return -1; - } - - pid_t child_pid = fork(); - - if(child_pid==0) - { - environ = new char*[1]; - *environ=NULL; - - close(pipefd[0]); - - if(dup2(pipefd[1], 1)==-1) - { - return -1; - } - - int rc = execvp(path.c_str(), args.data()); - exit(rc); - } - else - { - close(pipefd[1]); - - char buf[512]; - int r; - while( (r=read(pipefd[0], buf, 512))>0) - { - stdout.insert(stdout.end(), buf, buf+r); - } - - close(pipefd[0]); - - int status; - waitpid(child_pid, &status, 0); - if(WIFEXITED(status)) - { - return WEXITSTATUS(status); - } - else - { - return -1; - } - } } bool chown_dir(const std::string& dir) @@ -241,373 +242,344 @@ bool chown_dir(const std::string& dir) return rc!=-1; } return false; -} - -std::string find_btrfs_cmd() -{ - static std::string btrfs_cmd; - - if(!btrfs_cmd.empty()) - { - return btrfs_cmd; - } - - if(exec_wait("btrfs", false, "--version", NULL)==0) - { - btrfs_cmd="btrfs"; - return btrfs_cmd; - } - else if(exec_wait("/sbin/btrfs", false, "--version", NULL)==0) - { - btrfs_cmd="/sbin/btrfs"; - return btrfs_cmd; - } - else if(exec_wait("/bin/btrfs", false, "--version", NULL)==0) - { - btrfs_cmd="/bin/btrfs"; - return btrfs_cmd; - } - else if(exec_wait("/usr/sbin/btrfs", false, "--version", NULL)==0) - { - btrfs_cmd="/usr/sbin/btrfs"; - return btrfs_cmd; - } - else if(exec_wait("/usr/bin/btrfs", false, "--version", NULL)==0) - { - btrfs_cmd="/usr/bin/btrfs"; - return btrfs_cmd; - } - else - { - btrfs_cmd="btrfs"; - return btrfs_cmd; - } -} - -std::string find_zfs_cmd() -{ - static std::string zfs_cmd; - - if(!zfs_cmd.empty()) - { - return zfs_cmd; - } - - if(exec_wait("zfs", false, "--version", NULL)==2) - { - zfs_cmd="zfs"; - return zfs_cmd; - } - else if(exec_wait("/sbin/zfs", false, "--version", NULL)==2) - { - zfs_cmd="/sbin/zfs"; - return zfs_cmd; - } - else if(exec_wait("/bin/zfs", false, "--version", NULL)==2) - { - zfs_cmd="/bin/zfs"; - return zfs_cmd; - } - else if(exec_wait("/usr/sbin/zfs", false, "--version", NULL)==2) - { - zfs_cmd="/usr/sbin/zfs"; - return zfs_cmd; - } - else if(exec_wait("/usr/bin/zfs", false, "--version", NULL)==2) - { - zfs_cmd="/usr/bin/zfs"; - return zfs_cmd; - } - else - { - zfs_cmd="zfs"; - return zfs_cmd; - } } -#endif - -bool create_subvolume(int mode, std::string subvolume_folder) -{ -#ifdef _WIN32 - return os_create_dir(subvolume_folder); -#else - if(mode==mode_btrfs) - { + +std::string find_btrfs_cmd() +{ + static std::string btrfs_cmd; + + if(!btrfs_cmd.empty()) + { + return btrfs_cmd; + } + + if(exec_wait("btrfs", false, "--version", NULL)==0) + { + btrfs_cmd="btrfs"; + return btrfs_cmd; + } + else if(exec_wait("/sbin/btrfs", false, "--version", NULL)==0) + { + btrfs_cmd="/sbin/btrfs"; + return btrfs_cmd; + } + else if(exec_wait("/bin/btrfs", false, "--version", NULL)==0) + { + btrfs_cmd="/bin/btrfs"; + return btrfs_cmd; + } + else if(exec_wait("/usr/sbin/btrfs", false, "--version", NULL)==0) + { + btrfs_cmd="/usr/sbin/btrfs"; + return btrfs_cmd; + } + else if(exec_wait("/usr/bin/btrfs", false, "--version", NULL)==0) + { + btrfs_cmd="/usr/bin/btrfs"; + return btrfs_cmd; + } + else + { + btrfs_cmd="btrfs"; + return btrfs_cmd; + } +} + +std::string find_zfs_cmd() +{ + static std::string zfs_cmd; + + if(!zfs_cmd.empty()) + { + return zfs_cmd; + } + + if(exec_wait("zfs", false, "--version", NULL)==2) + { + zfs_cmd="zfs"; + return zfs_cmd; + } + else if(exec_wait("/sbin/zfs", false, "--version", NULL)==2) + { + zfs_cmd="/sbin/zfs"; + return zfs_cmd; + } + else if(exec_wait("/bin/zfs", false, "--version", NULL)==2) + { + zfs_cmd="/bin/zfs"; + return zfs_cmd; + } + else if(exec_wait("/usr/sbin/zfs", false, "--version", NULL)==2) + { + zfs_cmd="/usr/sbin/zfs"; + return zfs_cmd; + } + else if(exec_wait("/usr/bin/zfs", false, "--version", NULL)==2) + { + zfs_cmd="/usr/bin/zfs"; + return zfs_cmd; + } + else + { + zfs_cmd="zfs"; + return zfs_cmd; + } +} +#endif + +bool create_subvolume(int mode, std::string subvolume_folder) +{ +#ifdef _WIN32 + return os_create_dir(subvolume_folder); +#else + if(mode==mode_btrfs) + { int rc=exec_wait(find_btrfs_cmd(), true, "subvolume", "create", subvolume_folder.c_str(), NULL); chown_dir(subvolume_folder); - return rc==0; - } - else if(mode==mode_zfs) - { - int rc=exec_wait(find_zfs_cmd(), true, "create", "-p", subvolume_folder.c_str(), NULL); - chown_dir(subvolume_folder); - return rc==0; - } - return false; -#endif -} - -bool get_mountpoint(int mode, std::string subvolume_folder) -{ -#ifdef _WIN32 - std::cout << subvolume_folder << std::endl; - return true; -#else - if(mode==mode_btrfs) - { - std::cout << subvolume_folder << std::endl; - return true; - } - else if(mode==mode_zfs) - { - int rc=exec_wait(find_zfs_cmd(), true, "get", "-H", "-o", "value", "mountpoint", subvolume_folder.c_str(), NULL); - return rc==0; - } - return false; -#endif -} - -bool create_snapshot(int mode, std::string snapshot_src, std::string snapshot_dst) -{ -#ifdef _WIN32 - return CopyFolder(widen(snapshot_src), widen(snapshot_dst)); -#else - if(mode==mode_btrfs) - { + return rc==0; + } + else if(mode==mode_zfs) + { + int rc=exec_wait(find_zfs_cmd(), true, "create", "-p", subvolume_folder.c_str(), NULL); + chown_dir(subvolume_folder); + return rc==0; + } + return false; +#endif +} + +bool get_mountpoint(int mode, std::string subvolume_folder) +{ +#ifdef _WIN32 + std::cout << subvolume_folder << std::endl; + return true; +#else + if(mode==mode_btrfs) + { + std::cout << subvolume_folder << std::endl; + return true; + } + else if(mode==mode_zfs) + { + int rc=exec_wait(find_zfs_cmd(), true, "get", "-H", "-o", "value", "mountpoint", subvolume_folder.c_str(), NULL); + return rc==0; + } + return false; +#endif +} + +bool create_snapshot(int mode, std::string snapshot_src, std::string snapshot_dst) +{ +#ifdef _WIN32 + return CopyFolder(widen(snapshot_src), widen(snapshot_dst)); +#else + if(mode==mode_btrfs) + { int rc=exec_wait(find_btrfs_cmd(), true, "subvolume", "snapshot", snapshot_src.c_str(), snapshot_dst.c_str(), NULL); - chown_dir(snapshot_dst); - return rc==0; - } - else if(mode==mode_zfs) - { - int rc=exec_wait(find_zfs_cmd(), true, "clone", (snapshot_src+"@ro").c_str(), snapshot_dst.c_str(), NULL); - chown_dir(snapshot_dst); - return rc==0; - } - return false; -#endif -} - -bool is_subvolume(int mode, std::string subvolume_folder) -{ -#ifdef _WIN32 - return true; -#else - if(mode==mode_btrfs) - { - int rc=exec_wait(find_btrfs_cmd(), false, "subvolume", "list", subvolume_folder.c_str(), NULL); - return rc==0; - } - else if(mode==mode_zfs) - { - int rc=exec_wait(find_zfs_cmd(), false, "list", subvolume_folder.c_str(), NULL); - return rc==0; - } - return false; -#endif -} - -bool promote_dependencies(const std::string& snapshot, std::vector& dependencies) -{ - std::cout << "Searching for origin " << snapshot << std::endl; - - std::string snap_data; - int rc = exec_wait(find_zfs_cmd(), snap_data, "list", "-H", "-o", "name", NULL); - if(rc!=0) - return false; - - std::vector snaps; - TokenizeMail(snap_data, snaps, "\n"); - - std::string snap_folder = ExtractFilePath(snapshot); - for(size_t i=0;i&1 | grep \"ERROR: error accessing '-c'\"").c_str(), NULL); - if(compat_rc==0) - { - compat_rc=12; - } - } - - int rc; - if(compat_rc==12) - { - rc=exec_wait(find_btrfs_cmd(), !quiet, "subvolume", "delete", subvolume_folder.c_str(), NULL); - } - else - { - rc=exec_wait(find_btrfs_cmd(), !quiet, "subvolume", "delete", "-c", subvolume_folder.c_str(), NULL); + chown_dir(snapshot_dst); + return rc==0; + } + else if(mode==mode_zfs) + { + int rc=exec_wait(find_zfs_cmd(), true, "clone", (snapshot_src+"@ro").c_str(), snapshot_dst.c_str(), NULL); + chown_dir(snapshot_dst); + return rc==0; + } + return false; +#endif +} + +bool is_subvolume(int mode, std::string subvolume_folder) +{ +#ifdef _WIN32 + return true; +#else + if(mode==mode_btrfs) + { + int rc=exec_wait(find_btrfs_cmd(), false, "subvolume", "list", subvolume_folder.c_str(), NULL); + return rc==0; + } + else if(mode==mode_zfs) + { + int rc=exec_wait(find_zfs_cmd(), false, "list", subvolume_folder.c_str(), NULL); + return rc==0; + } + return false; +#endif +} + +bool remove_subvolume(int mode, std::string subvolume_folder, bool quiet=false) +{ +#ifdef _WIN32 + return os_remove_nonempty_dir(widen(subvolume_folder)); +#else + if(mode==mode_btrfs) + { + int compat_rc = exec_wait(find_btrfs_cmd(), false, "subvolume", "delete", "-c", NULL); + + if(compat_rc==1) + { + compat_rc = exec_wait("/bin/sh", false, "-c", (find_btrfs_cmd() + + " subvolume delete -c 2>&1 | grep \"ERROR: error accessing '-c'\"").c_str(), NULL); + if(compat_rc==0) + { + compat_rc=12; + } + } + + int rc; + if(compat_rc==12) + { + rc=exec_wait(find_btrfs_cmd(), !quiet, "subvolume", "delete", subvolume_folder.c_str(), NULL); + } + else + { + rc=exec_wait(find_btrfs_cmd(), !quiet, "subvolume", "delete", "-c", subvolume_folder.c_str(), NULL); + } + return rc==0; + } + + else if(mode==mode_zfs) + { + int rc=exec_wait(find_zfs_cmd(), false, "destroy", "-r", subvolume_folder.c_str(), NULL); + if (rc!=0) + { + std::cout << "Checking " << subvolume_folder << " for dependencies..." << std::endl; + std::string clone_data; + rc=exec_wait(find_zfs_cmd(), clone_data, "get", "clones", "-H", "-o", "value", (subvolume_folder+"@ro").c_str(), NULL); + std::string rename_name = ExtractFileName(subvolume_folder); + if(rc==0) + { + std::vector clones; + TokenizeMail(clone_data, clones, "\n"); + std::cout << "Dependencies exist..." << std::endl; + + if (exec_wait(find_zfs_cmd(), true, "rename", (subvolume_folder+"@ro").c_str(), (subvolume_folder+"@"+rename_name).c_str(), NULL)!=0 && is_subvolume(mode, subvolume_folder+"@ro")) + { + return false; + } + + for(size_t i=0;i dependencies; - if(!promote_dependencies(subvolume_folder+"@"+rename_name, dependencies)) - { - return false; - } - - rc = exec_wait(find_zfs_cmd(), true, "destroy", subvolume_folder.c_str(), NULL); - - if(rc==0) - { - for(size_t i=0;i= 3.6." << std::endl; suc=false; - } - else + } + else { if(getFile(clientdir+os_file_sep()+"B"+os_file_sep()+"test")!="test") { std::cout << "TEST FAILED: Cannot read reflinked file" << std::endl; suc=false; - } + } } } - - if(!remove_subvolume(mode_btrfs, clientdir+os_file_sep()+"A") ) - { - std::cout << "TEST FAILED: Removing subvolume A failed" << std::endl; + + if(!remove_subvolume(mode_btrfs, clientdir+os_file_sep()+"A") ) + { + std::cout << "TEST FAILED: Removing subvolume A failed" << std::endl; suc=false; - } - - if(!remove_subvolume(mode_btrfs, clientdir+os_file_sep()+"B") ) - { - std::cout << "TEST FAILED: Removing subvolume B failed" << std::endl; + } + + if(!remove_subvolume(mode_btrfs, clientdir+os_file_sep()+"B") ) + { + std::cout << "TEST FAILED: Removing subvolume B failed" << std::endl; suc=false; - } - - if(!os_remove_dir(clientdir)) - { - std::cout << "TEST FAILED: Removing test clientdir failed" << std::endl; - return 1; - } + } + + if(!os_remove_dir(clientdir)) + { + std::cout << "TEST FAILED: Removing test clientdir failed" << std::endl; + return 1; + } if(!suc) { return 1; } - } - else - { - std::cout << "TEST FAILED: Creating test clientdir \"" << clientdir << "\" failed" << std::endl; - - return zfs_test(); - } - std::cout << "BTRFS TEST OK" << std::endl; - return 10 + mode_btrfs; - } - else if(cmd=="issubvolume") - { - if(argc<5) - { - std::cout << "Not enough parameters for issubvolume" << std::endl; - return 1; - } - - std::string clientname=handleFilename(argv[3]); - std::string name=handleFilename(argv[4]); - - std::string subvolume_folder=backupfolder+os_file_sep()+clientname+os_file_sep()+name; - - return is_subvolume(mode, subvolume_folder)?0:1; - } - else if(cmd=="makereadonly") - { - if(argc<5) - { - std::cout << "Not enough parameters for makereadonly" << std::endl; - return 1; - } - - std::string clientname=handleFilename(argv[3]); - std::string name=handleFilename(argv[4]); - - std::string subvolume_folder=backupfolder+os_file_sep()+clientname+os_file_sep()+name; - - return make_readonly(mode, subvolume_folder)?0:1; - } - else - { - std::cout << "Command not found" << std::endl; - return 1; - } + } + else + { + std::cout << "TEST FAILED: Creating test clientdir \"" << clientdir << "\" failed" << std::endl; + + return zfs_test(); + } + std::cout << "BTRFS TEST OK" << std::endl; + return 10 + mode_btrfs; + } + else if(cmd=="issubvolume") + { + if(argc<5) + { + std::cout << "Not enough parameters for issubvolume" << std::endl; + return 1; + } + + std::string clientname=handleFilename(argv[3]); + std::string name=handleFilename(argv[4]); + + std::string subvolume_folder=backupfolder+os_file_sep()+clientname+os_file_sep()+name; + + return is_subvolume(mode, subvolume_folder)?0:1; + } + else if(cmd=="makereadonly") + { + if(argc<5) + { + std::cout << "Not enough parameters for makereadonly" << std::endl; + return 1; + } + + std::string clientname=handleFilename(argv[3]); + std::string name=handleFilename(argv[4]); + + std::string subvolume_folder=backupfolder+os_file_sep()+clientname+os_file_sep()+name; + + return make_readonly(mode, subvolume_folder)?0:1; + } + else + { + std::cout << "Command not found" << std::endl; + return 1; + } } From 919cbaf3b7fa3b9fe7a84c041b31b7d8c5644b76 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 18 Dec 2020 19:20:38 +0100 Subject: [PATCH 002/469] Fix sending image backup bitmap --- urbackupclient/ClientServiceCMD.cpp | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index a7ded14cf..583b8b03b 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1632,20 +1632,18 @@ void ClientConnector::CMD_INCR_IMAGE(const std::string &cmd, bool ident_ok) state = CCSTATE_IMAGE_HASHDATA; - size_t bufpos = 0; IFile* datafile = hashdatafile; _u32* dataleft = &hashdataleft; - while(bufpos0) { - _u32 towrite = (std::min)(static_cast<_u32>(tcpstack.getBuffersize() - bufpos), *dataleft); - if(datafile->Write(tcpstack.getBuffer()+ bufpos, towrite)!= towrite) + _u32 towrite = (std::min)(static_cast<_u32>(tcpstack.getBuffersize()), *dataleft); + if(datafile->Write(tcpstack.getBuffer(), towrite)!= towrite) { Server->Log("Error writing to data temporary file in CMD_INCR_IMAGE", LL_ERROR); do_quit=true; return; } - bufpos += towrite; *dataleft -= towrite; tcpstack.removeFront(towrite); From 51b9ea0f33467b3f93159f137c21b1016ea3376f Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 22 Dec 2020 18:45:37 +0100 Subject: [PATCH 003/469] Remove clienlist also if backup does not exist in db --- urbackupserver/server_cleanup.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/server_cleanup.cpp b/urbackupserver/server_cleanup.cpp index d7db14b6c..d5419f1ed 100644 --- a/urbackupserver/server_cleanup.cpp +++ b/urbackupserver/server_cleanup.cpp @@ -2778,7 +2778,8 @@ bool ServerCleanupThread::cleanup_clientlists() { int backupid = watoi(getbetween("clientlist_b_", ".ub", files[i].name)); - if(cleanupdao->hasMoreRecentFileBackup(backupid).exists) + if(!cleanupdao->getFileBackupInfo(backupid).exists || + cleanupdao->hasMoreRecentFileBackup(backupid).exists) { if(!Server->deleteFile(os_file_prefix(srcfolder+os_file_sep()+files[i].name))) { From e95aa192a377d7d6c875585aae8a41fd9684355e Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 31 Jan 2021 23:54:35 +0100 Subject: [PATCH 004/469] Reset power throttling after THREAD_MODE_BACKGROUND_BEGIN --- urbackupcommon/os_functions_win.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/urbackupcommon/os_functions_win.cpp b/urbackupcommon/os_functions_win.cpp index ef8b09e9d..0985ca4e7 100644 --- a/urbackupcommon/os_functions_win.cpp +++ b/urbackupcommon/os_functions_win.cpp @@ -1765,7 +1765,14 @@ std::string os_format_errcode(int64 errcode) bool os_enable_background_priority(SPrioInfo& prio_info) { #ifdef THREAD_MODE_BACKGROUND_BEGIN - return SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN) == TRUE; + bool b= SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN) == TRUE; + if (b) + { + THREAD_POWER_THROTTLING_STATE ps = {}; + ps.Version = THREAD_POWER_THROTTLING_CURRENT_VERSION; + SetThreadInformation(GetCurrentThread(), ThreadPowerThrottling, &ps, sizeof(ps)); + } + return b; #else return SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST)==TRUE; #endif From 51d40ec1507bbbcd5aafdeb943f18479f95061d2 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 3 Mar 2021 17:13:22 +0100 Subject: [PATCH 005/469] Merge pull request #47 from grumat/dev Thread and I/O priority for MacOS X. (cherry picked from commit e0ec4f9326e8da89b54d1c1d0cd18a763cec0584) --- urbackupcommon/os_functions_lin.cpp | 126 ++++++++++++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/urbackupcommon/os_functions_lin.cpp b/urbackupcommon/os_functions_lin.cpp index 7cc9040f0..485408e94 100644 --- a/urbackupcommon/os_functions_lin.cpp +++ b/urbackupcommon/os_functions_lin.cpp @@ -61,6 +61,10 @@ #define fsblkcnt64_t fsblkcnt_t #endif +#if defined(__APPLE__) +#include +#endif + #if defined(__ANDROID__) #define fsblkcnt64_t fsblkcnt_t #include "android_popen.h" @@ -1286,6 +1290,128 @@ void os_reset_priority() setpriority(PRIO_PROCESS, 0, 0); } +#elif defined (__APPLE__) + +struct SPrioInfoInt +{ + int io_prio; + int cpu_prio; +}; + +SPrioInfo::SPrioInfo() + : prio_info(new SPrioInfoInt) +{ +} + +SPrioInfo::~SPrioInfo() +{ + delete prio_info; +} + +bool os_enable_background_priority(SPrioInfo& prio_info) +{ + if(prio_info.prio_info==NULL) + { + return false; + } + + uint64 thread_id; + if(pthread_threadid_np(NULL, &thread_id) == 0 + && (uint64)getpid() == thread_id ) + { + //This would set it for the whole process + return false; + } + + prio_info.prio_info->io_prio = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + prio_info.prio_info->cpu_prio = getpriority(PRIO_DARWIN_THREAD, 0); + + if(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, IOPOL_THROTTLE)==-1) + { + return false; + } + int cpuprio = 19; + if(setpriority(PRIO_DARWIN_THREAD, 0, cpuprio)==-1) + { + os_disable_background_priority(prio_info); + return false; + } + + return true; +} + +bool os_disable_background_priority(SPrioInfo& prio_info) +{ + if(prio_info.prio_info==NULL) + { + return false; + } + + bool success = (setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, prio_info.prio_info->io_prio)==0); + success &= (setpriority(PRIO_DARWIN_THREAD, 0, prio_info.prio_info->cpu_prio)==0); + return success; +} + +bool os_enable_prioritize(SPrioInfo& prio_info, EPrio prio) +{ + if(prio_info.prio_info==NULL) + { + return false; + } + + uint64 thread_id; + if(pthread_threadid_np(NULL, &thread_id) == 0 + && (uint64)getpid() == thread_id ) + { + //This would set it for the whole process + return false; + } + + prio_info.prio_info->io_prio = getiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD); + prio_info.prio_info->cpu_prio = getpriority(PRIO_DARWIN_THREAD, 0); + + int ioprio = IOPOL_STANDARD; + int cpuprio = -10; + + if(prio==Prio_SlightPrioritize) + { + ioprio=IOPOL_IMPORTANT; + cpuprio=-3; + } + else if(prio==Prio_SlightBackground) + { + ioprio=IOPOL_UTILITY; + cpuprio=5; + } + + if(setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_THREAD, ioprio)==-1) + { + return false; + } + if(setpriority(PRIO_DARWIN_THREAD, 0, cpuprio)==-1) + { + os_disable_prioritize(prio_info); + return false; + } + + return true; +} + +bool os_disable_prioritize(SPrioInfo& prio_info) +{ + return os_disable_background_priority(prio_info); +} + +void assert_process_priority() +{ +} + +void os_reset_priority() +{ + setiopolicy_np(IOPOL_TYPE_DISK, IOPOL_SCOPE_PROCESS, IOPOL_STANDARD); + setpriority(PRIO_PROCESS, 0, 0); +} + #else //__NR_ioprio_set SPrioInfo::SPrioInfo() From c76753b5f42ccb45e5ee608684cea8ac187ffd38 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Mar 2021 17:22:29 +0100 Subject: [PATCH 006/469] Merge pull request #46 from grumat/osx-client-crashes Fixed Crashes in MacOS X client App (cherry picked from commit 2ebd377461f3aa0ccbdbc50aaaacf21447043d81) --- fileservplugin/FileMetadataPipe.h | 7 +- fileservplugin/PipeFileTar.h | 4 + org.urbackup.server.plist | 50 +++++++++++ post_install_osx_server.sh | 107 ++++++++++++++++++++++++ pre_install_osx_server.sh | 81 ++++++++++++++++++ urbackupcommon/os_functions_lin.cpp | 8 ++ urbackupcommon/os_functions_lin_min.cpp | 2 +- urbackupserver/copy_storage.cpp | 22 ++--- 8 files changed, 268 insertions(+), 13 deletions(-) create mode 100644 org.urbackup.server.plist create mode 100755 post_install_osx_server.sh create mode 100755 pre_install_osx_server.sh diff --git a/fileservplugin/FileMetadataPipe.h b/fileservplugin/FileMetadataPipe.h index 8d706569f..d36fbabe7 100644 --- a/fileservplugin/FileMetadataPipe.h +++ b/fileservplugin/FileMetadataPipe.h @@ -133,5 +133,10 @@ class FileMetadataPipe : public PipeFileBase #include #include "../common/data.h" +#if defined(__APPLE__) +void serialize_stat_buf(const struct stat& buf, const std::string& symlink_target, CWData& data); +#else void serialize_stat_buf(const struct stat64& buf, const std::string& symlink_target, CWData& data); -#endif \ No newline at end of file +#endif + +#endif diff --git a/fileservplugin/PipeFileTar.h b/fileservplugin/PipeFileTar.h index 38606dcb7..78dc5186a 100644 --- a/fileservplugin/PipeFileTar.h +++ b/fileservplugin/PipeFileTar.h @@ -156,3 +156,7 @@ class PipeFileTar : public IPipeFile std::string identity; }; + +#if defined(_WIN32) || defined(__APPLE__) || defined(__FreeBSD__) +#undef stat64 +#endif diff --git a/org.urbackup.server.plist b/org.urbackup.server.plist new file mode 100644 index 000000000..d9eb12e01 --- /dev/null +++ b/org.urbackup.server.plist @@ -0,0 +1,50 @@ + + + + + + Disabled + + Label + org.urbackup.server + KeepAlive + + Crashed + + + + ProgramArguments + + /usr/local/bin/urbackupsrv + run + -l /var/log/urbackup/urbackup.log + + + WorkingDirectory /usr/local/var/urbackup + RunAtLoad + UserName urbackup + + + diff --git a/post_install_osx_server.sh b/post_install_osx_server.sh new file mode 100755 index 000000000..9ffa775b0 --- /dev/null +++ b/post_install_osx_server.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env sh +# Run this after each "sudo make install" to verify permissions + +USERNAME=urbackup +GROUPNAME=urbackup +PLISTFILE=org.urbackup.server.plist + + +if [ $EUID -ne 0 ]; then + echo "This script must be run as root" + exit 1 +fi + +src_dir=$(dirname $0) + +# Create urbackup group +GROUPID=$(dscl . -read /Groups/$GROUPNAME PrimaryGroupID 2> /dev/null | awk '{print $2}') +if [ -z "$GROUPID" ]; then + echo "Could not find group $GROUPNAME. Please run ./preinstall_osx_server.sh before 'make install'." + exit 1 +else + echo "Found group '$GROUPNAME' with ID $GROUPID" +fi + +# Create urbackup user +USERID=$(dscl . -read /Users/$USERNAME UniqueID 2> /dev/null | awk '{print $2}') +if [ -z "$USERID" ]; then + echo "Could not find user $USERNAME. Please run ./preinstall_osx_server.sh before 'make install'." + exit 1 +else + echo "Found user '$USERNAME' with ID $USERID" +fi + +# Create log folder +if [ ! -d "/var/log/$USERNAME" ]; then + echo "Creating directory for log files /var/log/$USERNAME" + mkdir /var/log/$USERNAME +fi + +# Update permissions +echo "Updating permissions..." +chown $USERNAME:$GROUPNAME /var/log/$USERNAME +chown -R $USERNAME:$GROUPNAME /usr/local/var/urbackup + +if [ ! -f /usr/local/var/urbackup/backupfolder ]; then + echo "Could not find '/usr/local/var/urbackup/backupfolder' file. Please specify your backup folder in this file." + exit 1 +fi + +echo "Verifying permissions..." +BACKUPDIR=$(cat /usr/local/var/urbackup/backupfolder) +parts=$(echo $BACKUPDIR/ | awk 'BEGIN{FS="/"}{for (i=1; i < NF; i++) print $i}') + +unset path +for part in $parts +do + path="$path/$part" + printf " Testing access to $path... " + if ! sudo -u $USERNAME test -d $path ; then + echo "ERROR!" + echo "Could not walk into folder '$path' using user '$USERNAME'. Please verify permissions." + exit 1 + else + echo "OK" + fi +done + +# Generate unique name +TMPFILE=$BACKUPDIR/test_file.$$ + +# Try harder if a clash (a bit paranoid) +if [ -f $TMPFILE ]; then + TMPFILE=$TMPFILE.1 +fi + +printf "Testing file write permission in '$BACKUPDIR'... " + +if ! sudo -u $USERNAME touch "$TMPFILE" ; then +#if ! touch $TMPFILE ; then + echo "ERROR!" + echo "No write permission for folder '$BACKUPDIR' using user '$USERNAME'. Please verify permissions." + exit 1 +else + echo "OK" + rm $TMPFILE +fi + +echo "Verifying Launchd plist file..." + +if [ ! -f $src_dir/$PLISTFILE ]; then + echo "ERROR! Could not find launchd plist file: $src_dir/$PLISTFILE" + exit 1 +fi + +if [ -f /Library/LaunchDaemons/$PLISTFILE ]; then + echo "Launchd plist file found in '/Library/LaunchDaemons/$PLISTFILE'. Keeping it." +else + echo "Installing a template Launchd plist file in '/Library/LaunchDaemons/$PLISTFILE'..." + cp "$src_dir/$PLISTFILE" /Library/LaunchDaemons/ +fi + +echo "All steps done!" +echo +echo "General commands to be done:" +echo " Start Service: sudo launchctl load -w /Library/LaunchDaemons/$PLISTFILE" +echo " Stop Service: sudo launchctl unload -w /Library/LaunchDaemons/$PLISTFILE" +echo " Customize Launchd: sudo nano /Library/LaunchDaemons/$PLISTFILE" diff --git a/pre_install_osx_server.sh b/pre_install_osx_server.sh new file mode 100755 index 000000000..3d1b1f7b3 --- /dev/null +++ b/pre_install_osx_server.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env sh +# Run this before the first "sudo make install" to create 'urbackup' user in OS X + +USERNAME=urbackup +USERLONGNAME=urbackup.org +GROUPNAME=urbackup +GROUPLONGNAME=urbackup.org + + +if [ $EUID -ne 0 ]; then + echo "This script must be run as root" + exit 1 +fi + +getHiddenUserUid () { + local __UIDS=$(dscl . -list /Users UniqueID | awk '{print $2}' | sort -ugr) + + local __NewUID + for __NewUID in $__UIDS + do + if [[ $__NewUID -lt 499 ]] ; then + break; + fi + done + + echo $((__NewUID+1)) +} + +getGroupUid () { + local __UIDS=$(dscl . -list /Groups PrimaryGroupID | awk '{print $2}' | sort -ugr) + + local __NewUID + for __NewUID in $__UIDS + do + if [[ $__NewUID -lt 390 ]] ; then + break; + fi + done + + echo $((__NewUID+1)) +} + + +# Create urbackup group +GROUPID=$(dscl . -read /Groups/$GROUPNAME PrimaryGroupID 2> /dev/null | awk '{print $2}') +if [ -z "$GROUPID" ]; then + GROUPID=$(getGroupUid) + echo "Creating group '$GROUPNAME' with ID $GROUPID" + dseditgroup -i $GROUPID -r "$GROUPLONGNAME" -o create "$GROUPNAME" +else + echo "Found $GROUPNAME group with ID $GROUPID" +fi + +# Create urbackup user +USERID=$(dscl . -read /Users/$USERNAME UniqueID 2> /dev/null | awk '{print $2}') +if [ -z "$USERID" ]; then + USERID=$(getHiddenUserUid) + echo "Creating user '$USERNAME' with ID $USERID" + dscl . -create /Users/$USERNAME UniqueID "$USERID" + dscl . -append /Users/$USERNAME RealName "$USERLONGNAME" + dscl . -append /Users/$USERNAME PrimaryGroupID "$GROUPID" + dscl . -append /Users/$USERNAME NFSHomeDirectory "/usr/local/var/urbackup" + dscl . -append /Users/$USERNAME UserShell /usr/bin/false + dscl . -append /Users/$USERNAME IsHidden "1" + dseditgroup -o edit -t user -a $USERNAME $GROUPNAME + dseditgroup -o edit -t user -a $USERNAME daemon +else + echo "Found $USERNAME group with ID $USERID" +fi + +# Create log folder +if [ ! -d "/var/log/$USERNAME" ] +then + echo "Creating directory for log files /var/log/$USERNAME" + mkdir /var/log/$USERNAME +fi + +# Update permissions +echo "Updating permissions..." +chown $USERNAME:$GROUPNAME /var/log/$USERNAME + diff --git a/urbackupcommon/os_functions_lin.cpp b/urbackupcommon/os_functions_lin.cpp index 485408e94..ee21b396a 100644 --- a/urbackupcommon/os_functions_lin.cpp +++ b/urbackupcommon/os_functions_lin.cpp @@ -381,8 +381,12 @@ int64 os_free_space(const std::string &path) int rc=statvfs64((path).c_str(), &buf); if(rc==0) { +#if defined(__FreeBSD__) || defined(__APPLE__) + int64 free = (int64)buf.f_frsize*buf.f_bavail; +#else fsblkcnt64_t blocksize = buf.f_frsize ? buf.f_frsize : buf.f_bsize; fsblkcnt64_t free = blocksize*buf.f_bavail; +#endif if(free>LLONG_MAX) { return LLONG_MAX; @@ -410,7 +414,11 @@ int64 os_total_space(const std::string &path) if(rc==0) { fsblkcnt64_t used=buf.f_blocks-buf.f_bfree; +#if defined(__FreeBSD__) || defined(__APPLE__) + int64 total = (int64)(used+buf.f_bavail)*buf.f_frsize; +#else fsblkcnt64_t total = (used+buf.f_bavail)*buf.f_bsize; +#endif if(total>LLONG_MAX) { return LLONG_MAX; diff --git a/urbackupcommon/os_functions_lin_min.cpp b/urbackupcommon/os_functions_lin_min.cpp index 42faa1252..d84ca0913 100644 --- a/urbackupcommon/os_functions_lin_min.cpp +++ b/urbackupcommon/os_functions_lin_min.cpp @@ -38,7 +38,7 @@ #include #include -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined(__APPLE__) #define open64 open #define stat64 stat #define lstat64 lstat diff --git a/urbackupserver/copy_storage.cpp b/urbackupserver/copy_storage.cpp index 6ec2e887e..ae25958b4 100644 --- a/urbackupserver/copy_storage.cpp +++ b/urbackupserver/copy_storage.cpp @@ -32,25 +32,25 @@ #include "server_log.h" #include "server_status.h" -#if defined(_WIN32) || defined(__APPLE__) || defined(__FreeBSD__) -#define stat64 stat -#endif - -#ifndef _WIN32 -#include -#include -#include -#include +#ifndef _WIN32 +#include +#include +#include +#include #else #include #endif +#if defined(_WIN32) || defined(__APPLE__) || defined(__FreeBSD__) +#define stat64 stat +#endif + namespace { std::string getBackupfolder(IDatabase *db) { - db_results res = db->Read("SELECT value FROM settings_db.settings WHERE key='backupfolder' AND clientid=0"); - if (!res.empty()) + db_results res = db->Read("SELECT value FROM settings_db.settings WHERE key='backupfolder' AND clientid=0"); + if (!res.empty()) { return res[0]["value"]; } From 6a559817dd09c3770c041b54d008ddbcd6482a95 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Mar 2021 17:23:07 +0100 Subject: [PATCH 007/469] Merge pull request #45 from grumat/client-fixes OSX Server support files (cherry picked from commit a024fb8563b7becb579af7fc7906463bbc9e9242) # Conflicts: # urbackupclient/ClientService.cpp --- create_osx_installer.sh | 14 ++++++++++---- fileservplugin/CUDPThread.cpp | 3 ++- urbackupclient/ClientService.cpp | 8 +++++++- urbackupclient/cmdline_preprocessor.cpp | 6 ++++++ 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 6d7dfb0a6..439cb240d 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -1,4 +1,4 @@ -#!/usr/local/bin/bash +#!/usr/bin/env bash set -e @@ -39,7 +39,11 @@ mkdir -p osx-pkg/Library/LaunchDaemons cp osx_installer/daemon.plist osx-pkg/Library/LaunchDaemons/org.urbackup.client.plist mkdir -p osx-pkg/Library/LaunchAgents cp osx_installer/agent.plist osx-pkg/Library/LaunchAgents/org.urbackup.client.plist -./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" +if !($development); then + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" +else + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" +fi make clean make -j5 make install DESTDIR=$PWD/osx-pkg2 @@ -54,8 +58,10 @@ fi cp osx_installer/urbackup.icns "osx-pkg2/Applications/UrBackup Client.app/Contents/Resources/" cp osx_installer/buildmacOSexclusions "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/buildmacOSexclusions" mv "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/urbackupclientgui" "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/" -strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/urbackupclientgui" -strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/sbin/urbackupclientbackend" +if !($development); then + strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/urbackupclientgui" + strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/sbin/urbackupclientbackend" +fi mkdir -p "$PWD/osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/sbin" UNINSTALLER="$PWD/osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/sbin/urbackup_uninstall" diff --git a/fileservplugin/CUDPThread.cpp b/fileservplugin/CUDPThread.cpp index 4d3055d75..5bffca65b 100644 --- a/fileservplugin/CUDPThread.cpp +++ b/fileservplugin/CUDPThread.cpp @@ -114,7 +114,7 @@ std::string getSystemServerName(bool use_fqdn) Server->wait(100); } } -#endif +#else _i32 rc=gethostname(hostname, MAX_PATH); @@ -154,6 +154,7 @@ std::string getSystemServerName(bool use_fqdn) } return ret; +#endif } bool CUDPThread::hasError(void) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 9dd697918..3e76f0c8d 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -600,7 +600,13 @@ bool ClientConnector::Run(IRunOtherCallback* p_run_other) if(crypto_fak!=NULL) { - if (crypto_fak->verifyFile(UPDATE_SIGNATURE_PREFIX "urbackup_ecdsa409k1.pub", +#if defined(__APPLE__) + // ./UrBackup\ Client.app/Contents/MacOS/sbin/../share/urbackup/urbackup_ecdsa409k1.pub + std::string pubkey = ExtractFilePath(Server->getServerWorkingDir()) + "/share/urbackup/urbackup_ecdsa409k1.pub"; +#else + std::string pubkey = UPDATE_SIGNATURE_PREFIX "urbackup_ecdsa409k1.pub"; +#endif + if (crypto_fak->verifyFile(pubkey, UPDATE_FILE_PREFIX "UrBackupUpdate_untested.dat", UPDATE_FILE_PREFIX "UrBackupUpdate.sig2")) { std::auto_ptr updatefile(Server->openFile(UPDATE_FILE_PREFIX "UrBackupUpdate_untested.dat")); diff --git a/urbackupclient/cmdline_preprocessor.cpp b/urbackupclient/cmdline_preprocessor.cpp index 3dc2f0cca..534ba44e3 100644 --- a/urbackupclient/cmdline_preprocessor.cpp +++ b/urbackupclient/cmdline_preprocessor.cpp @@ -350,7 +350,13 @@ int main(int argc, char* argv[]) real_args.push_back("--workingdir"); real_args.push_back(VARDIR); real_args.push_back("--script_path"); +#if defined(__APPLE__) + // ./UrBackup\ Client.app/Contents/MacOS/bin/urbackupclientctl../../share/urbackup/scripts + std::string datadir = ExtractFilePath(ExtractFilePath(argv[0])) + "/share/urbackup/scripts"; + real_args.push_back( datadir + ":" SYSCONFDIR "/urbackup/scripts"); +#else real_args.push_back( DATADIR "/urbackup/scripts:" SYSCONFDIR "/urbackup/scripts"); +#endif real_args.push_back("--pidfile"); real_args.push_back(pidfile_arg.getValue()); if(std::find(real_args.begin(), real_args.end(), "--logfile")==real_args.end()) From 4d3fc1ba8100afc7edf32d6c88418b3bc2660c62 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 7 Mar 2021 19:54:06 +0100 Subject: [PATCH 008/469] Add missing dist file --- Makefile.am_client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am_client b/Makefile.am_client index e131f708f..295d25d59 100644 --- a/Makefile.am_client +++ b/Makefile.am_client @@ -348,4 +348,4 @@ noinst_HEADERS=SessionMgr.h WorkerThread.h Helper_win32.h Database.h defaults.h EXTRA_DIST_GUI = client/info.txt client/data/backup-bad.xpm client/data/backup-ok.xpm client/data/backup-progress.xpm client/data/backup-progress-pause.xpm client/data/backup-no-server.xpm client/data/backup-no-recent.xpm client/data/backup-indexing.xpm client/data/logo1.png client/data/lang/it/urbackup.mo client/data/lang/pl/urbackup.mo client/data/lang/pt_BR/urbackup.mo client/data/lang/sk/urbackup.mo client/data/lang/zh_TW/urbackup.mo client/data/lang/zh_CN/urbackup.mo client/data/lang/de/urbackup.mo client/data/lang/es/urbackup.mo client/data/lang/fr/urbackup.mo client/data/lang/ru/urbackup.mo client/data/lang/uk/urbackup.mo client/data/lang/da/urbackup.mo client/data/lang/nl/urbackup.mo client/data/lang/fa/urbackup.mo client/data/lang/cs/urbackup.mo client/gui/GUISetupWizard.h client/SetupWizard.h -EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat +EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/list_incr urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat From b3f59c96413b6f30c690f7428f85faed72e592d0 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 7 Mar 2021 22:40:33 +0100 Subject: [PATCH 009/469] Add missing dist file --- Makefile.am_client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am_client b/Makefile.am_client index 295d25d59..f09ddb5ca 100644 --- a/Makefile.am_client +++ b/Makefile.am_client @@ -348,4 +348,4 @@ noinst_HEADERS=SessionMgr.h WorkerThread.h Helper_win32.h Database.h defaults.h EXTRA_DIST_GUI = client/info.txt client/data/backup-bad.xpm client/data/backup-ok.xpm client/data/backup-progress.xpm client/data/backup-progress-pause.xpm client/data/backup-no-server.xpm client/data/backup-no-recent.xpm client/data/backup-indexing.xpm client/data/logo1.png client/data/lang/it/urbackup.mo client/data/lang/pl/urbackup.mo client/data/lang/pt_BR/urbackup.mo client/data/lang/sk/urbackup.mo client/data/lang/zh_TW/urbackup.mo client/data/lang/zh_CN/urbackup.mo client/data/lang/de/urbackup.mo client/data/lang/es/urbackup.mo client/data/lang/fr/urbackup.mo client/data/lang/ru/urbackup.mo client/data/lang/uk/urbackup.mo client/data/lang/da/urbackup.mo client/data/lang/nl/urbackup.mo client/data/lang/fa/urbackup.mo client/data/lang/cs/urbackup.mo client/gui/GUISetupWizard.h client/SetupWizard.h -EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/list_incr urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat +EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/list_incr urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbxtrabackup_incr urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat From 2e3a49850730a702660332cd4d231e100a533801 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 19 Feb 2021 12:52:34 +0100 Subject: [PATCH 010/469] Fix snapshot group merge setting display (cherry picked from commit 30744ecf004aa60da01af27cb35351da7e1917d9) # Conflicts: # urbackupserver/www/css/template_post.css # urbackupserver/www/js/template_post.js # urbackupserver/www/js/templates.js --- urbackupserver/www/templates/settings_inv_row.htm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index b2a1924e9..a57c97d45 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -843,14 +843,14 @@
-
+
-
+
From 7c4796917a7d440e8b0a500b39aec3cd564cbe65 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Feb 2021 23:42:12 +0100 Subject: [PATCH 011/469] Always encrypt client tokens for old client versions (cherry picked from commit 754debd4024df8289ef93c55fca4d2f28e1752b0) --- urbackupserver/ClientMain.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index 4b4bd730a..e22c40851 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -1028,7 +1028,8 @@ void ClientMain::operator ()(void) } std::string client_token_key; - if (restore_client_access_encryption) + if (restore_client_access_encryption + || getProtocolVersions().restore_version<1) { client_token_key = "client_token"; if (crypto_fak == NULL) From 4901448568846cebfaf5932922b23d8924607c23 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 9 Feb 2021 18:26:47 +0100 Subject: [PATCH 012/469] Handle emptyaddr (cherry picked from commit af5a756b29a9bbf37975aad114c22be5d5a98062) --- urbackupserver/ClientMain.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index e22c40851..c5346e34d 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -2573,6 +2573,10 @@ std::string ClientMain::getClientIpStr(const std::string & clientname) addr_hint.is_ipv6 = false; memcpy(&addr_hint.addr_ipv4, ip_addr_binary.data(), ip_addr_binary.size()); } + else if(ip_addr_binary.empty()) + { + return std::string(); + } else { assert(false); From 3a8b6cc8497cbe41d014228a95bd1a25695fb18f Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 31 Jan 2021 03:43:09 +0100 Subject: [PATCH 013/469] Handle empty ip (cherry picked from commit c5b59ba62ad4dca5e8bb497e6e166f6511cb39ea) --- urbackupserver/serverinterface/status.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/urbackupserver/serverinterface/status.cpp b/urbackupserver/serverinterface/status.cpp index 0384ffcb6..734eb98ee 100644 --- a/urbackupserver/serverinterface/status.cpp +++ b/urbackupserver/serverinterface/status.cpp @@ -189,6 +189,9 @@ bool is_stop_show(IDatabase* db, std::string stop_key) std::string ipAddrToStr(const std::string& ip_addr_binary) { + if (ip_addr_binary.empty()) + return std::string(); + FileClient::SAddrHint addr_hint; if (ip_addr_binary.size() == sizeof(addr_hint.addr_ipv6)) { From 1cb4761db33951df8dc9e89dc0d0c1a87b97770c Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 26 Jan 2021 01:16:05 +0100 Subject: [PATCH 014/469] Lock overlay and era files (cherry picked from commit 50b4b89eb7b1ec4563912c384f6fc27212ceb3b1) --- linux_snapshot/dm_create_snapshot | 10 +++++ urbackupclient/client.cpp | 63 +++++++++++++++++++++++++++++++ urbackupclient/client.h | 6 +++ 3 files changed, 79 insertions(+) diff --git a/linux_snapshot/dm_create_snapshot b/linux_snapshot/dm_create_snapshot index f0575fc0c..39c913f49 100755 --- a/linux_snapshot/dm_create_snapshot +++ b/linux_snapshot/dm_create_snapshot @@ -31,6 +31,12 @@ then exit $? fi +if [ "x$TYPE" != "xxfs" ] && [ "x$TYPE" != "xext4" ] +then + echo "File system $TYPE not supported" + exit 1 +fi + if [ "x$DEVICE" = "x" ] then echo "Cannot get device for filesystem $SNAP_MOUNTPOINT" @@ -82,6 +88,8 @@ then fi fallocate -l $META_SIZE "$SNAP_MOUNTPOINT/$ERA_META_FN" + chattr +i "$SNAP_MOUNTPOINT/$ERA_META_FN" + echo "FLOCK_PERM=$SNAP_MOUNTPOINT/$ERA_META_FN" ORIG_DEVICE="/dev/mapper/$DEVNAME-$RUUID-clone-era" dmsetup table "$DEVICE" | dmsetup create "$DEVNAME-$RUUID-clone-era" @@ -101,6 +109,8 @@ then fi fallocate -l 5G "$OVERLAY_FN" +chattr +i "$OVERLAY_FN" +echo "FLOCK=$OVERLAY_FN" if ! [ -e "/dev/mapper/$DEVNAME-$RUUID-clone" ] then diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index efd132222..adae9990a 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -71,6 +71,7 @@ #include #include #include +#include #endif #if defined(__ANDROID__) @@ -3726,6 +3727,12 @@ bool IndexThread::deleteShadowcopy(SCDirs *dir) #ifdef _WIN32 return deleteShadowcopyWin(dir); #else + + for (size_t i = 0; i < dir->ref->flock_fds.size(); ++i) + close(dir->ref->flock_fds[i]); + + dir->ref->flock_fds.clear(); + std::string loglines; std::string scriptname; if(dir->fileserv) @@ -8469,6 +8476,15 @@ bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool f std::string snapshot_target; std::string cbt_info; std::string cbt_file; + struct FLockFile + { + FLockFile(std:string fn, bool perm) + : fn(fn), perm(perm) {} + + std:string fn; + bool perm; + }; + std::vector flock_files; for(size_t i=0;igetOsHandle(true); + + struct flock fl = {}; + fl.l_type = F_WRLCK; + fl.l_whence = SEEK_SET; + fl.l_start = 0; + fl.l_len = 0; + + int rc = fcntl(fd, F_SETLK, &fl); + + if (rc != 0) + { + VSSLog("Error locking file (F_SETLK) " + flock_fp + ". " + os_last_error_str(), LL_WARNING); + } + + rc = flock(fd, LOCK_EX); + + if (rc != 0) + { + VSSLog("Error locking file (flock) " + flock_fp + ". " + os_last_error_str(), LL_WARNING); + } + + if (flock_files[i].perm) + flock_fds_perm.push_back(fd); + else + dir->ref->flock_fds.push_back(fd); + } + } + dir->target.erase(0,wpath.size()); if(dir->target.empty() || dir->target[0]!='/') diff --git a/urbackupclient/client.h b/urbackupclient/client.h index c4ff9800d..5d1bd1f6a 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -115,6 +115,10 @@ struct SCRef bool with_writers; std::string cbt_file; CbtType cbt_type; + +#ifndef _WIN32 + std::vector flock_fds; +#endif }; struct SCDirs @@ -875,6 +879,8 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public static unsigned int next_result_id; static IMutex* result_mutex; + std::vector flock_fds_perm; + #ifdef _WIN32 struct SComponent { From 91b5cd92c46375976e1501aed2b7a7a88e6dc552 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 17 Dec 2020 21:26:57 +0100 Subject: [PATCH 015/469] Correct use bitflag use (cherry picked from commit 2740774c22efb349ec3ecd2a00128b579666f7bd) --- urbackupserver/www/js/urbackup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 10e734ef8..581ac1b47 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -2897,7 +2897,7 @@ function settingChangeKey(key) } if(typeof use=="undefined" - || use==2) + || (use&2)>0) { if(typeof use=="undefined") { From 99473d05d4bf18644730ac535dcc5f82e68c08b9 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 8 Mar 2021 21:03:59 +0100 Subject: [PATCH 016/469] Update templates --- urbackupserver/www/js/templates.js | 66 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 531dd9ceb..cb8e1aba5 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,22 +1,25 @@ (function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
 ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); +(function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
 

").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



").f(ctx.get(["tAlert script"], false),ctx,"h").w("

\t\t

").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
Saved script successfully.
");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tPreparing restore. Please be patient..."], false),ctx,"h").w("
 
");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tAccess denied"], false),ctx,"h").w("
").f(ctx.get(["tSorry, something went wrong or you do not have the required rights to access this file or folder."], false),ctx,"h").x(ctx.get(["errcode"], false),ctx,{"block":body_1},{}).w("

").f(ctx.get(["tLogin with username and password"], false),ctx,"h").w("

");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("(").f(ctx.get(["errcode"], false),ctx,"h").w(")");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
 

").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



").f(ctx.get(["tAlert script"], false),ctx,"h").w("

\t\t

").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
Saved script successfully.
");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tName:"], false),ctx,"h").w("
").f(ctx.get(["tLabel:"], false),ctx,"h").w("
").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
").f(ctx.get(["tType:"], false),ctx,"h").w("
 
");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tClients"], false),ctx,"h").w("
").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
 ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tError while accessing backups"], false),ctx,"h").w("
").f(ctx.get(["tSorry, something went wrong:"], false),ctx,"h").w(" ").f(ctx.get(["err"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tAdd client"], false),ctx,"h").w("

").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.w("
UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

").f(ctx.get(["tFile backups"], false),ctx,"h").w("

").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
 ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

").f(ctx.get(["tImage backups"], false),ctx,"h").w("

\t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
 ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

").f(ctx.get(["tNo backups"], false),ctx,"h").w("

").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tChange password"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.w("
UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tChanging password failed:"], false),ctx,"h").w("
").f(ctx.get(["fail_reason"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

Authors:
Translators:
Martin Raiber, Ettore Atalan (German)
Luis Miguel Muñoz (Spanish)
Mehmet Binici (Turkish)
Jussi Bergström (Finnish)
mehdincd, Charles Peltier (French)
Samuele, Paolo, Marco Longo (Italian)
buzzertnl, Pimmetje, buzzertnl (Dutch)
Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
J. Almeida (Portuguese)
matsr (Norwegian)
janda (Slovak)
Jonas Aaslund (Svedish)
Ales Hermann (Czech)
Artem Alabin (Russian)
Olivian Daniel Tofan (Romanian)
Ihor Maydanovich (Ukrainian)
osiengine group (Farsi)
Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
Via PayPal:

Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

UrBackup is using following libraries/code:
UrBackup License:
\"AGPLv3+\"/
UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
").f(ctx.get(["database_error_text"], false),ctx,"h").w("

").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("

").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("

  • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
  • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

    ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

    RUN TF=`mktemp` &&\\
    wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF &&\\
    sh $TF &&\\
    rm -f $TF &&\\
    ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
    ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

    RUN TF=`mktemp` &&\\
    wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
    sh $TF &&\\
    rm -f $TF &&\\
    urbackupclientctl wait-for-backend &&\\
    urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").f(ctx.get(["internet_server"], false),ctx,"h").w(" -k internet_server_port -v ").f(ctx.get(["internet_server_port"], false),ctx,"h").w(" -k computername -v \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" -k internet_authkey -v ").f(ctx.get(["new_authkey"], false),ctx,"h").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
    ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
    ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

  • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

    • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
    • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
    • ").f(ctx.get(["tSet the internet server to:"], false),ctx,"h").w(" ").f(ctx.get(["internet_server"], false),ctx,"h").w("
    • ").f(ctx.get(["tSet the internet server port to:"], false),ctx,"h").w(" ").f(ctx.get(["internet_server_port"], false),ctx,"h").w("
    • ").f(ctx.get(["tSet the computer name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("
    • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("
    • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

    ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

    urbackupclientctl wait-for-backend
    urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").f(ctx.get(["internet_server"], false),ctx,"h").w(" -k internet_server_port -v ").f(ctx.get(["internet_server_port"], false),ctx,"h").w(" -k computername -v \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" -k internet_authkey -v ").f(ctx.get(["new_authkey"], false),ctx,"h").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
    [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
    [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tClients"], false),ctx,"h").w("
").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
 ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
").f(ctx.get(["database_error_text"], false),ctx,"h").w("

").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.w("
").x(ctx.get(["generic_text"], false),ctx,{"block":body_1},{}).f(ctx.get(["ext_text"], false),ctx,"h",["s"]).x(ctx.get(["stop_show_key"], false),ctx,{"block":body_2},{}).w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["dir_error_text"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_2.__dustBody=!0;return body_0;})(); (function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
").f(ctx.get(["creating_filesindex_text"], false),ctx,"h").w("
").f(ctx.get(["tNumber of file entries processed"], false),ctx,"h").w(": ").f(ctx.get(["processed_file_entries"], false),ctx,"h").w("
").f(ctx.get(["tPercent finished"], false),ctx,"h").w(": ").f(ctx.get(["percent_finished"], false),ctx,"h").w("


");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["id"], false),ctx,"h").w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["is_image"], false),ctx,{"else":body_1,"block":body_4},{}).w("").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["duration"], false),ctx,"h").w("").f(ctx.get(["size"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("-");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("Path: ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("Volume: ").f(ctx.get(["details"], false),ctx,"h");}body_4.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

").f(ctx.get(["tFile backups"], false),ctx,"h").w("

").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
 ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

").f(ctx.get(["tImage backups"], false),ctx,"h").w("

\t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
 ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

").f(ctx.get(["tNo backups"], false),ctx,"h").w("

").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
 
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tLast activities"], false),ctx,"h").w("
\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
").f(ctx.get(["tID"], false),ctx,"h").w("").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tStarting time"], false),ctx,"h").w("").f(ctx.get(["tRequired time"], false),ctx,"h").w("").f(ctx.get(["tUsed Storage"], false),ctx,"h").w("
");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tLog"], false),ctx,"h").w(": (").f(ctx.get(["name"], false),ctx,"h").w(")
\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
").f(ctx.get(["tLevel"], false),ctx,"h").w("").f(ctx.get(["tTime"], false),ctx,"h").w("").f(ctx.get(["tMessage"], false),ctx,"h").w("

").f(ctx.get(["tBack"], false),ctx,"h").w("

");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.w("
");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); @@ -29,48 +32,45 @@ (function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.w("
").f(ctx.get(["tLogs"], false),ctx,"h").w("
\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
 ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tErrors"], false),ctx,"h").w("").f(ctx.get(["tWarnings"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("
").f(ctx.get(["tLive Log"], false),ctx,"h").w("
").f(ctx.get(["tReports"], false),ctx,"h").w("
").x(ctx.get(["has_user"], false),ctx,{"else":body_1,"block":body_2},{}).w("
");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tYou need to create a user to be able to send reports"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

 
+
").x(ctx.get(["can_report_script_edit"], false),ctx,{"block":body_3},{}).w("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("

").f(ctx.get(["tEdit report script"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["generic_text"], false),ctx,{"block":body_1},{}).f(ctx.get(["ext_text"], false),ctx,"h",["s"]).x(ctx.get(["stop_show_key"], false),ctx,{"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["dir_error_text"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_2.__dustBody=!0;return body_0;})(); (function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThere is a new version of UrBackup server available"], false),ctx,"h").w(" (").f(ctx.get(["new_version_number"], false),ctx,"h").w("). Download it here.
    ").f(ctx.get(["tOk. Stop showing this."], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["nospc_fatal_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); (function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tETA"], false),ctx,"h").w("").f(ctx.get(["tSpeed"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

    ").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); (function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); +(function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("

    ").f(ctx.get(["tReport script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSaved settings successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["ONLY_WIN32_BEGIN"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["ONLY_WIN32_END"], false),ctx,"h",["s"]).w("
    MBit/s
     
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest Mail sent successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("

    ").f(ctx.get(["tReport script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSaved settings successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["msg"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["rights"], false),ctx,"h").w("").x(ctx.get(["can_change"], false),ctx,{"block":body_1},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w(" ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo Users"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["tBackup Statistics"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_2},{}).w("\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["tAll"], false),ctx,"h").w("
    ").f(ctx.get(["tSum"], false),ctx,"h").w("
    ").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["images_total"], false),ctx,"h").w("
    ").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["files_total"], false),ctx,"h").w("
    ").f(ctx.get(["tAll"], false),ctx,"h").w("").f(ctx.get(["used_total"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_3},{}).w("
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_4},{}).w("
    ").f(ctx.get(["tStorage allocation"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_5},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ");}body_5.__dustBody=!0;return body_0;})(); (function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tDownload client for Windows"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["tDownload client for Mac OS X"], false),ctx,"h");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tDownload client for Linux"], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["hostname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["tBackup Statistics"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_2},{}).w("\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["tAll"], false),ctx,"h").w("
    ").f(ctx.get(["tSum"], false),ctx,"h").w("
    ").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["images_total"], false),ctx,"h").w("
    ").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["files_total"], false),ctx,"h").w("
    ").f(ctx.get(["tAll"], false),ctx,"h").w("").f(ctx.get(["used_total"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_3},{}).w("
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_4},{}).w("
    ").f(ctx.get(["tStorage allocation"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_5},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ");}body_5.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["groupname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w(" ").x(ctx.get(["online_add_status"], false),ctx,{"block":body_2},{}).w(" ").x(ctx.get(["reset_client_uid"], false),ctx,{"block":body_3},{}).w("").f(ctx.get(["status"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastseen"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h").f(ctx.get(["start_file_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastbackup_image"], false),ctx,"h").f(ctx.get(["start_image_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["file_ok_t"], false),ctx,"h").w("").f(ctx.get(["image_ok_t"], false),ctx,"h").w("").f(ctx.get(["ip"], false),ctx,"h").w("").f(ctx.get(["client_version_string"], false),ctx,"h").w("").f(ctx.get(["os_version_string"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("(").f(ctx.get(["status"], false),ctx,"h",["s"]).w(")");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tAllow new client"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_2},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_3},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("min-width: 2em;");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tmpdir_error_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["upgrade_error_text"], false),ctx,"h").w("
    ").f(ctx.get(["tCurrent version"], false),ctx,"h").w(": ").f(ctx.get(["curr_db_version"], false),ctx,"h").w("
    ").f(ctx.get(["tTarget version"], false),ctx,"h").w(": ").f(ctx.get(["target_db_version"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["groupname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w(" ").x(ctx.get(["online_add_status"], false),ctx,{"block":body_2},{}).w(" ").x(ctx.get(["reset_client_uid"], false),ctx,{"block":body_3},{}).w("").f(ctx.get(["status"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastseen"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h").f(ctx.get(["start_file_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastbackup_image"], false),ctx,"h").f(ctx.get(["start_image_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["file_ok_t"], false),ctx,"h").w("").f(ctx.get(["image_ok_t"], false),ctx,"h").w("").f(ctx.get(["ip"], false),ctx,"h").w("").f(ctx.get(["client_version_string"], false),ctx,"h").w("").f(ctx.get(["os_version_string"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("(").f(ctx.get(["status"], false),ctx,"h",["s"]).w(")");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tAllow new client"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tDownload client for Windows"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["tDownload client for Mac OS X"], false),ctx,"h");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tDownload client for Linux"], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSelect all"], false),ctx,"h").w("").f(ctx.get(["tSelect none"], false),ctx,"h").w("").f(ctx.get(["rem_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["tRemove selected"], false),ctx,"h").w("").f(ctx.get(["rem_stop"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); (function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.w("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").f(ctx.get(["virus_error_path"], false),ctx,"h").w(" ).").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); From 54ffa43a21860cf970f15227309661baf0b49b08 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 8 Nov 2020 20:03:12 +0100 Subject: [PATCH 017/469] Insert hashes into db from parallel hash thread if not already present (cherry picked from commit 540680a51bcdc2d7fd71b995ba8e4c434d333d11) --- urbackupclient/ParallelHash.cpp | 40 +++++++++++++++++++++++++-------- urbackupclient/ParallelHash.h | 7 +++--- 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/urbackupclient/ParallelHash.cpp b/urbackupclient/ParallelHash.cpp index eb4e2f02e..7d4cfc343 100644 --- a/urbackupclient/ParallelHash.cpp +++ b/urbackupclient/ParallelHash.cpp @@ -467,17 +467,31 @@ bool ParallelHash::finishDir(ParallelHash::SCurrDir* dir, ClientDAO& clientdao, if (added_hash) { - addModifyFileBuffer(clientdao, path_lower, dir->tgroup, files, target_generation); + addModifyFileBuffer(clientdao, path_lower, dir->tgroup, files, target_generation, false); + } + } + else + { + bool has_hash = false; + std::sort(dir->files.begin(), dir->files.end()); + for (size_t i = 0; i < dir->files.size(); ++i) + { + if (!dir->files[i].hash.empty()) + { + has_hash = true; + break; + } } - IScopedLock lock(mutex.get()); - curr_dirs.erase(id); - return true; + if (has_hash) + { + addModifyFileBuffer(clientdao, path_lower, dir->tgroup, files, target_generation, true); + } } IScopedLock lock(mutex.get()); curr_dirs.erase(id); - return false; + return true; } bool ParallelHash::addToStdoutBuf(const char * ptr, size_t size) @@ -548,13 +562,13 @@ void ParallelHash::runExtraThread() } void ParallelHash::addModifyFileBuffer(ClientDAO& clientdao, const std::string & path, int tgroup, - const std::vector& files, int64 target_generation) + const std::vector& files, int64 target_generation, bool insert) { IScopedLock lock(modify_file_buffer_mutex.get()); modify_file_buffer_size += calcBufferSize(path, files); - modify_file_buffer.push_back(SBufferItem(path, tgroup, files, target_generation)); + modify_file_buffer.push_back(SBufferItem(path, tgroup, files, target_generation, insert)); if (last_file_buffer_commit_time == 0) { @@ -573,8 +587,16 @@ void ParallelHash::commitModifyFileBuffer(ClientDAO& clientdao) DBScopedWriteTransaction trans(clientdao.getDatabase()); for (size_t i = 0; i& files, int64 target_generation); + void addModifyFileBuffer(ClientDAO& clientdao, const std::string& path, int tgroup, const std::vector& files, int64 target_generation, bool insert); void commitModifyFileBuffer(ClientDAO& clientdao); size_t calcBufferSize(const std::string &path, const std::vector &data); void runExtraThread(); @@ -81,14 +81,15 @@ class ParallelHash : public IPipeFileExt, public IThread struct SBufferItem { - SBufferItem(std::string path, int tgroup, std::vector files, int64 target_generation) - : path(path), tgroup(tgroup), files(files), target_generation(target_generation) + SBufferItem(std::string path, int tgroup, std::vector files, int64 target_generation, bool insert) + : path(path), tgroup(tgroup), files(files), target_generation(target_generation), insert(insert) {} std::string path; int tgroup; std::vector files; int64 target_generation; + bool insert; }; std::auto_ptr modify_file_buffer_mutex; From 87a37bd2d0d03fc099bbf4a8579d6611159dab6b Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 2 Nov 2020 12:28:33 +0100 Subject: [PATCH 018/469] Don't set do_quit early (cherry picked from commit 86ab045c01bf8acc5a6269732c57c598edd67b93) --- urbackupclient/ParallelHash.cpp | 10 +++++----- urbackupclient/ParallelHash.h | 1 + 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/urbackupclient/ParallelHash.cpp b/urbackupclient/ParallelHash.cpp index 7d4cfc343..7e7bf8107 100644 --- a/urbackupclient/ParallelHash.cpp +++ b/urbackupclient/ParallelHash.cpp @@ -23,7 +23,7 @@ ParallelHash::ParallelHash(SQueueRef* phash_queue, int sha_version, size_t extra last_file_buffer_commit_time(0), sha_version(sha_version), eof(false), extra_n_threads(extra_n_threads), extra_thread(false), extra_mutex(Server->createMutex()), extra_cond(Server->createCondition()), - modify_file_buffer_mutex(Server->createMutex()) + modify_file_buffer_mutex(Server->createMutex()), do_quit_extra(false) { stdout_buf.resize(4090); ticket = Server->getThreadPool()->execute(this, extra_n_threads>0 ? "phash master": "phash"); @@ -180,7 +180,7 @@ void ParallelHash::operator()() { IScopedLock lock(extra_mutex.get()); - do_quit = true; + do_quit_extra = true; extra_cond->notify_all(); } @@ -537,15 +537,15 @@ size_t ParallelHash::calcBufferSize(const std::string &path, const std::vectorwait(&lock); } - if (do_quit) + if (do_quit_extra) break; std::pair msg = extra_queue.front(); diff --git a/urbackupclient/ParallelHash.h b/urbackupclient/ParallelHash.h index 925ad8360..51721352f 100644 --- a/urbackupclient/ParallelHash.h +++ b/urbackupclient/ParallelHash.h @@ -64,6 +64,7 @@ class ParallelHash : public IPipeFileExt, public IThread std::vector stdout_buf; size_t stdout_buf_pos; size_t stdout_buf_size; + bool do_quit_extra; volatile bool do_quit; volatile bool eof; int64 phash_queue_pos; From 07f73a08ef3f4449d461dc7caad364c6e7fc5e93 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 5 Dec 2020 22:39:54 +0100 Subject: [PATCH 019/469] Wait a bit for server to start reading before removing parallel hash file again (cherry picked from commit 94aa8a243e76109783616cdf45f30106c67892ad) --- fileservplugin/FileServ.cpp | 1 + urbackupclient/ParallelHash.cpp | 16 +++++++++++++++- urbackupclient/ParallelHash.h | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/fileservplugin/FileServ.cpp b/fileservplugin/FileServ.cpp index a6de801c4..535fa22ec 100644 --- a/fileservplugin/FileServ.cpp +++ b/fileservplugin/FileServ.cpp @@ -418,6 +418,7 @@ void FileServ::deregisterScriptPipeFile(const std::string & script_fn) std::map::iterator it = script_mappings.find(script_fn); if (it != script_mappings.end()) { + delete it->second.pipe_file; script_mappings.erase(it); } } diff --git a/urbackupclient/ParallelHash.cpp b/urbackupclient/ParallelHash.cpp index 7e7bf8107..052d300ec 100644 --- a/urbackupclient/ParallelHash.cpp +++ b/urbackupclient/ParallelHash.cpp @@ -23,7 +23,8 @@ ParallelHash::ParallelHash(SQueueRef* phash_queue, int sha_version, size_t extra last_file_buffer_commit_time(0), sha_version(sha_version), eof(false), extra_n_threads(extra_n_threads), extra_thread(false), extra_mutex(Server->createMutex()), extra_cond(Server->createCondition()), - modify_file_buffer_mutex(Server->createMutex()), do_quit_extra(false) + modify_file_buffer_mutex(Server->createMutex()), do_quit_extra(false), + has_read(false) { stdout_buf.resize(4090); ticket = Server->getThreadPool()->execute(this, extra_n_threads>0 ? "phash master": "phash"); @@ -44,6 +45,8 @@ void ParallelHash::forceExit() bool ParallelHash::readStdoutIntoBuffer(char * buf, size_t buf_avail, size_t & read_bytes) { + has_read = true; + while(!do_quit) { IScopedLock lock(mutex.get()); @@ -99,6 +102,8 @@ bool ParallelHash::readStderrIntoBuffer(char * buf, size_t buf_avail, size_t & r void ParallelHash::operator()() { + int64 starttime = Server->getTimeMS(); + if (extra_thread) { runExtraThread(); @@ -186,6 +191,15 @@ void ParallelHash::operator()() Server->getThreadPool()->waitFor(extra_tickets); + while (!has_read && + Server->getTimeMS() - starttime < 5 * 60 * 1000) + { + //Wait at least 5min for server to start reading because + //deleting below might make it inaccessible if the server + //hasn't started reading yet + Server->wait(1000); + } + if (phash_queue->deref()) { delete phash_queue; diff --git a/urbackupclient/ParallelHash.h b/urbackupclient/ParallelHash.h index 51721352f..8ef214c2a 100644 --- a/urbackupclient/ParallelHash.h +++ b/urbackupclient/ParallelHash.h @@ -67,6 +67,7 @@ class ParallelHash : public IPipeFileExt, public IThread bool do_quit_extra; volatile bool do_quit; volatile bool eof; + volatile bool has_read; int64 phash_queue_pos; SQueueRef* phash_queue; std::auto_ptr mutex; From 39a104b7972d91591247c89d6b548b3be1962077 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 1 Nov 2020 13:48:33 +0100 Subject: [PATCH 020/469] Improve handling of default paths to backup on the client (cherry picked from commit 7fed1a0020452ff37ac29d0b40ae5b1db9156d71) --- clientctl/Connector.cpp | 8 ++- clientctl/Connector.h | 1 + clientctl/main.cpp | 51 ++++++++++++++-- configure.ac_client | 2 +- urbackupclient/ClientService.cpp | 92 +++++++++++++++++++++++------ urbackupclient/ClientService.h | 4 +- urbackupclient/ClientServiceCMD.cpp | 2 +- urbackupclient/dllmain.cpp | 2 +- 8 files changed, 132 insertions(+), 30 deletions(-) diff --git a/clientctl/Connector.cpp b/clientctl/Connector.cpp index 120a1f2a5..0e2922b95 100644 --- a/clientctl/Connector.cpp +++ b/clientctl/Connector.cpp @@ -255,7 +255,8 @@ std::vector Connector::getSharedPaths(bool use_change_pw) dir["id"].asInt(), dir["group"].asInt(), virtual_client, - dir["flags"].asString() + dir["flags"].asString(), + dir["server_default"].asInt() }; ret.push_back(rdir); @@ -270,9 +271,12 @@ std::vector Connector::getSharedPaths(bool use_change_pw) bool Connector::saveSharedPaths(const std::vector &res) { - std::string args="all_virtual_clients=1"; + std::string args="all_virtual_clients=1&enable_client_paths_use=1"; for (size_t i = 0; i args) bool has_virtual_client = false; bool has_group = false; + bool has_server_default = false; for (size_t i = 0; i < backup_dirs.size(); ++i) { @@ -1304,6 +1305,8 @@ int action_list_backupdirs(std::vector args) { has_group = true; } + if (backup_dirs[i].server_default) + has_server_default = true; } std::vector > tab; @@ -1320,6 +1323,10 @@ int action_list_backupdirs(std::vector args) 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); @@ -1354,6 +1361,18 @@ int action_list_backupdirs(std::vector args) 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); } @@ -1394,20 +1413,35 @@ int action_remove_backupdir(std::vector args) } 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()) { - backup_dirs.erase(backup_dirs.begin() + i); - del_ok = true; + 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()) { - backup_dirs.erase(backup_dirs.begin() + i); - del_ok = true; + if (backup_dirs[i].server_default) + { + del_server_default = true; + } + else + { + backup_dirs.erase(backup_dirs.begin() + i); + del_ok = true; + } } else { @@ -1417,7 +1451,14 @@ int action_remove_backupdir(std::vector args) if (!del_ok) { - std::cerr << "Backup directory to remove not found" << std::endl; + 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; } diff --git a/configure.ac_client b/configure.ac_client index 9ea55dece..07589e9ae 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.10.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.11.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 3e76f0c8d..493b3680d 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -1411,13 +1411,27 @@ bool ClientConnector::saveBackupDirs(str_map &args, bool server_default, int gro if (args.find("all_virtual_clients") != args.end()) { all_virtual_clients = true; - db->Write("DELETE FROM backupdirs WHERE symlinked=0 AND server_default!=2"); + if (!server_default) + { + db->Write("DELETE FROM backupdirs WHERE symlinked=0 AND server_default=0"); + } + else + { + db->Write("DELETE FROM backupdirs WHERE symlinked=0 AND server_default!=2"); + } } else { - db->Write("DELETE FROM backupdirs WHERE symlinked=0 AND server_default!=2 AND tgroup BETWEEN " + convert(group_offset) + " AND " + convert(group_offset + c_group_max)); + if (!server_default) + { + db->Write("DELETE FROM backupdirs WHERE symlinked=0 AND server_default=0 AND tgroup BETWEEN " + convert(group_offset) + " AND " + convert(group_offset + c_group_max)); + } + else + { + db->Write("DELETE FROM backupdirs WHERE symlinked=0 AND server_default!=2 AND tgroup BETWEEN " + convert(group_offset) + " AND " + convert(group_offset + c_group_max)); + } } - IQuery *q=db->Prepare("INSERT INTO backupdirs (name, path, server_default, optional, tgroup) VALUES (?, ? ,"+convert(server_default?1:0)+", ?, ?)"); + IQuery *q_insert_dir=db->Prepare("INSERT INTO backupdirs (name, path, server_default, optional, tgroup) VALUES (?, ? , ?, ?, ?)"); /** Use empty client settings if(server_default==false) @@ -1583,13 +1597,25 @@ bool ClientConnector::saveBackupDirs(str_map &args, bool server_default, int gro new_watchdirs.push_back(new_dir); } + + int curr_server_default = 0; + + if (server_default) + { + str_map::iterator server_default_arg = args.find("dir_" + convert(i) + "_server_default"); + if (server_default_arg != args.end()) + { + curr_server_default = watoi(server_default_arg->second); + } + } - q->Bind(name); - q->Bind(dir); - q->Bind(flags); - q->Bind(group); - q->Write(); - q->Reset(); + q_insert_dir->Bind(name); + q_insert_dir->Bind(dir); + q_insert_dir->Bind(curr_server_default); + q_insert_dir->Bind(flags); + q_insert_dir->Bind(group); + q_insert_dir->Write(); + q_insert_dir->Reset(); } ++i; } @@ -1727,7 +1753,7 @@ bool ClientConnector::saveBackupDirs(str_map &args, bool server_default, int gro } #endif - if (updateDefaultDirsSetting(db, all_virtual_clients, group_offset)) + if (updateDefaultDirsSetting(db, all_virtual_clients, group_offset, args.find("enable_client_paths_use")!=args.end())) { IScopedLock lock(backup_mutex); for (size_t o = 0; ogetValue("default_dirs.use", 0); std::vector default_dirs_toks; + size_t default_dirs_client_off = std::string::npos; if (default_dirs_use & c_use_group) { @@ -1868,6 +1895,7 @@ void ClientConnector::updateSettings(const std::string &pData) std::string val; std::vector toks; Tokenize(new_settings->getValue("default_dirs.client", ""), toks, ";"); + default_dirs_client_off = default_dirs_toks.size(); default_dirs_toks.insert(default_dirs_toks.end(), toks.begin(), toks.end()); } @@ -1895,6 +1923,7 @@ void ClientConnector::updateSettings(const std::string &pData) args["dir_"+convert(i)+"_name"]=name; args["dir_"+convert(i)+"_group"]=convert(group); + args["dir_" + convert(i) + "_server_default"] = convert(i>= default_dirs_client_off ? 0 : 1); } saveBackupDirs(args, true, group_offset); @@ -3311,6 +3340,9 @@ bool ClientConnector::calculateFilehashesOnClient(const std::string& clientsubna } ISettingsReader *curr_settings=Server->createFileSettingsReader(settings_fn); + if (curr_settings == NULL) + return false; + std::string val; if(curr_settings->getValue("internet_calculate_filehashes_on_client", &val) || curr_settings->getValue("internet_calculate_filehashes_on_client_def", &val)) @@ -3326,8 +3358,8 @@ bool ClientConnector::calculateFilehashesOnClient(const std::string& clientsubna } return false; -} - +} + bool ClientConnector::isBackupRunning() { IScopedLock lock(backup_mutex); @@ -3642,8 +3674,8 @@ bool ClientConnector::restoreDone( int64 log_id, int64 status_id, int64 restore_ "&log_id=" + convert(log_id) + "&id=" + convert(restore_id) + "&success=" + convert(success), 60000, identity); -} - +} + bool ClientConnector::sendMessageToChannel( const std::string& msg, int timeoutms, const std::string& identity ) { IScopedLock lock(backup_mutex); @@ -3885,13 +3917,13 @@ void ClientConnector::refreshSessionFromChannel(const std::string& endpoint_name } } -bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_clients, int group_offset) +bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_clients, int group_offset, bool update_use) { db_results res_virtual_clients; if(all_virtual_clients) - db->Read("SELECT virtual_client, group_offset FROM virtual_client_group_offsets"); + res_virtual_clients = db->Read("SELECT virtual_client, group_offset FROM virtual_client_group_offsets"); else - db->Read("SELECT virtual_client, group_offset FROM virtual_client_group_offsets WHERE group_offset="+convert(group_offset)); + res_virtual_clients = db->Read("SELECT virtual_client, group_offset FROM virtual_client_group_offsets WHERE group_offset="+convert(group_offset)); if (all_virtual_clients || group_offset == 0) { @@ -3911,6 +3943,7 @@ bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_c db_results res_paths = db->Read("SELECT path, name, optional, server_default FROM backupdirs WHERE symlinked=0 AND tgroup=" + convert(curr_group_offset)); int default_dirs_use = c_use_value_client; + bool has_client_path = false; std::string default_dirs; for (size_t j = 0; j < res_paths.size(); ++j) { @@ -3921,6 +3954,10 @@ bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_c default_dirs_use |= c_use_group | c_use_value; continue; } + else + { + has_client_path = true; + } int optional = watoi(res_path["optional"]); @@ -3966,11 +4003,30 @@ bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_c if (curr_settings.get() != NULL) { str_map settings_repl; + + int64 default_dirs_use_lm_orig = curr_settings->getValue("default_dirs.use_lm", 0LL); + + int64 ctime = Server->getTimeSeconds(); + if (default_dirs_use_lm_orig > ctime) + ctime = default_dirs_use_lm_orig + 1; + if (curr_settings->getValue("default_dirs.use", 0) == 0) { - settings_repl["default_dirs.use"] = convert(default_dirs_use); + settings_repl["default_dirs.use"] = convert(default_dirs_use); + settings_repl["default_dirs.use_lm"] = convert(ctime); mod = true; } + else if (update_use) + { + int curr_use = curr_settings->getValue("default_dirs.use", 0); + if (has_client_path && !(curr_use & c_use_value_client)) + { + settings_repl["default_dirs.use"] = convert(curr_use|c_use_value_client); + settings_repl["default_dirs.use_lm"] = convert(ctime); + mod = true; + } + } + if (curr_settings->getValue("default_dirs.client", "") != default_dirs) { diff --git a/urbackupclient/ClientService.h b/urbackupclient/ClientService.h index 8a98c05e5..0bb3dd942 100644 --- a/urbackupclient/ClientService.h +++ b/urbackupclient/ClientService.h @@ -238,11 +238,11 @@ class ClientConnector : public ICustomClient static std::string removeIllegalCharsFromBackupName(std::string in); - static bool updateDefaultDirsSetting(IDatabase *db, bool all_virtual_clients, int group_offset); + static bool updateDefaultDirsSetting(IDatabase *db, bool all_virtual_clients, int group_offset, bool update_use); private: bool checkPassword(const std::string &cmd, bool& change_pw); - bool saveBackupDirs(str_map &args, bool server_default=false, int group_offset=0); + bool saveBackupDirs(str_map &args, bool server_default, int group_offset); std::string replaceChars(std::string in); void updateSettings(const std::string &pData); void replaceSettings(const std::string &pData); diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 6fa1f2d4d..3c77da9fc 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -902,7 +902,7 @@ void ClientConnector::CMD_SAVE_BACKUPDIRS(const std::string &cmd, str_map ¶m return; } - if(saveBackupDirs(params)) + if(saveBackupDirs(params, false, 0)) { tcpstack.Send(pipe, "OK"); } diff --git a/urbackupclient/dllmain.cpp b/urbackupclient/dllmain.cpp index 42aef91bf..284f5664c 100644 --- a/urbackupclient/dllmain.cpp +++ b/urbackupclient/dllmain.cpp @@ -755,7 +755,7 @@ void update_client26_27(IDatabase* db) void update_client27_28(IDatabase* db) { - ClientConnector::updateDefaultDirsSetting(db, true, 0); + ClientConnector::updateDefaultDirsSetting(db, true, 0, false); } bool upgrade_client(void) From a5d493f668900b9610c6d32b22e6cfe36d7c2ec6 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 1 Nov 2020 14:38:32 +0100 Subject: [PATCH 021/469] Don't escape generated default dirs setting (cherry picked from commit 46bb3187a5094555bd44d7eb703eb8fb7f9c824b) --- urbackupclient/ClientService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 493b3680d..be13c4883 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -3989,7 +3989,7 @@ bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_c else if (str_flags == str_flags_default) str_flags.clear(); - default_dirs += EscapePathParamString(res_path["path"])+"|"+EscapePathParamString(res_path["name"]) + str_flags; + default_dirs += res_path["path"]+"|"+res_path["name"] + str_flags; } std::string settings_fn = "urbackup/data/settings.cfg"; From bdac3d856d40b0660854d8fe558ed3f42c0b8006 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 1 Nov 2020 14:42:22 +0100 Subject: [PATCH 022/469] Escape except forward slash (cherry picked from commit 6361b04e59005839d3e9387643fde00aa4b604aa) --- urbackupclient/ClientService.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index be13c4883..bfa58f34e 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -3989,7 +3989,9 @@ bool ClientConnector::updateDefaultDirsSetting(IDatabase* db, bool all_virtual_c else if (str_flags == str_flags_default) str_flags.clear(); - default_dirs += res_path["path"]+"|"+res_path["name"] + str_flags; + std::string path = greplace("%2F", "/", EscapePathParamString(res_path["path"])); + + default_dirs += path +"|"+EscapePathParamString(res_path["name"]) + str_flags; } std::string settings_fn = "urbackup/data/settings.cfg"; From 51538a42718eaaba2076e21dff7fce87e6e00d92 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 1 Nov 2020 17:40:16 +0100 Subject: [PATCH 023/469] Fix skipping small files in parallel hashing (cherry picked from commit 21b65777d0c93faf6d82520636795edc975a9297) --- urbackupclient/ClientServiceCMD.cpp | 16 ++++++++++++++++ urbackupclient/ParallelHash.cpp | 5 +++++ urbackupclient/client.cpp | 3 ++- urbackupclient/client.h | 2 ++ urbackupserver/FileBackup.cpp | 2 +- urbackupserver/FullFileBackup.cpp | 3 ++- urbackupserver/IncrFileBackup.cpp | 3 ++- 7 files changed, 30 insertions(+), 4 deletions(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 3c77da9fc..285ee104c 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -380,6 +380,14 @@ void ClientConnector::CMD_START_INCR_FILEBACKUP(const std::string &cmd) bool async_list = params.find("async") != params.end() && params["async"] == "1"; + if (async_list && + (flags & flag_calc_checksums) && + params["phash"] == "1" && + params["ph_skip_small"] == "1") + { + flags |= flag_phash_skip_small; + } + IScopedLock lock(backup_mutex); CWData data; @@ -553,6 +561,14 @@ void ClientConnector::CMD_START_FULL_FILEBACKUP(const std::string &cmd) bool async_list = params.find("async") != params.end() && params["async"] == "1"; + if (async_list && + (flags & flag_calc_checksums) && + params["phash"] == "1" && + params["ph_skip_small"] == "1") + { + flags |= flag_phash_skip_small; + } + IScopedLock lock(backup_mutex); CWData data; diff --git a/urbackupclient/ParallelHash.cpp b/urbackupclient/ParallelHash.cpp index 052d300ec..10450868a 100644 --- a/urbackupclient/ParallelHash.cpp +++ b/urbackupclient/ParallelHash.cpp @@ -110,6 +110,11 @@ void ParallelHash::operator()() return; } + if (extra_n_threads > 0) + { + ++extra_n_threads; + } + extra_thread = true; for (size_t i = 0; i < extra_n_threads; ++i) { diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index adae9990a..9ee1bff8c 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -2548,7 +2548,8 @@ bool IndexThread::initialCheck(std::vector& params_stack, size_t s else if (calculate_filehashes_on_client && phash_queue != NULL && !files[i].isspecialf - && files[i].size>=link_file_min_size) + && (!(index_flags & flag_phash_skip_small)>0 + || files[i].size>=link_file_min_size ) ) { if (!finish_phash_path) { diff --git a/urbackupclient/client.h b/urbackupclient/client.h index 5d1bd1f6a..aaef5bcca 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -59,6 +59,8 @@ const unsigned int flag_calc_checksums = 8; const unsigned int flag_with_orig_path = 16; const unsigned int flag_with_sequence = 32; const unsigned int flag_with_proper_symlinks = 64; +const unsigned int flag_phash_skip_small = 128; + const uint64 change_indicator_symlink_bit = 0x4000000000000000ULL; const uint64 change_indicator_special_bit = 0x2000000000000000ULL; diff --git a/urbackupserver/FileBackup.cpp b/urbackupserver/FileBackup.cpp index 64f4f0d2b..8341e66da 100644 --- a/urbackupserver/FileBackup.cpp +++ b/urbackupserver/FileBackup.cpp @@ -186,7 +186,7 @@ bool FileBackup::request_filelist_construct(bool full, bool resume, int group, if (client_main->getProtocolVersions().phash_version > 0 && server_settings->getSettings()->internet_parallel_file_hashing) { - start_backup_cmd += "&phash=1"; + start_backup_cmd += "&phash=1&ph_skip_small=1"; phash = true; } diff --git a/urbackupserver/FullFileBackup.cpp b/urbackupserver/FullFileBackup.cpp index 0e8bf80f6..077204ce8 100644 --- a/urbackupserver/FullFileBackup.cpp +++ b/urbackupserver/FullFileBackup.cpp @@ -545,7 +545,8 @@ bool FullFileBackup::doFileBackup() else if (!file_ok && phash_load.get() != NULL && !script_dir - && extra_params.find("no_hash") == extra_params.end()) + && extra_params.find("no_hash") == extra_params.end() + && cf.size >= link_file_min_size) { if (!phash_load->getHash(line, curr_sha2)) { diff --git a/urbackupserver/IncrFileBackup.cpp b/urbackupserver/IncrFileBackup.cpp index c2341796f..ba6fdf908 100644 --- a/urbackupserver/IncrFileBackup.cpp +++ b/urbackupserver/IncrFileBackup.cpp @@ -1118,7 +1118,8 @@ bool IncrFileBackup::doFileBackup() && extra_params.find("sym_target")==extra_params.end() && extra_params.find("special") == extra_params.end() && !phash_load_offline - && extra_params.find("no_hash")==extra_params.end()) + && extra_params.find("no_hash")==extra_params.end() + && cf.size >= link_file_min_size) { if (!phash_load->getHash(line, curr_sha2)) { From 19dfad54f71b10c5b0d1842c6d8e329e6de92c2d Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 1 Nov 2020 19:16:05 +0100 Subject: [PATCH 024/469] Remove client forward compatibility (cherry picked from commit 4074ab3f07898f24ac385f26aa30ee9cb569c32a) --- urbackupclient/ClientServiceCMD.cpp | 16 ---------------- urbackupclient/client.cpp | 3 +-- urbackupclient/client.h | 1 - urbackupserver/FileBackup.cpp | 2 +- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 285ee104c..3c77da9fc 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -380,14 +380,6 @@ void ClientConnector::CMD_START_INCR_FILEBACKUP(const std::string &cmd) bool async_list = params.find("async") != params.end() && params["async"] == "1"; - if (async_list && - (flags & flag_calc_checksums) && - params["phash"] == "1" && - params["ph_skip_small"] == "1") - { - flags |= flag_phash_skip_small; - } - IScopedLock lock(backup_mutex); CWData data; @@ -561,14 +553,6 @@ void ClientConnector::CMD_START_FULL_FILEBACKUP(const std::string &cmd) bool async_list = params.find("async") != params.end() && params["async"] == "1"; - if (async_list && - (flags & flag_calc_checksums) && - params["phash"] == "1" && - params["ph_skip_small"] == "1") - { - flags |= flag_phash_skip_small; - } - IScopedLock lock(backup_mutex); CWData data; diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 9ee1bff8c..6f79c9460 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -2548,8 +2548,7 @@ bool IndexThread::initialCheck(std::vector& params_stack, size_t s else if (calculate_filehashes_on_client && phash_queue != NULL && !files[i].isspecialf - && (!(index_flags & flag_phash_skip_small)>0 - || files[i].size>=link_file_min_size ) ) + && files[i].size>=link_file_min_size ) { if (!finish_phash_path) { diff --git a/urbackupclient/client.h b/urbackupclient/client.h index aaef5bcca..9d584f699 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -59,7 +59,6 @@ const unsigned int flag_calc_checksums = 8; const unsigned int flag_with_orig_path = 16; const unsigned int flag_with_sequence = 32; const unsigned int flag_with_proper_symlinks = 64; -const unsigned int flag_phash_skip_small = 128; const uint64 change_indicator_symlink_bit = 0x4000000000000000ULL; diff --git a/urbackupserver/FileBackup.cpp b/urbackupserver/FileBackup.cpp index 8341e66da..64f4f0d2b 100644 --- a/urbackupserver/FileBackup.cpp +++ b/urbackupserver/FileBackup.cpp @@ -186,7 +186,7 @@ bool FileBackup::request_filelist_construct(bool full, bool resume, int group, if (client_main->getProtocolVersions().phash_version > 0 && server_settings->getSettings()->internet_parallel_file_hashing) { - start_backup_cmd += "&phash=1&ph_skip_small=1"; + start_backup_cmd += "&phash=1"; phash = true; } From ee65c74302a34439e1a56a088e2070d3ef7dba79 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 8 Nov 2020 20:04:20 +0100 Subject: [PATCH 025/469] Map Linux mount points to devices for image backups (cherry picked from commit 80c97161cefb12399427a9b5d19833ebd84ea3b3) --- urbackupclient/ClientServiceCMD.cpp | 13 ++++--------- urbackupclient/lin_sysvol.h | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 3c77da9fc..382300be9 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1535,10 +1535,9 @@ void ClientConnector::CMD_FULL_IMAGE(const std::string &cmd, bool ident_ok) } } #ifndef _WIN32 - else if (image_inf.image_letter == "C" - || image_inf.image_letter == "C:") + else { - image_inf.image_letter = getRootVol(); + image_inf.image_letter = mapLinuxDev(image_inf.image_letter); } #endif @@ -1643,11 +1642,7 @@ void ClientConnector::CMD_INCR_IMAGE(const std::string &cmd, bool ident_ok) image_inf.clientsubname = params["clientsubname"]; #ifndef _WIN32 - if (image_inf.image_letter == "C" - || image_inf.image_letter == "C:") - { - image_inf.image_letter = getRootVol(); - } + image_inf.image_letter = mapLinuxDev(image_inf.image_letter); #endif str_map::iterator f_cbitmapsize = params.find("cbitmapsize"); @@ -1779,7 +1774,7 @@ void ClientConnector::CMD_MBR(const std::string &cmd) } else if(params.find("disk_path")!=params.end()) { - dl=params["disk_path"]; + dl= mapLinuxDev(params["disk_path"]); } #endif diff --git a/urbackupclient/lin_sysvol.h b/urbackupclient/lin_sysvol.h index d08d156c0..e33f72e55 100644 --- a/urbackupclient/lin_sysvol.h +++ b/urbackupclient/lin_sysvol.h @@ -2,6 +2,9 @@ #ifdef HAVE_MNTENT_H #include #endif +#include +#include +#include namespace { @@ -46,4 +49,28 @@ namespace { return getMountDevice("/"); } + + bool isDevice(const std::string& path) + { + struct stat stbuf; + if (stat(path, stbuf) == 0) + { + if (S_ISBLK(stat_buf.st_mode)) + { + return true; + } + } + return false; + } + + std::string mapLinuxDev(const std::string& path) + { + if (path == "C" || path == "C:") + return getRootVol(); + + if (isDevice(path)) + return path; + + return getMountDevice(path); + } } From 435f28f94483cc419c1f026aca6b8b899f689cbc Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 8 Nov 2020 21:28:22 +0100 Subject: [PATCH 026/469] Fix build (cherry picked from commit 9ef95dc2efcdd2a34f2ffa160d68853e6aab27c2) --- urbackupclient/lin_sysvol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/lin_sysvol.h b/urbackupclient/lin_sysvol.h index e33f72e55..268aa52c4 100644 --- a/urbackupclient/lin_sysvol.h +++ b/urbackupclient/lin_sysvol.h @@ -53,7 +53,7 @@ namespace bool isDevice(const std::string& path) { struct stat stbuf; - if (stat(path, stbuf) == 0) + if (stat(path.c_str(), stbuf) == 0) { if (S_ISBLK(stat_buf.st_mode)) { From f68bb99569321ae9599fc255cf52017165d57416 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 8 Nov 2020 22:30:33 +0100 Subject: [PATCH 027/469] Fix build (cherry picked from commit cbd6199540918f8d15298d4609f86a78fc7afd15) --- urbackupclient/lin_sysvol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/lin_sysvol.h b/urbackupclient/lin_sysvol.h index 268aa52c4..e05170567 100644 --- a/urbackupclient/lin_sysvol.h +++ b/urbackupclient/lin_sysvol.h @@ -55,7 +55,7 @@ namespace struct stat stbuf; if (stat(path.c_str(), stbuf) == 0) { - if (S_ISBLK(stat_buf.st_mode)) + if (S_ISBLK(stbuf.st_mode)) { return true; } From d4fc0075bf8414902a9bc8e988cd53a0472fd065 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 8 Nov 2020 23:41:34 +0100 Subject: [PATCH 028/469] Fix build (cherry picked from commit 677d6c22ba27c038b5ff0fe653b4b41ef8db0ea1) --- urbackupclient/lin_sysvol.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/lin_sysvol.h b/urbackupclient/lin_sysvol.h index e05170567..e497db23f 100644 --- a/urbackupclient/lin_sysvol.h +++ b/urbackupclient/lin_sysvol.h @@ -53,7 +53,7 @@ namespace bool isDevice(const std::string& path) { struct stat stbuf; - if (stat(path.c_str(), stbuf) == 0) + if (stat(path.c_str(), &stbuf) == 0) { if (S_ISBLK(stbuf.st_mode)) { From c3e882321521b919828bcdb0893970708273d3ed Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 25 Nov 2020 02:24:25 +0100 Subject: [PATCH 029/469] C_image_cowraw_bit was broken (should have been or-ed). Set to zero instead for backward compatibility (cherry picked from commit 0160d786ecb1702e9e9b9049a14bf1d511c26766) --- urbackupserver/ClientMain.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index c5346e34d..e26cce06a 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -86,7 +86,6 @@ const unsigned int c_internet_fileclient_timeout=30*60*1000; const unsigned int c_sleeptime_failed_imagebackup=20*60; const unsigned int c_sleeptime_failed_filebackup=20*60; const unsigned int c_exponential_backoff_div=2; -const unsigned int c_image_cowraw_bit=1024; const int64 max_ecdh_key_age = 6 * 60 * 60 * 1000; //6h @@ -351,11 +350,11 @@ void ClientMain::operator ()(void) if(server_settings->getImageFileFormat()==image_file_format_cowraw) { - curr_image_version = curr_image_version & c_image_cowraw_bit; + curr_image_version = 0; } else { - curr_image_version = curr_image_version & ~c_image_cowraw_bit; + curr_image_version = 1; } prepareSQL(); @@ -675,11 +674,11 @@ void ClientMain::operator ()(void) if(server_settings->getImageFileFormat()==image_file_format_cowraw) { - curr_image_version = curr_image_version & c_image_cowraw_bit; + curr_image_version = 0; } else { - curr_image_version = curr_image_version & ~c_image_cowraw_bit; + curr_image_version = 1; } updateVirtualClients(); From c970516834ef9628324400452360a227c3cda5ea Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 4 Dec 2020 21:11:10 +0100 Subject: [PATCH 030/469] Fix ipv6 accept issue in filesrv that caused 100% CPU usage (cherry picked from commit 0fa8b683f29c7e779489184c209754c8462c199b) --- fileservplugin/CTCPFileServ.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fileservplugin/CTCPFileServ.cpp b/fileservplugin/CTCPFileServ.cpp index be11b57b6..43fd2b12c 100644 --- a/fileservplugin/CTCPFileServ.cpp +++ b/fileservplugin/CTCPFileServ.cpp @@ -293,7 +293,7 @@ bool CTCPFileServ::TcpStep(void) SOCKET accept_socket = conn[s].fd; #endif SOCKET ns; - if (accept_socket == mSocketv6) + if (accept_socket == mSocket) { sockaddr_in naddr; socklen_t addrsize = sizeof(naddr); From e33c87c37fdde4ac2a5d0ee96a42de82d67b77fe Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 19 Feb 2021 14:41:17 +0100 Subject: [PATCH 031/469] Don't exit if in internet_only_mode and internet mode is not enabled (cherry picked from commit 4e61336b1147f264688724fb2c090c96c31e7761) --- urbackupclient/InternetClient.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/urbackupclient/InternetClient.cpp b/urbackupclient/InternetClient.cpp index 14b90b71d..095d55cd1 100644 --- a/urbackupclient/InternetClient.cpp +++ b/urbackupclient/InternetClient.cpp @@ -287,7 +287,6 @@ void InternetClient::doUpdateSettings(void) if(Server->getServerParameter("internet_only_mode")=="true") { Server->Log("Internet mode not enabled. Please set \"internet_mode_enabled\" to \"true\".", LL_ERROR); - exit(2); } else { From 78064612fe442ddd8a7c94dbbe6dee230dd97832 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Mar 2021 17:04:01 +0100 Subject: [PATCH 032/469] Fix compile issue --- urbackupclient/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 6f79c9460..b4963ec82 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -8481,7 +8481,7 @@ bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool f FLockFile(std:string fn, bool perm) : fn(fn), perm(perm) {} - std:string fn; + std::string fn; bool perm; }; std::vector flock_files; From 1daed0529e90b7a93287a7060e14893901521400 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Mar 2021 17:40:51 +0100 Subject: [PATCH 033/469] Fix compile issue --- urbackupclient/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index b4963ec82..4d1010c88 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -8478,7 +8478,7 @@ bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool f std::string cbt_file; struct FLockFile { - FLockFile(std:string fn, bool perm) + FLockFile(std::string fn, bool perm) : fn(fn), perm(perm) {} std::string fn; From af2c3b2250ad782ba01854a591c6176c4dc0d645 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Mar 2021 17:43:09 +0100 Subject: [PATCH 034/469] Fix compile issue --- urbackupclient/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 4d1010c88..70c14a699 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -8507,7 +8507,7 @@ bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool f } else if (next(line, 0, "FLOCK_PERM=")) { - flock_files_perm.push_back(FLockFile(line.substr(11), true)); + flock_files.push_back(FLockFile(line.substr(11), true)); } else { From b1d475a8123d10991570bc1330ab40ca6542eec8 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 9 Mar 2021 17:44:53 +0100 Subject: [PATCH 035/469] Fix compile issue --- urbackupclient/client.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.h b/urbackupclient/client.h index 9d584f699..b96db25c9 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -880,7 +880,7 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public static unsigned int next_result_id; static IMutex* result_mutex; - std::vector flock_fds_perm; + std::vector flock_fds_perm; #ifdef _WIN32 struct SComponent From c51c1ab9cfa48e70b79b92a662d1234c2c1201f3 Mon Sep 17 00:00:00 2001 From: Moisie2000 Date: Tue, 7 Jul 2020 06:51:45 +0100 Subject: [PATCH 036/469] Adding settings status symbols to dist --- Makefile.am_client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile.am_client b/Makefile.am_client index f09ddb5ca..6f23e6065 100644 --- a/Makefile.am_client +++ b/Makefile.am_client @@ -346,6 +346,6 @@ zstd_headers = \ noinst_HEADERS=SessionMgr.h WorkerThread.h Helper_win32.h Database.h defaults.h ServiceAcceptor.h Query.h SettingsReader.h file.h file_memory.h MemorySettingsReader.h Condition_lin.h LookupService.h Template.h types.h DBSettingsReader.h stringtools.h ThreadPool.h libs.h vld_.h ServiceWorker.h StreamPipe.h LoadbalancerClient.h socket_header.h FileSettingsReader.h SelectThread.h md5.h vld.h Table.h Client.h MemoryPipe.h Mutex_lin.h AcceptThread.h OutputStream.h Server.h Interface/SessionMgr.h Interface/Service.h Interface/PluginMgr.h Interface/Database.h Interface/Pipe.h Interface/CustomClient.h Interface/User.h Interface/Query.h Interface/SettingsReader.h Interface/Types.h Interface/Template.h Interface/ThreadPool.h Interface/Mutex.h Interface/File.h Interface/Condition.h Interface/Table.h Interface/Plugin.h Interface/Thread.h Interface/Action.h Interface/Object.h Interface/OutputStream.h Interface/Server.h libfastcgi/fastcgi.hpp sqlite/sqlite3.h sqlite/sqlite3ext.h utf8/utf8.h utf8/utf8/checked.h utf8/utf8/core.h utf8/utf8/unchecked.h cryptoplugin/ICryptoFactory.h cryptoplugin/IAESEncryption.h cryptoplugin/IAESDecryption.h Interface/DatabaseFactory.h Interface/DatabaseInt.h sqlite/shell.h SQLiteFactory.h PipeThrottler.h Interface/PipeThrottler.h mt19937ar.h DatabaseCursor.h Interface/DatabaseCursor.h Interface/WebSocket.h client_version.h Interface/SharedMutex.h SharedMutex_lin.h StaticPluginRegistration.h common/bitmap.h OpenSSLPipe.h $(cryptoplugin_headers) $(fileservplugin_headers) $(fsimageplugin_headers) $(urbackupclientctl_headers) $(client_headers) $(tclap_headers) $(urbackupclient_headers) $(cryptopp_headers) $(blockalign_headers) $(zstd_headers) -EXTRA_DIST_GUI = client/info.txt client/data/backup-bad.xpm client/data/backup-ok.xpm client/data/backup-progress.xpm client/data/backup-progress-pause.xpm client/data/backup-no-server.xpm client/data/backup-no-recent.xpm client/data/backup-indexing.xpm client/data/logo1.png client/data/lang/it/urbackup.mo client/data/lang/pl/urbackup.mo client/data/lang/pt_BR/urbackup.mo client/data/lang/sk/urbackup.mo client/data/lang/zh_TW/urbackup.mo client/data/lang/zh_CN/urbackup.mo client/data/lang/de/urbackup.mo client/data/lang/es/urbackup.mo client/data/lang/fr/urbackup.mo client/data/lang/ru/urbackup.mo client/data/lang/uk/urbackup.mo client/data/lang/da/urbackup.mo client/data/lang/nl/urbackup.mo client/data/lang/fa/urbackup.mo client/data/lang/cs/urbackup.mo client/gui/GUISetupWizard.h client/SetupWizard.h +EXTRA_DIST_GUI = client/info.txt client/data/backup-bad.xpm client/data/backup-ok.xpm client/data/backup-progress.xpm client/data/backup-progress-pause.xpm client/data/backup-no-server.xpm client/data/backup-no-recent.xpm client/data/backup-indexing.xpm client/data/logo1.png client/data/lang/it/urbackup.mo client/data/lang/pl/urbackup.mo client/data/lang/pt_BR/urbackup.mo client/data/lang/sk/urbackup.mo client/data/lang/zh_TW/urbackup.mo client/data/lang/zh_CN/urbackup.mo client/data/lang/de/urbackup.mo client/data/lang/es/urbackup.mo client/data/lang/fr/urbackup.mo client/data/lang/ru/urbackup.mo client/data/lang/uk/urbackup.mo client/data/lang/da/urbackup.mo client/data/lang/nl/urbackup.mo client/data/lang/fa/urbackup.mo client/data/lang/cs/urbackup.mo client/gui/GUISetupWizard.h client/SetupWizard.h client/fa-copy.png client/fa-home.png client/fa-lock.png client/fa-road.png EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/list_incr urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbxtrabackup_incr urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat From 6d7197dd0f4cb1f07d3c54afb673869a43e321c4 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 10 Mar 2021 18:12:52 +0100 Subject: [PATCH 037/469] Increment version --- configure.ac_client | 2 +- configure.ac_server | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 07589e9ae..3461b3089 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.11.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.12.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/configure.ac_server b/configure.ac_server index 2783e8786..f338a5eb6 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.16.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.18.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) From 5ce6ad283000bc8ae67be76fe7022738e1875b33 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 14 Nov 2020 18:36:44 +0100 Subject: [PATCH 038/469] Merge pull request #40 from Moisie2000/fix_macos_installer_2 Move the Application Support folder into osx-pkg before packaging (cherry picked from commit dd3e18a8e3acd638a15c95f2254ad05260153322) (cherry picked from commit bb8dd509d69b77f5206269181fd8c403e998ed89) --- create_osx_installer.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 439cb240d..057e5baee 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -57,6 +57,8 @@ else fi cp osx_installer/urbackup.icns "osx-pkg2/Applications/UrBackup Client.app/Contents/Resources/" cp osx_installer/buildmacOSexclusions "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/buildmacOSexclusions" +mv "osx-pkg2/Library/Application Support" "osx-pkg/Library" +rm -R "osx-pkg2/Library" mv "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/urbackupclientgui" "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/" if !($development); then strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/urbackupclientgui" From e4327015759f4d8c1838de7699a11e91f0554b33 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 14 Mar 2021 16:07:49 +0100 Subject: [PATCH 039/469] Merge pull request #49 from grumat/dev Nested call to popen causes crashes in MacOS X High Sierra (cherry picked from commit d7ba9de40c64811ee2079816964e3e68acaced93) --- fileservplugin/CUDPThread.cpp | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/fileservplugin/CUDPThread.cpp b/fileservplugin/CUDPThread.cpp index 5bffca65b..b41a4aafe 100644 --- a/fileservplugin/CUDPThread.cpp +++ b/fileservplugin/CUDPThread.cpp @@ -49,7 +49,7 @@ namespace std::string mac_get_serial() { char buf[4096]; - FILE* fd = popen("system_profiler SPHardwareDataType | grep \"Serial Number\"", "r"); + FILE* fd = popen("/usr/sbin/system_profiler SPHardwareDataType | grep \"Serial Number\"", "r"); std::string serial; if (fd != NULL) { @@ -57,6 +57,7 @@ std::string mac_get_serial() { serial = trim(getafter(":", buf)); } + pclose(fd); } if (!serial.empty()) @@ -76,17 +77,17 @@ std::string mac_get_serial() std::string getSystemServerName(bool use_fqdn) { - char hostname[MAX_PATH]; #ifdef __APPLE__ //TODO: Fix FQDN for Apple while (true) { char hostname_appl[MAX_PATH + 15]; - FILE* fd = popen("system_profiler SPSoftwareDataType | grep \"Computer Name: \"", "r"); + FILE* fd = popen("/usr/sbin/system_profiler SPSoftwareDataType | grep \"Computer Name: \"", "r"); if (fd != NULL) { if (fgets(hostname_appl, sizeof(hostname_appl), fd) != NULL) { + pclose(fd); std::string chostname = getafter("Computer Name: ", trim(hostname_appl)); if (chostname.empty()) { @@ -107,7 +108,8 @@ std::string getSystemServerName(bool use_fqdn) return chostname + "-" + mac_add; } - pclose(fd); + else + pclose(fd); } else { @@ -116,6 +118,8 @@ std::string getSystemServerName(bool use_fqdn) } #else + char hostname[MAX_PATH]; + _i32 rc=gethostname(hostname, MAX_PATH); if(rc==SOCKET_ERROR) From 620c8ccffdf71436ad7d7697b62ae7c200416200 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 14 Mar 2021 16:29:31 +0100 Subject: [PATCH 040/469] Fix compile issue --- urbackupclient/client.cpp | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 70c14a699..3433c31f9 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -8434,6 +8434,18 @@ int64 IndexThread::getChangeIndicator(const SFile & file) } #ifndef _WIN32 +namespace +{ + struct FLockFile + { + FLockFile(std::string fn, bool perm) + : fn(fn), perm(perm) {} + + std::string fn; + bool perm; + }; +} + bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool for_imagebackup, bool * &onlyref, bool* not_configured) { std::string scriptname; @@ -8476,14 +8488,7 @@ bool IndexThread::start_shadowcopy_lin( SCDirs * dir, std::string &wpath, bool f std::string snapshot_target; std::string cbt_info; std::string cbt_file; - struct FLockFile - { - FLockFile(std::string fn, bool perm) - : fn(fn), perm(perm) {} - - std::string fn; - bool perm; - }; + std::vector flock_files; for(size_t i=0;i Date: Sun, 14 Mar 2021 17:13:48 +0100 Subject: [PATCH 041/469] Set macos min version in CFLAGS --- create_osx_installer.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 057e5baee..d269dedff 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -40,9 +40,9 @@ cp osx_installer/daemon.plist osx-pkg/Library/LaunchDaemons/org.urbackup.client. mkdir -p osx-pkg/Library/LaunchAgents cp osx_installer/agent.plist osx-pkg/Library/LaunchAgents/org.urbackup.client.plist if !($development); then - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" else - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" fi make clean make -j5 From 55de34d6dacf280452ea29a05671bdc1340ebc6f Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 23 Mar 2021 17:26:44 +0100 Subject: [PATCH 042/469] Fix internet server protocol handling (cherry picked from commit 321d01993272db24d628bf8dfd9a35490562e260) --- urbackupserver/www/js/urbackup.js | 8 +++++--- urbackupserver/www/templates/settings_inv_row.htm | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 581ac1b47..824c67497 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -3408,20 +3408,22 @@ function show_settings2(data) g.curr_settings = data.settings; data.settings = getCurrentSettings(data.settings); data.settings.backup_dirs_optional=getCheckboxValue(data.settings.backup_dirs_optional); - var internet_server = data.settings.internet_server; + var internet_server = unescapeHTML(data.settings.internet_server); if(internet_server.indexOf("ws://")!=0 && internet_server.indexOf("wss://")!=0 && internet_server.indexOf("urbackup://")!=0 ) { if(data.settings.internet_server_port==55415) { - data.settings.internet_server = "urbackup://" + internet_server; + internet_server = "urbackup://" + internet_server; } else { - data.settings.internet_server = "urbackup://" + internet_server + ":" + data.settings.internet_server_port; + internet_server = "urbackup://" + internet_server + ":" + data.settings.internet_server_port; } } + + data.settings.internet_server = internet_server; var transfer_mode_params1=["raw", "hashed"]; var transfer_mode_params2=["raw", "hashed", "blockhash"]; diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index a57c97d45..3e5b438ee 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -516,7 +516,7 @@
    - +
    From 18e2b5973cdeb8fbb79a5e6302a08f3a5297c105 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 23 Mar 2021 17:29:54 +0100 Subject: [PATCH 043/469] Update templates --- urbackupserver/www/js/templates.js | 58 +++++++++++++++--------------- 1 file changed, 29 insertions(+), 29 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index cb8e1aba5..912a278c4 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,14 +1,17 @@ -(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
     

    ").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



    ").f(ctx.get(["tAlert script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tPreparing restore. Please be patient..."], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAccess denied"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong or you do not have the required rights to access this file or folder."], false),ctx,"h").x(ctx.get(["errcode"], false),ctx,{"block":body_1},{}).w("

    ").f(ctx.get(["tLogin with username and password"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("(").f(ctx.get(["errcode"], false),ctx,"h").w(")");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClients"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tError while accessing backups"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong:"], false),ctx,"h").w(" ").f(ctx.get(["err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); -(function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); (function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.w("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanging password failed:"], false),ctx,"h").w("
    ").f(ctx.get(["fail_reason"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").f(ctx.get(["internet_server"], false),ctx,"h").w(" -k internet_server_port -v ").f(ctx.get(["internet_server_port"], false),ctx,"h").w(" -k computername -v \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" -k internet_authkey -v ").f(ctx.get(["new_authkey"], false),ctx,"h").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the internet server to:"], false),ctx,"h").w(" ").f(ctx.get(["internet_server"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the internet server port to:"], false),ctx,"h").w(" ").f(ctx.get(["internet_server_port"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the computer name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").f(ctx.get(["internet_server"], false),ctx,"h").w(" -k internet_server_port -v ").f(ctx.get(["internet_server_port"], false),ctx,"h").w(" -k computername -v \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" -k internet_authkey -v ").f(ctx.get(["new_authkey"], false),ctx,"h").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;return body_0;})(); @@ -17,9 +20,8 @@ (function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["creating_filesindex_text"], false),ctx,"h").w("
    ").f(ctx.get(["tNumber of file entries processed"], false),ctx,"h").w(": ").f(ctx.get(["processed_file_entries"], false),ctx,"h").w("
    ").f(ctx.get(["tPercent finished"], false),ctx,"h").w(": ").f(ctx.get(["percent_finished"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

    ").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["id"], false),ctx,"h").w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["is_image"], false),ctx,{"else":body_1,"block":body_4},{}).w("").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["duration"], false),ctx,"h").w("").f(ctx.get(["size"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("-");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("Path: ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("Volume: ").f(ctx.get(["details"], false),ctx,"h");}body_4.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLast activities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tID"], false),ctx,"h").w("").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tStarting time"], false),ctx,"h").w("").f(ctx.get(["tRequired time"], false),ctx,"h").w("").f(ctx.get(["tUsed Storage"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLog"], false),ctx,"h").w(": (").f(ctx.get(["name"], false),ctx,"h").w(")
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tLevel"], false),ctx,"h").w("").f(ctx.get(["tTime"], false),ctx,"h").w("").f(ctx.get(["tMessage"], false),ctx,"h").w("

    ").f(ctx.get(["tBack"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); @@ -28,49 +30,47 @@ (function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("logs_report_mail",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["report_single_mail"], false),ctx,"h").w(" -");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["time"], false),ctx,"h").w("").f(ctx.get(["errors"], false),ctx,"h").w("
    ").f(ctx.get(["warnings"], false),ctx,"h").w("
    ").f(ctx.get(["action"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLogs"], false),ctx,"h").w("
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tErrors"], false),ctx,"h").w("").f(ctx.get(["tWarnings"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("
    ").f(ctx.get(["tLive Log"], false),ctx,"h").w("
    ").f(ctx.get(["tReports"], false),ctx,"h").w("
    ").x(ctx.get(["has_user"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tYou need to create a user to be able to send reports"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

     
    +
    ").x(ctx.get(["can_report_script_edit"], false),ctx,{"block":body_3},{}).w("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("

    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThere is a new version of UrBackup server available"], false),ctx,"h").w(" (").f(ctx.get(["new_version_number"], false),ctx,"h").w("). Download it here.
    ").f(ctx.get(["tOk. Stop showing this."], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["time"], false),ctx,"h").w("").f(ctx.get(["errors"], false),ctx,"h").w("
    ").f(ctx.get(["warnings"], false),ctx,"h").w("
    ").f(ctx.get(["action"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["nospc_fatal_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tETA"], false),ctx,"h").w("").f(ctx.get(["tSpeed"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("

    ").f(ctx.get(["tReport script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); +(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tETA"], false),ctx,"h").w("").f(ctx.get(["tSpeed"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["ONLY_WIN32_BEGIN"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["ONLY_WIN32_END"], false),ctx,"h",["s"]).w("
    MBit/s
     
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest Mail sent successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSaved settings successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["msg"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["rights"], false),ctx,"h").w("").x(ctx.get(["can_change"], false),ctx,{"block":body_1},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w(" ");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo Users"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["tBackup Statistics"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_2},{}).w("\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["tAll"], false),ctx,"h").w("
    ").f(ctx.get(["tSum"], false),ctx,"h").w("
    ").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["images_total"], false),ctx,"h").w("
    ").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["files_total"], false),ctx,"h").w("
    ").f(ctx.get(["tAll"], false),ctx,"h").w("").f(ctx.get(["used_total"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_3},{}).w("
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_4},{}).w("
    ").f(ctx.get(["tStorage allocation"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_5},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ");}body_5.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tDownload client for Windows"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["tDownload client for Mac OS X"], false),ctx,"h");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tDownload client for Linux"], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); (function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["hostname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSelect all"], false),ctx,"h").w("").f(ctx.get(["tSelect none"], false),ctx,"h").w("").f(ctx.get(["rem_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["tRemove selected"], false),ctx,"h").w("").f(ctx.get(["rem_stop"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["groupname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w(" ").x(ctx.get(["online_add_status"], false),ctx,{"block":body_2},{}).w(" ").x(ctx.get(["reset_client_uid"], false),ctx,{"block":body_3},{}).w("").f(ctx.get(["status"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastseen"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h").f(ctx.get(["start_file_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastbackup_image"], false),ctx,"h").f(ctx.get(["start_image_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["file_ok_t"], false),ctx,"h").w("").f(ctx.get(["image_ok_t"], false),ctx,"h").w("").f(ctx.get(["ip"], false),ctx,"h").w("").f(ctx.get(["client_version_string"], false),ctx,"h").w("").f(ctx.get(["os_version_string"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("(").f(ctx.get(["status"], false),ctx,"h",["s"]).w(")");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tAllow new client"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_2},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_3},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("min-width: 2em;");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tmpdir_error_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_2},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_3},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("min-width: 2em;");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["upgrade_error_text"], false),ctx,"h").w("
    ").f(ctx.get(["tCurrent version"], false),ctx,"h").w(": ").f(ctx.get(["curr_db_version"], false),ctx,"h").w("
    ").f(ctx.get(["tTarget version"], false),ctx,"h").w(": ").f(ctx.get(["target_db_version"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSelect all"], false),ctx,"h").w("").f(ctx.get(["tSelect none"], false),ctx,"h").w("").f(ctx.get(["rem_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["tRemove selected"], false),ctx,"h").w("").f(ctx.get(["rem_stop"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.w("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").f(ctx.get(["virus_error_path"], false),ctx,"h").w(" ).").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); From b7f6f37d126ee8b24b2de4fc176b59109b10b083 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 25 Mar 2021 19:40:52 +0100 Subject: [PATCH 044/469] Increment versions --- configure.ac_client | 2 +- configure.ac_server | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 3461b3089..55b776dda 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.12.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.13.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/configure.ac_server b/configure.ac_server index f338a5eb6..3fa7e6849 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.18.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.19.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) From 5362f4da128c835f9bf991fe12790011f4bcb7c0 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 26 Mar 2021 00:23:39 +0100 Subject: [PATCH 045/469] Don't delete old snapshots on Linux as long as they are still used (cherry picked from commit bedbe0bde12e978fbac3513244a883b4b5cc19ac) --- fileservplugin/FileMetadataPipe.cpp | 2 +- urbackupclient/client.cpp | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/fileservplugin/FileMetadataPipe.cpp b/fileservplugin/FileMetadataPipe.cpp index 0f7b8b519..9afe35812 100644 --- a/fileservplugin/FileMetadataPipe.cpp +++ b/fileservplugin/FileMetadataPipe.cpp @@ -511,7 +511,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t if (file_type_flags == 0) { - Server->Log("Error getting file type of " + local_fn, LL_ERROR); + Server->Log("Error getting file type of " + local_fn+". "+os_last_error_str(), LL_ERROR); *buf = ID_METADATA_NOP; read_bytes = 1; PipeSessions::fileMetadataDone(public_fn, server_token); diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 3433c31f9..97de48e72 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -3469,6 +3469,24 @@ bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allo || only_own_tokens || cannot_open_shadowcopy ) ) { +#ifndef _WIN32 + int64 wait_starttime = Server->getTimeMS(); + + while (filesrv != NULL + && filesrv->hasActiveTransfers(dir->dir, starttoken) + && Server->getTimeMS() - wait_starttime < 5000) + { + Server->wait(100); + } + + if (filesrv != NULL + && filesrv->hasActiveTransfers(dir->dir, starttoken)) + { + VSSLog("Old shadow copy of " + sc_refs[i]->target + " still in use. Not deleting or using it.", LL_WARNING); + continue; + } +#endif + if ( (sc_refs[i]->for_imagebackup == for_imagebackup) || !simultaneous_other ) { From 6d1c2cfdecadc6d65a5880eeec6a3844eb1f3c60 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 29 Mar 2021 14:27:29 +0200 Subject: [PATCH 046/469] Sync new shadow copy to disk (cherry picked from commit f26cf9909ff04fa16410bc9083bb631cd2059429) --- urbackupclient/DirectoryWatcherThread.cpp | 1 + urbackupclient/clientdao.cpp | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/urbackupclient/DirectoryWatcherThread.cpp b/urbackupclient/DirectoryWatcherThread.cpp index 9a175d9b8..e7bde2289 100644 --- a/urbackupclient/DirectoryWatcherThread.cpp +++ b/urbackupclient/DirectoryWatcherThread.cpp @@ -398,6 +398,7 @@ bool DirectoryWatcherThread::is_stopped(void) void DirectoryWatcherThread::On_ResetAll(const std::string & vol) { + DBScopedSynchronous sync_db(db); OnDirMod("##-GAP-##"+strlower(vol)); } diff --git a/urbackupclient/clientdao.cpp b/urbackupclient/clientdao.cpp index f13ca851c..5df7287cf 100644 --- a/urbackupclient/clientdao.cpp +++ b/urbackupclient/clientdao.cpp @@ -470,6 +470,8 @@ std::vector ClientDAO::getShadowcopies(void) int ClientDAO::addShadowcopy(const SShadowCopy &sc) { + DBScopedSynchronous sync_db(db); + q_insert_shadowcopy->Bind((char*)&sc.vssid, sizeof(GUID) ); q_insert_shadowcopy->Bind((char*)&sc.ssetid, sizeof(GUID) ); q_insert_shadowcopy->Bind(sc.target); @@ -488,6 +490,8 @@ int ClientDAO::addShadowcopy(const SShadowCopy &sc) int ClientDAO::modShadowcopyRefCount(int id, int m) { + DBScopedSynchronous sync_db(db); + q_get_shadowcopy_refcount->Bind(id); db_results res=q_get_shadowcopy_refcount->Read(); q_get_shadowcopy_refcount->Reset(); @@ -506,6 +510,8 @@ int ClientDAO::modShadowcopyRefCount(int id, int m) void ClientDAO::deleteShadowcopy(int id) { + DBScopedSynchronous sync_db(db); + q_remove_shadowcopies->Bind(id); q_remove_shadowcopies->Write(); q_remove_shadowcopies->Reset(); From 2900ef49051c7fa5f12f0eb71664bf37820982ca Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 29 Mar 2021 15:08:18 +0200 Subject: [PATCH 047/469] Cleanup snapshot later if it cannot be deleted because it is in use (cherry picked from commit fc3ccacf05f4163981b972d2aff8a2b7d04c1cd8) --- urbackupclient/client.cpp | 108 +++++++++++++++++++++++++++++++++++--- urbackupclient/client.h | 7 ++- 2 files changed, 108 insertions(+), 7 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 97de48e72..d875145bb 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -542,7 +542,7 @@ std::string add_trailing_slash(const std::string &strDirName) IndexThread::IndexThread(void) : index_error(false), last_filebackup_filetime(0), index_group(-1), with_scripts(false), volumes_cache(NULL), phash_queue(NULL), - index_backup_dirs_optional(false) + index_backup_dirs_optional(false), sc_refs_cleanup(false) { if(filelist_mutex==NULL) filelist_mutex=Server->createMutex(); @@ -691,11 +691,7 @@ void IndexThread::operator()(void) db=Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT); cd=new ClientDAO(Server->getDatabase(Server->getThreadID(), URBACKUPDB_CLIENT)); -#ifdef _WIN32 -#ifdef ENABLE_VSS cleanup_saved_shadowcopies(); -#endif -#endif updateDirs(); register_token_callback(); @@ -714,7 +710,13 @@ void IndexThread::operator()(void) } std::string msg; - msgpipe->Read(&msg); + msgpipe->Read(&msg, sc_refs_cleanup ? 60000 : -1); + + if (msg.empty()) + { + run_sc_refs_cleanup(); + continue; + } async_timeout = false; CRData data(&msg); @@ -3482,6 +3484,8 @@ bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allo if (filesrv != NULL && filesrv->hasActiveTransfers(dir->dir, starttoken)) { + sc_refs[i]->cleanup = true; + sc_refs_cleanup = true; VSSLog("Old shadow copy of " + sc_refs[i]->target + " still in use. Not deleting or using it.", LL_WARNING); continue; } @@ -3551,6 +3555,7 @@ bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allo } else if(!cannot_open_shadowcopy) { + sc_refs[i]->cleanup = false; dir->ref=sc_refs[i]; if(!dir->ref->dontincrement) { @@ -3980,6 +3985,9 @@ SCDirs* IndexThread::getSCDir(const std::string& path, const std::string& client std::map::iterator it=scdirs_server.find(path); if(it!=scdirs_server.end()) { + if (it->second->ref != NULL) + it->second->ref->cleanup = false; + return it->second; } else @@ -6473,6 +6481,94 @@ bool IndexThread::punchHoleOrZero(IFile* f, int64 pos, const char* zero_buf, ch } return true; +} + +void IndexThread::run_sc_refs_cleanup() +{ + bool has_cleanup = false; + bool retry_all = true; + while(retry_all) + { + retry_all = false; + for (size_t i = 0; i < sc_refs.size(); ++i) + { + if (sc_refs[i]->cleanup) + { + has_cleanup = true; + + bool in_use = false; + + SCRef* curr = sc_refs[i]; + for (std::map >::iterator it_scdirs = scdirs.begin(); + it_scdirs != scdirs.end(); ++it_scdirs) + { + std::map& scdirs_server = it_scdirs->second; + + VSS_ID ssetid = curr->ssetid; + + bool in_use = false; + std::vector paths; + for (std::map::iterator it = scdirs_server.begin(); + it != scdirs_server.end(); ++it) + { + if (filesrv != NULL + && filesrv->hasActiveTransfers(it->second->dir, it_scdirs->first.start_token)) + { + VSSLog(it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " still in use. Not releasing.", LL_DEBUG); + in_use = true; + break; + } + + paths.push_back(it->first); + } + + if (in_use) + break; + + for (size_t j = 0; j < paths.size(); ++j) + { + std::map::iterator it = scdirs_server.find(paths[j]); + if (it != scdirs_server.end() + && it->second->ref == curr) + { + VSSLog("Releasing " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG); + release_shadowcopy(it->second, false, -1); + } + } + + bool retry = true; + while (retry) + { + retry = false; + for (std::map::iterator it = scdirs_server.begin(); + it != scdirs_server.end(); ++it) + { + if (it->second->ref != NULL + && it->second->ref != curr + && it->second->ref->ssetid == ssetid) + { + VSSLog("Releasing group shadow copy " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG); + release_shadowcopy(it->second, false, -1); + retry = true; + break; + } + } + } + } + + if (in_use) + continue; + + retry_all = true; + break; + } + } + } + + if (!has_cleanup) + { + sc_refs_cleanup = false; + } } bool IndexThread::finishCbt(std::string volume, int shadow_id, std::string snap_volume, diff --git a/urbackupclient/client.h b/urbackupclient/client.h index b96db25c9..3c9e81c71 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -90,7 +90,7 @@ enum CbtType struct SCRef { - SCRef(void): ok(false), dontincrement(false), cbt(false), + SCRef(void): ok(false), dontincrement(false), cleanup(false), cbt(false), for_imagebackup(false), with_writers(false), cbt_type(CbtType_None) { #ifdef _WIN32 @@ -109,6 +109,7 @@ struct SCRef int save_id; bool ok; bool dontincrement; + bool cleanup; std::vector starttokens; std::string clientsubname; bool cbt; @@ -643,6 +644,8 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public bool punchHoleOrZero(IFile* f, int64 pos, const char* zero_buf, char* zero_read_buf, size_t zero_size); + void run_sc_refs_cleanup(); + SVolumesCache* volumes_cache; std::auto_ptr background_prio; @@ -678,6 +681,8 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public std::map > scdirs; std::vector sc_refs; + bool sc_refs_cleanup; + int index_c_db; int index_c_fs; int index_c_db_update; From e4632a6af2250d6e8942085ca3c1d42d9c89a7fd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 30 Mar 2021 02:23:06 +0200 Subject: [PATCH 048/469] Add generation number to share refcounting (cherry picked from commit 07b3a65cc7c14aab2f6c3e8b8e1c44d52a684f15) # Conflicts: # fileservplugin/FileServ.cpp # fileservplugin/FileServ.h # fileservplugin/IFileServ.h --- fileservplugin/CClientThread.cpp | 4 +- fileservplugin/CClientThread.h | 1 + fileservplugin/ChunkSendThread.cpp | 7 +-- fileservplugin/ChunkSendThread.h | 1 + fileservplugin/FileMetadataPipe.cpp | 44 +++++++++------- fileservplugin/FileMetadataPipe.h | 1 + fileservplugin/FileServ.cpp | 52 +++++++++++++++--- fileservplugin/FileServ.h | 24 ++++++--- fileservplugin/IFileServ.h | 2 + fileservplugin/PipeSessions.cpp | 82 ++++++++++++++++++++++------- fileservplugin/PipeSessions.h | 10 +++- 11 files changed, 167 insertions(+), 61 deletions(-) diff --git a/fileservplugin/CClientThread.cpp b/fileservplugin/CClientThread.cpp index 9c1debeae..392fca78e 100644 --- a/fileservplugin/CClientThread.cpp +++ b/fileservplugin/CClientThread.cpp @@ -212,7 +212,7 @@ void CClientThread::operator()(void) { if (next_chunks.front().pipe_file_user == NULL) { - FileServ::decrShareActive(next_chunks.front().s_filename); + FileServ::decrShareActive(next_chunks.front().s_filename, next_chunks.front().share_active_gen); Server->destroy(next_chunks.front().update_file); } delete next_chunks.front().pipe_file_user; @@ -2008,7 +2008,7 @@ bool CClientThread::GetFileBlockdiff(CRData *data, bool with_metadata) hFile=INVALID_HANDLE_VALUE; - scoped_share_active.release(); + chunk.share_active_gen = scoped_share_active.release(); queueChunk(chunk); diff --git a/fileservplugin/CClientThread.h b/fileservplugin/CClientThread.h index 78c29507f..3730639fc 100644 --- a/fileservplugin/CClientThread.h +++ b/fileservplugin/CClientThread.h @@ -73,6 +73,7 @@ struct SChunk bool with_sparse; std::string s_filename; IFileServ::CbtHashFileInfo cbt_hash_file_info; + size_t share_active_gen; }; struct SLPData diff --git a/fileservplugin/ChunkSendThread.cpp b/fileservplugin/ChunkSendThread.cpp index 0fab64f20..78aaad141 100644 --- a/fileservplugin/ChunkSendThread.cpp +++ b/fileservplugin/ChunkSendThread.cpp @@ -92,7 +92,7 @@ void ChunkSendThread::operator()(void) Server->Log("Closing file (free) " + file->getFilename(), LL_DEBUG); Server->destroy(file); assert(!s_filename.empty()); - FileServ::decrShareActive(s_filename); + FileServ::decrShareActive(s_filename, share_active_gen); file = NULL; } else if (pipe_file_user.get() != NULL) @@ -125,7 +125,7 @@ void ChunkSendThread::operator()(void) Server->Log("Closing file " + file->getFilename(), LL_DEBUG); Server->destroy(file); assert(!s_filename.empty()); - FileServ::decrShareActive(s_filename); + FileServ::decrShareActive(s_filename, share_active_gen); } if (cbt_hash_file_info.cbt_hash_file != NULL && cbt_hash_file_info.metadata_offset != -1) @@ -136,6 +136,7 @@ void ChunkSendThread::operator()(void) file=chunk.update_file; Server->Log("Retaining file " + file->getFilename(), LL_DEBUG); s_filename = chunk.s_filename; + share_active_gen = chunk.share_active_gen; curr_hash_size=chunk.hashsize; curr_file_size =chunk.startpos; curr_max_vdl = -1; @@ -222,7 +223,7 @@ void ChunkSendThread::operator()(void) Server->Log("Closing file (finish) " + file->getFilename(), LL_DEBUG); Server->destroy(file); assert(!s_filename.empty()); - FileServ::decrShareActive(s_filename); + FileServ::decrShareActive(s_filename, share_active_gen); file=NULL; } diff --git a/fileservplugin/ChunkSendThread.h b/fileservplugin/ChunkSendThread.h index a255849df..756ee8570 100644 --- a/fileservplugin/ChunkSendThread.h +++ b/fileservplugin/ChunkSendThread.h @@ -28,6 +28,7 @@ class ChunkSendThread : public IThread CClientThread *parent; IFile *file; std::string s_filename; + size_t share_active_gen; std::auto_ptr pipe_file_user; _i64 curr_hash_size; _i64 curr_file_size; diff --git a/fileservplugin/FileMetadataPipe.cpp b/fileservplugin/FileMetadataPipe.cpp index 9afe35812..59deaa0a3 100644 --- a/fileservplugin/FileMetadataPipe.cpp +++ b/fileservplugin/FileMetadataPipe.cpp @@ -160,7 +160,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t read_bytes=0; metadata_file.reset(); - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); metadata_state = MetadataState_Wait; return false; } @@ -273,7 +273,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t if(fn_off==sizeof(unsigned int)) { - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); metadata_state = MetadataState_Wait; } @@ -319,7 +319,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t if(fn_off==sizeof(unsigned int)) { metadata_file.reset(); - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); metadata_state = MetadataState_Wait; } @@ -335,7 +335,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t } if (raw_metadata.size() - metadata_buffer_off == 0) { - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); metadata_state = MetadataState_Wait; } return true; @@ -450,7 +450,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t transmit_wait_pipe->Write(std::string()); transmit_wait_pipe = NULL; transmit_file = NULL; - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); metadata_state = MetadataState_Wait; } return true; @@ -488,13 +488,14 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t msg_data.getStr(&local_fn) && msg_data.getInt64(&folder_items) && msg_data.getInt64(&metadata_id) && - msg_data.getStr(&server_token)) + msg_data.getStr(&server_token) && + msg_data.getInt64(&active_gen)) { assert(!public_fn.empty() || !local_fn.empty()); if (std::find(last_public_fns.begin(), last_public_fns.end(), public_fn) != last_public_fns.end()) { - PipeSessions::fileMetadataDone(public_fn, server_token); + PipeSessions::fileMetadataDone(public_fn, server_token, active_gen); *buf = ID_METADATA_NOP; read_bytes = 1; return true; @@ -514,7 +515,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t Server->Log("Error getting file type of " + local_fn+". "+os_last_error_str(), LL_ERROR); *buf = ID_METADATA_NOP; read_bytes = 1; - PipeSessions::fileMetadataDone(public_fn, server_token); + PipeSessions::fileMetadataDone(public_fn, server_token, active_gen); return true; } @@ -528,7 +529,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t Server->Log("Error opening file handle to " + local_fn+". "+os_last_error_str(), LL_ERROR); *buf = ID_METADATA_NOP; read_bytes = 1; - PipeSessions::fileMetadataDone(public_fn, server_token); + PipeSessions::fileMetadataDone(public_fn, server_token, active_gen); return true; } @@ -569,7 +570,7 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t *buf = ID_METADATA_NOP; read_bytes = 1; metadata_state = MetadataState_Wait; - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); return true; } @@ -585,7 +586,8 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t else if (id == METADATA_PIPE_SEND_RAW && msg_data.getStr(&public_fn) && msg_data.getStr(&raw_metadata) - && msg_data.getStr(&server_token)) + && msg_data.getStr(&server_token) + && msg_data.getInt64(&active_gen)) { metadata_state = MetadataState_Raw; metadata_buffer_off = 0; @@ -595,7 +597,8 @@ bool FileMetadataPipe::readStdoutIntoBuffer( char* buf, size_t buf_avail, size_t && msg_data.getStr(&public_fn) && msg_data.getVoidPtr(reinterpret_cast(&transmit_file)) && msg_data.getVoidPtr(reinterpret_cast(&transmit_wait_pipe)) - && msg_data.getStr(&server_token)) + && msg_data.getStr(&server_token) + && msg_data.getInt64(&active_gen) ) { metadata_state = MetadataState_RawFileFnSize; *buf = ID_RAW_FILE; @@ -659,7 +662,7 @@ void FileMetadataPipe::cleanupOnForceShutdown() { if (metadata_state != MetadataState_Wait) { - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); } metadata_file.reset(); @@ -698,24 +701,27 @@ void FileMetadataPipe::cleanupOnForceShutdown() msg_data.getStr(&local_fn) && msg_data.getInt64(&folder_items) && msg_data.getInt64(&metadata_id) && - msg_data.getStr(&server_token)) + msg_data.getStr(&server_token) && + msg_data.getInt64(&active_gen) ) { - PipeSessions::fileMetadataDone(public_fn, server_token); + PipeSessions::fileMetadataDone(public_fn, server_token, active_gen); } else if (id == METADATA_PIPE_SEND_RAW && msg_data.getStr(&public_fn) && msg_data.getStr(&raw_metadata) - && msg_data.getStr(&server_token)) + && msg_data.getStr(&server_token) + && msg_data.getInt64(&active_gen)) { - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); } else if (id == METADATA_PIPE_SEND_RAW_FILEDATA && msg_data.getStr(&public_fn) && msg_data.getVoidPtr(reinterpret_cast(&transmit_file)) && msg_data.getVoidPtr(reinterpret_cast(&transmit_wait_pipe)) - && msg_data.getStr(&server_token)) + && msg_data.getStr(&server_token) + && msg_data.getInt64(&active_gen)) { - PipeSessions::fileMetadataDone(public_fn.substr(1), server_token); + PipeSessions::fileMetadataDone(public_fn.substr(1), server_token, active_gen); } } } diff --git a/fileservplugin/FileMetadataPipe.h b/fileservplugin/FileMetadataPipe.h index d36fbabe7..7024c46f3 100644 --- a/fileservplugin/FileMetadataPipe.h +++ b/fileservplugin/FileMetadataPipe.h @@ -107,6 +107,7 @@ class FileMetadataPipe : public PipeFileBase int64 metadata_file_off; int64 metadata_file_size; int64 metadata_id; + int64 active_gen; MetadataState metadata_state; diff --git a/fileservplugin/FileServ.cpp b/fileservplugin/FileServ.cpp index 535fa22ec..b35b68649 100644 --- a/fileservplugin/FileServ.cpp +++ b/fileservplugin/FileServ.cpp @@ -33,7 +33,8 @@ bool FileServ::pause=false; std::map FileServ::script_mappings; IFileServ::ITokenCallbackFactory* FileServ::token_callback_factory = NULL; std::map FileServ::fn_redirects; -std::map FileServ::active_shares; +std::map, size_t> FileServ::active_shares; +size_t FileServ::active_generation = 0; FileServ::IReadErrorCallback* FileServ::read_error_callback = NULL; std::vector FileServ::read_error_files; std::map, IFileServ::CbtHashFileInfo> FileServ::cbt_hash_files; @@ -263,7 +264,7 @@ IFileServ::ITokenCallback* FileServ::newTokenCallback() return token_callback_factory->getTokenCallback(); } -void FileServ::incrShareActive(std::string sharename) +size_t FileServ::incrShareActive(std::string sharename) { if (sharename.find("/") != std::string::npos) { @@ -271,10 +272,11 @@ void FileServ::incrShareActive(std::string sharename) } IScopedLock lock(mutex); - ++active_shares[sharename]; + ++active_shares[std::make_pair(sharename, active_generation)]; + return active_generation; } -void FileServ::decrShareActive(std::string sharename) +void FileServ::decrShareActive(std::string sharename, size_t gen) { if (sharename.find("/") != std::string::npos) { @@ -283,7 +285,7 @@ void FileServ::decrShareActive(std::string sharename) IScopedLock lock(mutex); - std::map::iterator it = active_shares.find(sharename); + std::map, size_t>::iterator it = active_shares.find(std::make_pair(sharename, gen)); if (it != active_shares.end()) { @@ -304,9 +306,38 @@ bool FileServ::hasActiveTransfers(const std::string& sharename, const std::strin IScopedLock lock(mutex); - std::map::iterator it = active_shares.find(server_token + "|" + sharename); + for (std::map, size_t>::iterator it = active_shares.begin(); + it != active_shares.end();++it) + { + if (it->first.first == server_token + "|" + sharename) + { + return true; + } + } - return it != active_shares.end(); + return false; +} + +bool FileServ::hasActiveTransfersGen(const std::string& sharename, const std::string& server_token, size_t gen) +{ + if (PipeSessions::isShareActiveGen(sharename, server_token, gen)) + { + return true; + } + + IScopedLock lock(mutex); + + for (std::map, size_t>::iterator it = active_shares.begin(); + it != active_shares.end(); ++it) + { + if (it->first.first == server_token + "|" + sharename && + it->first.second<=gen) + { + return true; + } + } + + return false; } bool FileServ::registerFnRedirect(const std::string & source_fn, const std::string & target_fn) @@ -422,3 +453,10 @@ void FileServ::deregisterScriptPipeFile(const std::string & script_fn) script_mappings.erase(it); } } + +} + +size_t FileServ::incrActiveGeneration() +{ + IScopedLock lock(mutex); + return active_generation++; \ No newline at end of file diff --git a/fileservplugin/FileServ.h b/fileservplugin/FileServ.h index 14ac57721..e5935b037 100644 --- a/fileservplugin/FileServ.h +++ b/fileservplugin/FileServ.h @@ -43,12 +43,14 @@ class FileServ : public IFileServ static IFileServ::ITokenCallback* newTokenCallback(); - static void incrShareActive(std::string sharename); + static size_t incrShareActive(std::string sharename); - static void decrShareActive(std::string sharename); + static void decrShareActive(std::string sharename, size_t gen); bool hasActiveTransfers(const std::string& sharename, const std::string& server_token); + bool hasActiveTransfersGen(const std::string& sharename, const std::string& server_token, size_t gen); + bool registerFnRedirect(const std::string& source_fn, const std::string& target_fn); static std::string getRedirectedFn(const std::string& source_fn); @@ -71,6 +73,8 @@ class FileServ : public IFileServ virtual void deregisterScriptPipeFile(const std::string& script_fn); + size_t incrActiveGeneration(); + private: bool *dostop; THREADPOOL_TICKET serverticket; @@ -125,7 +129,9 @@ class FileServ : public IFileServ static ITokenCallbackFactory* token_callback_factory; - static std::map active_shares; + static std::map, size_t> active_shares; + + static size_t active_generation; static IReadErrorCallback* read_error_callback; @@ -137,6 +143,7 @@ class FileServ : public IFileServ class ScopedShareActive { + size_t gen; public: ScopedShareActive() { @@ -148,7 +155,7 @@ class ScopedShareActive { if (!sharename.empty()) { - FileServ::incrShareActive(sharename); + gen = FileServ::incrShareActive(sharename); } } @@ -156,7 +163,7 @@ class ScopedShareActive { if (!sharename.empty()) { - FileServ::decrShareActive(sharename); + FileServ::decrShareActive(sharename, gen); } } @@ -164,18 +171,19 @@ class ScopedShareActive { if (!sharename.empty()) { - FileServ::decrShareActive(sharename); + FileServ::decrShareActive(sharename, gen); } sharename = new_sharename; if (!sharename.empty()) { - FileServ::incrShareActive(sharename); + gen = FileServ::incrShareActive(sharename); } } - void release() + size_t release() { sharename.clear(); + return gen; } private: diff --git a/fileservplugin/IFileServ.h b/fileservplugin/IFileServ.h index 9491e2599..feefaf4d8 100644 --- a/fileservplugin/IFileServ.h +++ b/fileservplugin/IFileServ.h @@ -57,11 +57,13 @@ class IFileServ : public IObject virtual void removeMetadataCallback(const std::string &name, const std::string& identity) = 0; virtual void registerTokenCallbackFactory(ITokenCallbackFactory* callback_factory) = 0; virtual bool hasActiveTransfers(const std::string& sharename, const std::string& server_token) = 0; + virtual bool hasActiveTransfersGen(const std::string& sharename, const std::string& server_token, size_t gen) = 0; virtual bool registerFnRedirect(const std::string& source_fn, const std::string& target_fn) = 0; virtual void registerReadErrorCallback(IReadErrorCallback* cb) = 0; virtual void registerScriptPipeFile(const std::string& script_fn, IPipeFileExt* pipe_file) = 0; virtual void deregisterScriptPipeFile(const std::string& script_fn) = 0; virtual void clearReadErrors() = 0; + virtual size_t incrActiveGeneration() = 0; struct CbtHashFileInfo { diff --git a/fileservplugin/PipeSessions.cpp b/fileservplugin/PipeSessions.cpp index 2d02ac254..34bae72d4 100644 --- a/fileservplugin/PipeSessions.cpp +++ b/fileservplugin/PipeSessions.cpp @@ -32,10 +32,12 @@ volatile bool PipeSessions::do_stop = false; IMutex* PipeSessions::mutex = NULL; +IMutex* PipeSessions::active_shares_mutex = NULL; std::map PipeSessions::pipe_files; std::map PipeSessions::exit_information; std::map, IFileServ::IMetadataCallback*> PipeSessions::metadata_callbacks; -std::map PipeSessions::active_shares; +std::map, size_t> PipeSessions::active_shares; +size_t PipeSessions::active_shares_gen = 0; const int64 pipe_file_timeout = 1*60*60*1000; const int64 pipe_file_read_timeout = 30 * 60 * 1000; @@ -173,6 +175,7 @@ void PipeSessions::injectPipeSession(const std::string & session_key, int backup void PipeSessions::init() { mutex = Server->createMutex(); + active_shares_mutex = Server->createMutex(); Server->getThreadPool()->execute(new PipeSessions, "PipeSession: timeout"); } @@ -180,6 +183,7 @@ void PipeSessions::init() void PipeSessions::destroy() { delete mutex; + delete active_shares_mutex; do_stop=true; } @@ -406,21 +410,26 @@ IFileServ::IMetadataCallback* PipeSessions::transmitFileMetadata( const std::str IScopedLock lock(mutex); - std::map, IFileServ::IMetadataCallback*>::iterator iter_cb = - metadata_callbacks.find(std::make_pair(sharename, identity)); - IFileServ::IMetadataCallback* ret = NULL; - if(iter_cb!=metadata_callbacks.end()) - { - data.addVoidPtr(iter_cb->second); - ret = iter_cb->second; - } std::map::iterator it = pipe_files.find("urbackup/FILE_METADATA|"+server_token); if(it!=pipe_files.end() && it->second.input_pipe!=NULL) { - ++active_shares[sharename + "|" + server_token]; + { + IScopedLock alock(active_shares_mutex); + ++active_shares[std::make_pair(sharename + "|" + server_token, active_shares_gen)]; + data.addUInt64(active_shares_gen); + } + + std::map, IFileServ::IMetadataCallback*>::iterator iter_cb = + metadata_callbacks.find(std::make_pair(sharename, identity)); + + if(iter_cb!=metadata_callbacks.end()) + { + data.addVoidPtr(iter_cb->second); + ret = iter_cb->second; + } it->second.input_pipe->Write(data.getDataPtr(), data.getDataSize()); } @@ -457,7 +466,11 @@ void PipeSessions::transmitFileMetadata(const std::string & public_fn, const std if (it != pipe_files.end() && it->second.input_pipe != NULL) { - ++active_shares[sharename + "|" + server_token]; + { + IScopedLock alock(active_shares_mutex); + ++active_shares[std::make_pair(sharename + "|" + server_token, active_shares_gen)]; + data.addUInt64(active_shares_gen); + } it->second.input_pipe->Write(data.getDataPtr(), data.getDataSize()); } @@ -500,12 +513,15 @@ void PipeSessions::transmitFileMetadataAndFiledataWait(const std::string & publi if (it != pipe_files.end() && it->second.input_pipe != NULL) { - ++active_shares[sharename + "|" + server_token]; + { + IScopedLock alock(active_shares_mutex); + active_shares[std::make_pair(sharename + "|" + server_token, active_shares_gen)]+=2; + metadatamsg.addUInt(active_shares_gen); + datamsg.addUInt(active_shares_gen); + } it->second.input_pipe->Write(datamsg.getDataPtr(), datamsg.getDataSize()); - ++active_shares[sharename + "|" + server_token]; - it->second.input_pipe->Write(metadatamsg.getDataPtr(), metadatamsg.getDataSize()); lock.relock(NULL); @@ -520,7 +536,7 @@ void PipeSessions::transmitFileMetadataAndFiledataWait(const std::string & publi -void PipeSessions::fileMetadataDone(const std::string & public_fn, const std::string& server_token) +void PipeSessions::fileMetadataDone(const std::string & public_fn, const std::string& server_token, size_t active_gen) { std::string sharename = getuntil("/", public_fn); if (sharename.empty()) @@ -528,9 +544,9 @@ void PipeSessions::fileMetadataDone(const std::string & public_fn, const std::st sharename = public_fn; } - IScopedLock lock(mutex); + IScopedLock lock(active_shares_mutex); - std::map::iterator it = active_shares.find(sharename + "|" + server_token); + std::map, size_t>::iterator it = active_shares.find(std::make_pair(sharename + "|" + server_token, active_gen)); if (it != active_shares.end()) { @@ -544,11 +560,37 @@ void PipeSessions::fileMetadataDone(const std::string & public_fn, const std::st bool PipeSessions::isShareActive(const std::string & sharename, const std::string& server_token) { - IScopedLock lock(mutex); + IScopedLock lock(active_shares_mutex); + + for (std::map, size_t>::iterator it = active_shares.begin(); + it != active_shares.end(); ++it) + { + if (it->first.first == sharename + "|" + server_token) + return true; + } - std::map::iterator it = active_shares.find(sharename + "|" + server_token); + return false; +} + +bool PipeSessions::isShareActiveGen(const std::string& sharename, const std::string& server_token, size_t gen) +{ + IScopedLock lock(active_shares_mutex); - return it != active_shares.end(); + for (std::map, size_t>::iterator it = active_shares.begin(); + it != active_shares.end(); ++it) + { + if (it->first.first == sharename + "|" + server_token && + it->first.second<=gen) + return true; + } + + return false; +} + +void PipeSessions::setActiveSharesGen(size_t gen) +{ + IScopedLock lock(active_shares_mutex); + active_shares_gen = gen; } void PipeSessions::metadataStreamEnd( const std::string& server_token ) diff --git a/fileservplugin/PipeSessions.h b/fileservplugin/PipeSessions.h index 1157309c4..3933a6ca0 100644 --- a/fileservplugin/PipeSessions.h +++ b/fileservplugin/PipeSessions.h @@ -73,10 +73,14 @@ class PipeSessions : public IThread static void transmitFileMetadataAndFiledataWait(const std::string& public_fn, const std::string& metadata, const std::string& server_token, const std::string& identity, IFile* file); - static void fileMetadataDone(const std::string& public_fn, const std::string& server_token); + static void fileMetadataDone(const std::string& public_fn, const std::string& server_token, size_t active_gen); static bool isShareActive(const std::string& sharename, const std::string& server_token); + static bool isShareActiveGen(const std::string& sharename, const std::string& server_token, size_t gen); + + static void setActiveSharesGen(size_t gen); + static void metadataStreamEnd(const std::string& server_token); static void phashEnd(const std::string& server_token, const std::string& phash_fn); @@ -91,9 +95,11 @@ class PipeSessions : public IThread static std::string getKey(const std::string& cmd, int& backupnum, int64& fn_random); static IMutex* mutex; + static IMutex* active_shares_mutex; static volatile bool do_stop; static std::map pipe_files; static std::map exit_information; static std::map, IFileServ::IMetadataCallback*> metadata_callbacks; - static std::map active_shares; + static std::map, size_t> active_shares; + static size_t active_shares_gen; }; \ No newline at end of file From c15c2400a6e7ab6f598baa049c7db41ae05aee8c Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 30 Mar 2021 02:31:07 +0200 Subject: [PATCH 049/469] Improve: Cleanup snapshot later if it cannot be deleted because it is in use (cherry picked from commit d84523ba034a103a15994c10e9bc9eff9891ce39) --- urbackupclient/client.cpp | 145 +++++++++++++++++++++++++++----------- urbackupclient/client.h | 2 + 2 files changed, 105 insertions(+), 42 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index d875145bb..3c8ba5a6a 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -3485,6 +3485,7 @@ bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allo && filesrv->hasActiveTransfers(dir->dir, starttoken)) { sc_refs[i]->cleanup = true; + sc_refs[i]->cleanup_gen = filesrv->incrActiveGeneration(); sc_refs_cleanup = true; VSSLog("Old shadow copy of " + sc_refs[i]->target + " still in use. Not deleting or using it.", LL_WARNING); continue; @@ -3566,6 +3567,8 @@ bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allo dir->ref->dontincrement=false; } + dir->ref->sharenames.push_back(dir->dir); + VSSLog("orig_target="+dir->orig_target+" volpath="+dir->ref->volpath, LL_DEBUG); dir->target=dir->orig_target; @@ -3690,6 +3693,7 @@ bool IndexThread::start_shadowcopy(SCDirs *dir, bool *onlyref, bool allow_restar dir->ref->starttime=Server->getTimeSeconds(); dir->ref->target=wpath; dir->ref->starttokens.push_back(starttoken); + dir->ref->sharenames.push_back(dir->dir); dir->ref->clientsubname = index_clientsubname; dir->ref->for_imagebackup = for_imagebackup; sc_refs.push_back(dir->ref); @@ -6498,67 +6502,124 @@ void IndexThread::run_sc_refs_cleanup() bool in_use = false; - SCRef* curr = sc_refs[i]; - for (std::map >::iterator it_scdirs = scdirs.begin(); - it_scdirs != scdirs.end(); ++it_scdirs) + bool found_ref = false; + + starttoken.clear(); + + SCRef* curr = sc_refs[i]; + + for (size_t k = 0; k < curr->starttokens.size(); ++k) { - std::map& scdirs_server = it_scdirs->second; - - VSS_ID ssetid = curr->ssetid; + starttoken = curr->starttokens[k]; - bool in_use = false; - std::vector paths; - for (std::map::iterator it = scdirs_server.begin(); - it != scdirs_server.end(); ++it) + for (std::map >::iterator it_scdirs = scdirs.begin(); + it_scdirs != scdirs.end(); ++it_scdirs) { - if (filesrv != NULL - && filesrv->hasActiveTransfers(it->second->dir, it_scdirs->first.start_token)) - { - VSSLog(it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " still in use. Not releasing.", LL_DEBUG); - in_use = true; - break; - } - - paths.push_back(it->first); - } + std::map& scdirs_server = it_scdirs->second; - if (in_use) - break; + VSS_ID ssetid = curr->ssetid; - for (size_t j = 0; j < paths.size(); ++j) - { - std::map::iterator it = scdirs_server.find(paths[j]); - if (it != scdirs_server.end() - && it->second->ref == curr) + bool in_use = false; + std::vector paths; + for (std::map::iterator it = scdirs_server.begin(); + it != scdirs_server.end(); ++it) { - VSSLog("Releasing " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG); - release_shadowcopy(it->second, false, -1); + if (filesrv != NULL + && filesrv->hasActiveTransfersGen(it->second->dir, it_scdirs->first.start_token, curr->cleanup_gen)) + { + VSSLog(it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " gen="+convert(curr->cleanup_gen)+" still in use. Not releasing.", LL_DEBUG); + in_use = true; + break; + } + + paths.push_back(it->first); } - } - bool retry = true; - while (retry) - { - retry = false; - for (std::map::iterator it = scdirs_server.begin(); - it != scdirs_server.end(); ++it) + if (in_use) + break; + + for (size_t j = 0; j < paths.size(); ++j) { - if (it->second->ref != NULL - && it->second->ref != curr - && it->second->ref->ssetid == ssetid) + std::map::iterator it = scdirs_server.find(paths[j]); + if (it != scdirs_server.end() + && it->second->ref == curr) { - VSSLog("Releasing group shadow copy " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG); + VSSLog("Releasing " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG); + found_ref = true; release_shadowcopy(it->second, false, -1); - retry = true; - break; } } + + bool retry = true; + while (retry) + { + retry = false; + for (std::map::iterator it = scdirs_server.begin(); + it != scdirs_server.end(); ++it) + { + if (it->second->ref != NULL + && it->second->ref != curr + && it->second->ref->ssetid == ssetid) + { + VSSLog("Releasing group shadow copy " + it->first + " orig_target=" + it->second->orig_target + " target=" + it->second->target + " (run_sc_refs_cleanup)", LL_DEBUG); + release_shadowcopy(it->second, false, -1); + found_ref = true; + retry = true; + break; + } + } + } } } if (in_use) continue; + if (!found_ref) + { + VSSLog("Reference not found. Iterating over all start tokens and share names for deletion", LL_INFO); + for (size_t k = 0; k < curr->starttokens.size() && !retry_all; ++k) + { + starttoken = curr->starttokens[k]; + SCDirs scd; + scd.running = true; + + for (size_t j = 0; j < curr->sharenames.size() && !retry_all; ++j) + { + scd.dir = curr->sharenames[j]; + scd.starttime = Server->getTimeSeconds(); + if (sc_refs[i]->for_imagebackup) + { + scd.target = scd.dir; + scd.fileserv = false; + } + else + { + scd.target = getShareDir(scd.dir); + scd.fileserv = true; + } + + if (filesrv != NULL + && filesrv->hasActiveTransfersGen(scd.dir, starttoken, curr->cleanup_gen)) + { + VSSLog(scd.dir + " orig_target=" + scd.orig_target + " target=" + scd.target + " starttoken="+ starttoken+ " gen="+convert(curr->cleanup_gen)+" still in use. Not releasing. (2)", LL_DEBUG); + in_use = true; + break; + } + + size_t orig_size = sc_refs.size(); + + release_shadowcopy(&scd, false, -1, &scd); + + if (sc_refs.size() != orig_size) + { + retry_all = true; + break; + } + } + } + } + retry_all = true; break; } diff --git a/urbackupclient/client.h b/urbackupclient/client.h index 3c9e81c71..460e468a2 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -110,7 +110,9 @@ struct SCRef bool ok; bool dontincrement; bool cleanup; + size_t cleanup_gen; std::vector starttokens; + std::vector sharenames; std::string clientsubname; bool cbt; bool for_imagebackup; From 54af7888c9e6533abb242113e6d033c9080f1357 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 30 Mar 2021 11:19:40 +0200 Subject: [PATCH 050/469] Skip in use snapshots (cherry picked from commit 843031d8e9189010cec41e7832be6e48b2d3e5f5) --- urbackupclient/client.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 3c8ba5a6a..7a89b7380 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -6572,9 +6572,6 @@ void IndexThread::run_sc_refs_cleanup() } } - if (in_use) - continue; - if (!found_ref) { VSSLog("Reference not found. Iterating over all start tokens and share names for deletion", LL_INFO); @@ -6620,6 +6617,9 @@ void IndexThread::run_sc_refs_cleanup() } } + if (in_use) + continue; + retry_all = true; break; } From 50ae162112efb6299c38e692f8ce479d0cb8b62f Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 31 Mar 2021 19:43:22 +0200 Subject: [PATCH 051/469] Properly set ref (cherry picked from commit 41e32b6e1bc0ae2dbbbb428c89edf22267e0ddfa) --- urbackupclient/client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 7a89b7380..0d1926628 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -6603,6 +6603,8 @@ void IndexThread::run_sc_refs_cleanup() in_use = true; break; } + + scd.ref = sc_refs[i]; size_t orig_size = sc_refs.size(); From 3bfdf2638c73fa9cda8df626e60f1d5b229941a2 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 3 Apr 2021 15:20:18 +0200 Subject: [PATCH 052/469] Reset target path when snapshot is still in use (cherry picked from commit 02c82fde35f95bbdb80c9787e8874da61abddb65) --- urbackupclient/client.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 0d1926628..996dd8dd4 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -3488,6 +3488,7 @@ bool IndexThread::find_existing_shadowcopy(SCDirs *dir, bool *onlyref, bool allo sc_refs[i]->cleanup_gen = filesrv->incrActiveGeneration(); sc_refs_cleanup = true; VSSLog("Old shadow copy of " + sc_refs[i]->target + " still in use. Not deleting or using it.", LL_WARNING); + dir->target = dir->orig_target; continue; } #endif From 3ce664152bd4b8fb0404cdd5bb1699befdf27c35 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 5 Apr 2021 18:14:44 +0200 Subject: [PATCH 053/469] Increment version --- configure.ac_client | 2 +- configure.ac_server | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 55b776dda..97b748259 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.13.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.14.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/configure.ac_server b/configure.ac_server index 3fa7e6849..759e4d3d9 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.19.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.20.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) From 4252154d14dcd3fe343088878e522ea03ed7f58a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 5 Apr 2021 18:24:19 +0200 Subject: [PATCH 054/469] Fix merge issue --- fileservplugin/FileServ.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/fileservplugin/FileServ.cpp b/fileservplugin/FileServ.cpp index b35b68649..037cf73f4 100644 --- a/fileservplugin/FileServ.cpp +++ b/fileservplugin/FileServ.cpp @@ -454,9 +454,8 @@ void FileServ::deregisterScriptPipeFile(const std::string & script_fn) } } -} - size_t FileServ::incrActiveGeneration() { IScopedLock lock(mutex); - return active_generation++; \ No newline at end of file + return active_generation++; +} From 06e3743a74c799e3bd8806a957a9f3406b426f69 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 6 Apr 2021 18:40:56 +0200 Subject: [PATCH 055/469] Repair UTF-8 after making file name shorter --- urbackupserver/FileBackup.cpp | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/urbackupserver/FileBackup.cpp b/urbackupserver/FileBackup.cpp index 64f4f0d2b..141c00cbe 100644 --- a/urbackupserver/FileBackup.cpp +++ b/urbackupserver/FileBackup.cpp @@ -1018,6 +1018,39 @@ std::string FileBackup::fixFilenameForOS(std::string fn, std::set& } fn.resize(name_max); append_hash = true; + + size_t rm_bytes = 0; + //Repair UTF-8 + for (size_t i = fn.size() - 1; i-- > 0;) + { + const unsigned char first_mask = 0x80; + const unsigned char utf8_start = 0xC0; + + const unsigned char ch = static_cast(fn[i]); + + if (ch & first_mask) + { + if (ch & utf8_start == utf8_start) + { + ++rm_bytes; + break; + } + else + { + ++rm_bytes; + } + } + else + { + //ASCII char + break; + } + } + + if (rm_bytes > 0) + { + fn.resize(name_max - rm_bytes); + } } #endif From 697ad8dfd35f842bcaea7a0457fd99382125358a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 28 Apr 2021 18:45:01 +0200 Subject: [PATCH 056/469] Add vhdx support (cherry picked from commit a3140908182a9fce69caa616adf0741ab4f2899d) # Conflicts: # urbackupserver/www/js/templates.js --- fsimageplugin/FSImageFactory.cpp | 9 +- fsimageplugin/IFSImageFactory.h | 4 +- fsimageplugin/dllmain.cpp | 149 +- fsimageplugin/fsimageplugin.vcxproj | 2 + fsimageplugin/vhdxfile.cpp | 2887 +++++++++++++++++ fsimageplugin/vhdxfile.h | 182 ++ urbackupserver/ClientMain.cpp | 12 +- urbackupserver/ImageBackup.cpp | 30 +- urbackupserver/server_settings.h | 2 + urbackupserver/www/js/templates.js | 152 +- urbackupserver/www/js/urbackup.js | 4 +- .../www/templates/settings_inv_row.htm | 4 +- 12 files changed, 3349 insertions(+), 88 deletions(-) create mode 100644 fsimageplugin/vhdxfile.cpp create mode 100644 fsimageplugin/vhdxfile.h diff --git a/fsimageplugin/FSImageFactory.cpp b/fsimageplugin/FSImageFactory.cpp index 3f4e7bba7..0e09fe13b 100644 --- a/fsimageplugin/FSImageFactory.cpp +++ b/fsimageplugin/FSImageFactory.cpp @@ -1,6 +1,6 @@ /************************************************************************* * UrBackup - Client/Server backup system -* Copyright (C) 2011-2016 Martin Raiber +* Copyright (C) 2011-2021 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 @@ -28,6 +28,7 @@ #endif #include "fs/unknown.h" #include "vhdfile.h" +#include "vhdxfile.h" #include "../stringtools.h" #ifdef _WIN32 #include @@ -545,6 +546,9 @@ IVHDFile *FSImageFactory::createVHDFile(const std::string &fn, bool pRead_only, case ImageFormat_VHD: case ImageFormat_CompressedVHD: return new VHDFile(fn, pRead_only, pDstsize, pBlocksize, fast_mode, format!=ImageFormat_VHD); + case ImageFormat_VHDX: + case ImageFormat_CompressedVHDX: + return new VHDXFile(fn, pRead_only, pDstsize, pBlocksize, fast_mode, format != ImageFormat_VHDX); case ImageFormat_RawCowFile: #if !defined(__APPLE__) return new CowFile(fn, pRead_only, pDstsize); @@ -563,6 +567,9 @@ IVHDFile *FSImageFactory::createVHDFile(const std::string &fn, const std::string case ImageFormat_VHD: case ImageFormat_CompressedVHD: return new VHDFile(fn, parent_fn, pRead_only, fast_mode, format!=ImageFormat_VHD, pDstsize); + case ImageFormat_VHDX: + case ImageFormat_CompressedVHDX: + return new VHDXFile(fn, parent_fn, pRead_only, fast_mode, format != ImageFormat_VHDX, pDstsize); case ImageFormat_RawCowFile: #if !defined(__APPLE__) return new CowFile(fn, parent_fn, pRead_only, pDstsize); diff --git a/fsimageplugin/IFSImageFactory.h b/fsimageplugin/IFSImageFactory.h index c5ff88b6a..bc6533f0c 100644 --- a/fsimageplugin/IFSImageFactory.h +++ b/fsimageplugin/IFSImageFactory.h @@ -35,7 +35,9 @@ class IFSImageFactory : public IPlugin { ImageFormat_VHD=0, ImageFormat_CompressedVHD=1, - ImageFormat_RawCowFile=2 + ImageFormat_RawCowFile=2, + ImageFormat_VHDX = 3, + ImageFormat_CompressedVHDX = 4 }; virtual IVHDFile *createVHDFile(const std::string &fn, bool pRead_only, uint64 pDstsize, diff --git a/fsimageplugin/dllmain.cpp b/fsimageplugin/dllmain.cpp index 065ed4866..580fdd3ec 100644 --- a/fsimageplugin/dllmain.cpp +++ b/fsimageplugin/dllmain.cpp @@ -1,6 +1,6 @@ /************************************************************************* * UrBackup - Client/Server backup system -* Copyright (C) 2011-2016 Martin Raiber +* Copyright (C) 2011-2021 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 @@ -54,6 +54,7 @@ extern IServer* Server; #include #include "vhdfile.h" +#include "vhdxfile.h" #ifndef _WIN32 #include "cowfile.h" #endif @@ -189,17 +190,28 @@ namespace } - IVHDFile* open_device_file(std::string device_verify) + IVHDFile* open_device_file(std::string device_verify, bool read_only=true, int64 dst_size = 0, + std::string parent_fn=std::string()) { std::string ext = strlower(findextension(device_verify)); if(ext=="vhd" || ext=="vhdz") { - return new VHDFile(device_verify, true,0); + if(parent_fn.empty()) + return new VHDFile(device_verify, read_only, dst_size); + else + return new VHDFile(device_verify, parent_fn, read_only); + } + else if (ext == "vhdx" || ext == "vhdxz") + { + if (parent_fn.empty()) + return new VHDXFile(device_verify, read_only, dst_size); + else + return new VHDXFile(device_verify, parent_fn, read_only); } #if !defined(_WIN32) && !defined(__APPLE__) else if(ext=="raw") { - return new CowFile(device_verify, true,0); + return new CowFile(device_verify, read_only, dst_size); } #endif else @@ -865,6 +877,135 @@ DLLEXPORT void LoadActions(IServer* pServer) } } } + + std::string vhdmake_in = Server->getServerParameter("vhdmake_in"); + if (!vhdmake_in.empty()) + { + Server->Log("VHDMake."); + + std::unique_ptr inFile(Server->openFile(vhdmake_in, MODE_READ)); + + if (inFile.get() == nullptr) + { + Server->Log("Error opening " + vhdmake_in + ". ", LL_ERROR); + exit(1); + } + + std::string vhd_out = Server->getServerParameter("vhd_out"); + IVHDFile* vhdfile = open_device_file(vhd_out, false, inFile->Size()); + if (vhdfile == nullptr) + { + Server->Log("Error opening " + vhd_out + ". ", LL_ERROR); + exit(1); + } + + std::vector buf(512 * 1024); + + for (int64 pos = 0, size = inFile->Size(); pos < size; pos += buf.size()) + { + _u32 towrite = static_cast<_u32>((std::min)(static_cast(buf.size()), size - pos)); + if (inFile->Read(pos, buf.data(), towrite) != towrite) + { + Server->Log("Error reading from in file", LL_ERROR); + exit(2); + } + + if (vhdfile->Write(buf.data(), towrite) != towrite) + { + Server->Log("Error writing to vhd file", LL_ERROR); + exit(2); + } + } + + delete vhdfile; + Server->Log("VHDMake complete", LL_INFO); + exit(0); + } + + std::string vhdmake_diff_in = Server->getServerParameter("vhdmake_diff_in"); + if (!vhdmake_diff_in.empty()) + { + Server->Log("VHDMake Diff."); + + std::unique_ptr inFile(Server->openFile(vhdmake_diff_in, MODE_READ)); + + if (inFile.get() == nullptr) + { + Server->Log("Error opening " + vhdmake_diff_in + ". ", LL_ERROR); + exit(1); + } + + std::string vhd_out_parent = Server->getServerParameter("vhd_out_parent"); + + if (vhd_out_parent.empty() || !FileExists(vhd_out_parent)) + { + Server->Log("Error finding vhd_out_parent \"" + vhd_out_parent + "\"", LL_ERROR); + exit(2); + } + + std::string vhdmake_in_parent = Server->getServerParameter("vhdmake_in_parent"); + + if (vhdmake_in_parent.empty() || !FileExists(vhdmake_in_parent)) + { + Server->Log("Error finding vhdmake_in_parent \"" + vhdmake_in_parent + "\"", LL_ERROR); + exit(2); + } + + std::unique_ptr inParentFile(Server->openFile(vhdmake_in_parent, MODE_READ)); + + if (inParentFile.get() == nullptr) + { + Server->Log("Error opening " + vhdmake_in_parent + ". ", LL_ERROR); + exit(1); + } + + std::string vhd_out = Server->getServerParameter("vhd_out"); + Server->deleteFile(vhd_out); //TODO: rm + IVHDFile* vhdfile = open_device_file(vhd_out, false, inFile->Size(), vhd_out_parent); + if (vhdfile == nullptr) + { + Server->Log("Error opening " + vhd_out + ". ", LL_ERROR); + exit(1); + } + + std::vector buf(512); + std::vector buf_prev(512); + + int64 written = 0; + for (int64 pos = 0, size = inFile->Size(); pos < size; pos += buf.size()) + { + _u32 towrite = static_cast<_u32>((std::min)(static_cast(buf.size()), size - pos)); + if (inFile->Read(pos, buf.data(), towrite) != towrite) + { + Server->Log("Error reading from in file", LL_ERROR); + exit(2); + } + + if (inParentFile->Read(pos, buf_prev.data(), towrite) != towrite) + { + Server->Log("Error reading from in parent file", LL_ERROR); + exit(2); + } + + if (memcmp(buf.data(), buf_prev.data(), towrite) == 0) + { + continue; + } + + vhdfile->Seek(pos); + if (vhdfile->Write(buf.data(), towrite) != towrite) + { + Server->Log("Error writing to vhd file", LL_ERROR); + exit(2); + } + + written += towrite; + } + + delete vhdfile; + Server->Log("VHDMakeDiff complete. "+PrettyPrintBytes(written)+" written.", LL_INFO); + exit(0); + } std::string hashfilecomp_1=Server->getServerParameter("hashfilecomp_1"); if(!hashfilecomp_1.empty()) diff --git a/fsimageplugin/fsimageplugin.vcxproj b/fsimageplugin/fsimageplugin.vcxproj index c87420bcc..bd4097767 100644 --- a/fsimageplugin/fsimageplugin.vcxproj +++ b/fsimageplugin/fsimageplugin.vcxproj @@ -204,6 +204,7 @@ + @@ -226,6 +227,7 @@ + diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp new file mode 100644 index 000000000..ded4f620c --- /dev/null +++ b/fsimageplugin/vhdxfile.cpp @@ -0,0 +1,2887 @@ +/************************************************************************* +* UrBackup - Client/Server backup system +* Copyright (C) 2021 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 . +**************************************************************************/ + +#include "vhdxfile.h" +#include "../stringtools.h" +#include +#include "CompressedFile.h" +#include "../urbackupcommon/os_functions.h" +#include "FileWrapper.h" +#include "ClientBitmap.h" +#include "IFilesystem.h" +#include "fs/ntfs.h" + +#define PAYLOAD_BLOCK_NOT_PRESENT 0 +#define PAYLOAD_BLOCK_UNDEFINED 1 +#define PAYLOAD_BLOCK_ZERO 2 +#define PAYLOAD_BLOCK_UNMAPPED 3 +#define PAYLOAD_BLOCK_FULLY_PRESENT 6 +#define PAYLOAD_BLOCK_PARTIALLY_PRESENT 7 + +namespace +{ + const int64 vhdx_header_length = 3 * 1024 * 1024; + const int64 allocate_size_add_size = 100 * 1024 * 1024; + const _u32 log_sector_size = 4096; + + template + auto roundUp(T numToRound, T multiple) + { + return ((numToRound + multiple - 1) / multiple) * multiple; + } + + std::vector getFileIdentifier() + { + std::vector ret; + ret.resize(500); + memcpy(ret.data(), "vhdxfile", 8); + std::string creator = Server->ConvertToUTF16("UrBackup vhdx file"); + memcpy(ret.data() + 8, creator.data(), creator.size()); + return ret; + } + + void secureRandomGuid(VhdxGUID& g) + { + Server->secureRandomFill(g, 16); + g[6] = 0x40 | (g[6] & 0xf); + g[8] = 0x80 | (g[8] & 0x3f); + } + + void randomGuid(VhdxGUID& g) + { + Server->randomFill(g, 16); + g[6] = 0x40 | (g[6] & 0xf); + g[8] = 0x80 | (g[8] & 0x3f); + } + + void zeroGUID(VhdxGUID& g) + { + memset(g, 0, 16); + } + + bool equalsGUID(const VhdxGUID& a, const VhdxGUID& b) + { + return memcmp(a, b, sizeof(VhdxGUID)) == 0; + } + + bool isZeroGUID(VhdxGUID& g) + { + VhdxGUID z = {}; + return equalsGUID(g, z); + } + + void copyGUID(const VhdxGUID& src, VhdxGUID& dst) + { + memcpy(dst, src, 16); + } + + void reorderGUID(VhdxGUID& g) + { + *reinterpret_cast(&g[0]) = big_endian(*reinterpret_cast(&g[0])); + *reinterpret_cast(&g[4]) = big_endian(*reinterpret_cast(&g[4])); + *reinterpret_cast(&g[6]) = big_endian(*reinterpret_cast(&g[6])); + } + + bool parseStrGuid(const std::string& str, VhdxGUID& g) + { + if (str.size() < 5) + return false; + + if (str[0] != '{' || str[str.size() - 1] != '}') + return false; + + std::string hb; + for (size_t i = 1; i < str.size() - 1; ++i) + { + if(IsHex(str.substr(i, 1) ) ) + hb+=str[i]; + } + + if (hb.size() != 32) + return false; + + for (size_t i = 0; i < hb.size(); i += 2) + { + std::string cb = hb.substr(i, 2); + g[i/2] = static_cast(hexToULong(cb)); + } + + reorderGUID(g); + + return true; + } + + std::string strGUID(const VhdxGUID& g) + { + VhdxGUID tmp; + copyGUID(g, tmp); + reorderGUID(tmp); + + std::string ret = "{"; + + for (size_t i = 0; i < 16; ++i) + { + ret += byteToHex(tmp[i]); + + if (i == 3 || i==5 || i==7 || i==9) + ret += "-"; + } + + return ret + "}"; + } + + unsigned int crc32c(unsigned char* data, size_t data_size) + { + unsigned int crc = 0xFFFFFFFF; + for (size_t i = 0; i < data_size; ++i) + { + unsigned int b = data[i]; + crc = crc ^ b; + for (int j = 7; j >= 0; j--) + { + unsigned int mask = -1 * (crc & 1); + crc = (crc >> 1) ^ (0x82F63B78 & mask); + } + } + return ~crc; + } + + std::vector getVhdxHeader(uint64 SequenceNumber) + { + std::vector ret; + ret.resize(sizeof(VhdxHeader)); + VhdxHeader* vhdxHeader = reinterpret_cast(ret.data()); + memcpy(ret.data(), "head", 4); + vhdxHeader->SequenceNumber = SequenceNumber; + secureRandomGuid(vhdxHeader->FileWriteGuid); + secureRandomGuid(vhdxHeader->DataWriteGuid); + vhdxHeader->Version = 1; + vhdxHeader->LogOffset = 1 * 1024 * 1024; + vhdxHeader->LogLength = 1 * 1024 * 1024; + vhdxHeader->Checksum = crc32c(reinterpret_cast(&ret[0]), ret.size()); + return ret; + } + + bool checkHeader(IFile* backing_file, VhdxHeader& header) + { + std::string ident(reinterpret_cast(&header), 4); + if (ident != "head") + { + Server->Log("VHDX header tag wrong", LL_WARNING); + return false; + } + + _u32 ccrc = header.Checksum; + + header.Checksum = 0; + + if (crc32c(reinterpret_cast(&header), sizeof(header)) != ccrc) + { + header.Checksum = ccrc; + Server->Log("VHDX header checksum wrong", LL_WARNING); + return false; + } + + header.Checksum = ccrc; + return true; + } + +#pragma pack(1) + struct VhdxRegionTableHeader + { + _u32 Signature; + _u32 Checksum; + _u32 EntryCount; + _u32 Reserved; + }; +#pragma pack() + + int64 getDataBlocks(int64 rawf_size, _u32 block_size) + { + int64 data_blocks = rawf_size / block_size; + if (rawf_size % block_size != 0) ++data_blocks; + return data_blocks; + } + + _u32 getChunkRatio(_u32 block_size, _u32 sector_size) + { + return static_cast<_u32>((8388608LL * sector_size) / block_size); + } + + _u32 getBatEntries(int64 size, _u32 block_size, _u32 sector_size) + { + int64 data_blocks = getDataBlocks(size, block_size); + return static_cast<_u32>(data_blocks + (data_blocks - 1) / getChunkRatio(block_size, sector_size)); + } + + _u32 getBatEntry(int64 pos, _u32 block_size, _u32 sector_size) + { + int64 data_blocks = pos / block_size; + return static_cast<_u32>(data_blocks + (data_blocks - 1) / getChunkRatio(block_size, sector_size)); + } + + _u32 getSectorBitmapEntry(int64 pos, _u32 block_size, _u32 sector_size) + { + int64 data_blocks = pos / block_size; + _u32 chunk_ratio = getChunkRatio(block_size, sector_size); + return static_cast<_u32>(data_blocks + (data_blocks - 1) / chunk_ratio + + (chunk_ratio - data_blocks%chunk_ratio)); + } + + _u32 getSectorBitmapOffset(int64 pos, _u32 block_size, _u32 sector_size) + { + int64 sector = pos / sector_size; + return static_cast<_u32>(sector % 8388608LL); + } + + bool isSectorSetInt(const char* sector_buf, + int64 pos, _u32 block_size, _u32 sector_size) + { + _u32 offs = getSectorBitmapOffset(pos, block_size, sector_size); + const char* byte = sector_buf + offs / 8; + _u32 bitmap_bit = offs % 8; + + bool has_bit = (( (*byte) & (1 << bitmap_bit)) > 0); + + return has_bit; + } + + void setSectorInt(char* sector_buf, + int64 start, int64 end, _u32 block_size, _u32 sector_size) + { + while (start < end) + { + _u32 offs = getSectorBitmapOffset(start, block_size, sector_size); + char* byte = sector_buf + offs / 8; + _u32 bitmap_bit = offs % 8; + + *byte = *byte | (1 << bitmap_bit); + start += sector_size; + } + } + + _u32 getBatLength(int64 rawf_size, _u32 block_size, _u32 sector_size) + { + int64 bat_entries = getBatEntries(rawf_size, block_size, sector_size); + + int64 mb_blocks = (bat_entries * sizeof(uint64)) / block_size; + if (bat_entries % block_size != 0) ++mb_blocks; + + return static_cast<_u32>(mb_blocks * 1024 * 1024); + } + + _u32 getSectorBitmapBlocksLength(int64 rawf_size, _u32 block_size, _u32 sector_size) + { + int64 data_blocks = getDataBlocks(rawf_size, block_size); + _u32 chunk_ratio = getChunkRatio(block_size, sector_size); + int64 sector_bitmaps = data_blocks / chunk_ratio; + if (data_blocks % chunk_ratio != 0)++sector_bitmaps; + + return static_cast<_u32>(sector_bitmaps * 1 * 1024 * 1024); + } + + int64 getMetadataSizeSize(int64 rawf_size, _u32 block_size, _u32 sector_size) + { + int64 data_blocks = getDataBlocks(rawf_size, block_size); + _u32 chunk_ratio = getChunkRatio(block_size, sector_size); + int64 sector_bitmaps = data_blocks / chunk_ratio; + if (data_blocks % chunk_ratio != 0)++sector_bitmaps; + + // | -- HEADER -- | -- DATA BLOCKS -- | -- BAT -- | -- SECTOR BITMAP BLOCKS -- | + + return vhdx_header_length + data_blocks * block_size + + getBatLength(rawf_size, block_size, sector_size) + getSectorBitmapBlocksLength(rawf_size, block_size, sector_size); + } + + void makeMetaTableGUID(VhdxGUID& g) + { + unsigned char meta_guid[16] = { 0x8B, 0x7C, 0xA2, 0x06, 0x47, 0x90, 0x4B, 0x9A, 0xB8, 0xFE, 0x57, 0x5F, 0x05, 0x0F, 0x88, 0x6E }; + memcpy(g, meta_guid, sizeof(meta_guid)); + reorderGUID(g); + } + + void makeBatGUID(VhdxGUID& g) + { + unsigned char bat_guid[16] = { 0x2D, 0xC2, 0x77, 0x66, 0xF6, 0x23, 0x42, 0x00, 0x9D, 0x64, 0x11, 0x5E, 0x9B, 0xFD, 0x4A, 0x08 }; + memcpy(g, bat_guid, sizeof(bat_guid)); + reorderGUID(g); + } + + const uint64 meta_region_offset = 2 * 1024 * 1024; + const uint64 bat_table_offset = meta_region_offset + 1 * 1024 * 1024; + + std::vector getVhdxRegionTable(int64 rawf_size, _u32 block_size, _u32 sector_size) + { + std::vector ret; + ret.resize(64 * 1024); + + memcpy(ret.data(), "regi", 4); + VhdxRegionTableHeader* header = reinterpret_cast(ret.data()); + header->EntryCount = 2; + + VhdxRegionTableEntry* meta_entry = reinterpret_cast(ret.data() + sizeof(VhdxRegionTableHeader)); + makeMetaTableGUID(meta_entry->Guid); + meta_entry->FileOffset = meta_region_offset; + meta_entry->Length = 1 * 1024 * 1024; + meta_entry->Required = 1; + + VhdxRegionTableEntry* bat_entry = reinterpret_cast(ret.data() + sizeof(VhdxRegionTableHeader) + + sizeof(VhdxRegionTableEntry)); + makeBatGUID(bat_entry->Guid); + bat_entry->FileOffset = bat_table_offset; + bat_entry->Length = getBatLength(rawf_size, block_size, sector_size); + bat_entry->Required = 1; + + header->Checksum = crc32c(reinterpret_cast(&ret[0]), ret.size()); + + return ret; + } + +#pragma pack(1) + struct VhdxMetadataTableHeader + { + uint64 Signature; + unsigned short Reserved; + unsigned short EntryCount; + _u32 Reserved2[5]; + }; + + struct VhdxMetadataTableEntry + { + VhdxGUID ItemId; + _u32 Offset; + _u32 Length; + _u32 IsUser : 1; + _u32 IsVirtualDisk : 1; + _u32 IsRequired : 1; + _u32 Reserved : 29; + _u32 Reserved2; + }; + + struct VhdxVirtualDiskSize + { + uint64 VirtualDiskSize; + }; + + struct VhdxVirtualDiskLogicalSectorSize + { + _u32 LogicalSectorSize; + }; + + struct VhdxPhysicalDiskSectorSize + { + _u32 PhysicalSectorSize; + }; + + struct VhdxVirtualDiskId + { + VhdxGUID VirtualDiskId; + }; + + struct VhdxParentLocatorHeader + { + VhdxGUID LocatorType; + unsigned short Reserved; + unsigned short KeyValueCount; + }; + + struct VhdxParentLocatorEntry + { + _u32 KeyOffset; + _u32 ValueOffset; + unsigned short KeyLength; + unsigned short ValueLength; + }; + +#pragma pack() + + void makeFileParametersGUID(VhdxGUID& g) + { + unsigned char file_parameters_guid[16] = { 0xCA, 0xA1, 0x67, 0x37, 0xFA, 0x36, 0x4D, 0x43, 0xB3, 0xB6, 0x33, 0xF0, 0xAA, 0x44, 0xE7, 0x6B }; + memcpy(&g, file_parameters_guid, sizeof(file_parameters_guid)); + reorderGUID(g); + } + + void makeVirtualDiskSizeGUID(VhdxGUID& g) + { + unsigned char virtual_disk_size_guid[16] = { 0x2F, 0xA5, 0x42, 0x24, 0xCD, 0x1B, 0x48, 0x76, 0xB2, 0x11, 0x5D, 0xBE, 0xD8, 0x3B, 0xF4, 0xB8 }; + memcpy(&g, virtual_disk_size_guid, sizeof(virtual_disk_size_guid)); + reorderGUID(g); + } + + void makeLogicalSectorSizeGUID(VhdxGUID& g) + { + unsigned char logical_sector_size_guid[16] = { 0x81, 0x41, 0xBF, 0x1D, 0xA9, 0x6F, 0x47, 0x09, 0xBA, 0x47, 0xF2, 0x33, 0xA8, 0xFA, 0xAB, 0x5F }; + memcpy(&g, logical_sector_size_guid, sizeof(logical_sector_size_guid)); + reorderGUID(g); + } + + void makePhysicalSectorSizeGUID(VhdxGUID& g) + { + unsigned char physical_sector_size_guid[16] = { 0xCD, 0xA3, 0x48, 0xC7, 0x44, 0x5D, 0x44, 0x71, 0x9C, 0xC9, 0xE9, 0x88, 0x52, 0x51, 0xC5, 0x56 }; + memcpy(&g, physical_sector_size_guid, sizeof(physical_sector_size_guid)); + reorderGUID(g); + } + + void makeVirtualDiskIdGUID(VhdxGUID& g) + { + unsigned char page83_data_guid[16] = { 0xBE, 0xCA, 0x12, 0xAB, 0xB2, 0xE6, 0x45, 0x23, 0x93, 0xEF, 0xC3, 0x09, 0xE0, 0x00, 0xC7, 0x46 }; + memcpy(&g, page83_data_guid, sizeof(page83_data_guid)); + reorderGUID(g); + } + + void makeParentLocatorGUID(VhdxGUID& g) + { + unsigned char parent_locator_guid[16] = { 0xA8, 0xD3, 0x5F, 0x2D, 0xB3, 0x0B, 0x45, 0x4D, 0xAB, 0xF7, 0xD3, 0xD8, 0x48, 0x34, 0xAB, 0x0C }; + memcpy(&g, parent_locator_guid, sizeof(parent_locator_guid)); + reorderGUID(g); + } + + void makeVhdxParentLocatorGUID(VhdxGUID& g) + { + unsigned char vhdx_parent_locator_guid[16] = { 0xB0, 0x4A, 0xEF, 0xB7, 0xD1, 0x9E, 0x4A, 0x81, 0xB7, 0x89, 0x25, + 0xB8, 0xE9, 0x44, 0x59, 0x13 }; + memcpy(&g, vhdx_parent_locator_guid, sizeof(vhdx_parent_locator_guid)); + reorderGUID(g); + } + + std::vector getMetaRegion(int64 rawf_size, _u32 block_size, _u32 sector_size, + std::string parent_data_uuid, std::string parent_rel_loc, std::string parent_abs_loc) + { + size_t parent_locator_size = 0; + + str_map parent_loc_entries; + + if (!parent_data_uuid.empty()) + { + parent_loc_entries[Server->ConvertToUTF16("parent_linkage")] = Server->ConvertToUTF16(parent_data_uuid); + parent_loc_entries[Server->ConvertToUTF16("relative_path")] = Server->ConvertToUTF16(parent_rel_loc); + parent_loc_entries[Server->ConvertToUTF16("absolute_win32_path")] = Server->ConvertToUTF16(parent_abs_loc); + + parent_locator_size = sizeof(VhdxParentLocatorHeader); + parent_locator_size += sizeof(VhdxParentLocatorEntry) * parent_loc_entries.size(); + + for (auto it : parent_loc_entries) + { + parent_locator_size += it.first.size(); + parent_locator_size += it.second.size(); + } + } + + std::vector ret; + ret.resize(64 * 1024 + + sizeof(VhdxFileParameters) + + sizeof(VhdxVirtualDiskSize) + + sizeof(VhdxVirtualDiskLogicalSectorSize) + + sizeof(VhdxPhysicalDiskSectorSize) + + sizeof(VhdxVirtualDiskId) + + parent_locator_size); + + memcpy(ret.data(), "metadata", 8); + VhdxMetadataTableHeader* header = reinterpret_cast(ret.data()); + header->EntryCount = 5; + + VhdxMetadataTableEntry* file_parameters_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader)); + makeFileParametersGUID(file_parameters_entry->ItemId); + file_parameters_entry->Offset = 64 * 1024; + file_parameters_entry->Length = sizeof(VhdxFileParameters); + file_parameters_entry->IsRequired = 1; + + VhdxFileParameters* file_parameters = reinterpret_cast(ret.data() + file_parameters_entry->Offset); + file_parameters->BlockSize = block_size; + file_parameters->LeaveBlocksAllocated = 0; + file_parameters->HasParent = parent_data_uuid.empty() ? 0 : 1; + + VhdxMetadataTableEntry* virtual_disk_size_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + + sizeof(VhdxMetadataTableEntry)); + makeVirtualDiskSizeGUID(virtual_disk_size_entry->ItemId); + virtual_disk_size_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters); + virtual_disk_size_entry->Length = sizeof(VhdxFileParameters); + virtual_disk_size_entry->IsRequired = 1; + virtual_disk_size_entry->IsVirtualDisk = 1; + + VhdxVirtualDiskSize* virtual_disk_size = reinterpret_cast(ret.data() + virtual_disk_size_entry->Offset); + virtual_disk_size->VirtualDiskSize = rawf_size; + + VhdxMetadataTableEntry* logical_sector_size_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + + 2 * sizeof(VhdxMetadataTableEntry)); + makeLogicalSectorSizeGUID(logical_sector_size_entry->ItemId); + logical_sector_size_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize); + logical_sector_size_entry->Length = sizeof(VhdxVirtualDiskLogicalSectorSize); + logical_sector_size_entry->IsRequired = 1; + logical_sector_size_entry->IsVirtualDisk = 1; + + VhdxVirtualDiskLogicalSectorSize* logical_sector_size = reinterpret_cast(ret.data() + logical_sector_size_entry->Offset); + logical_sector_size->LogicalSectorSize = sector_size; + + VhdxMetadataTableEntry* physical_sector_size_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + + 3 * sizeof(VhdxMetadataTableEntry)); + makePhysicalSectorSizeGUID(physical_sector_size_entry->ItemId); + physical_sector_size_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + sizeof(VhdxVirtualDiskLogicalSectorSize); + physical_sector_size_entry->Length = sizeof(VhdxPhysicalDiskSectorSize); + physical_sector_size_entry->IsRequired = 1; + physical_sector_size_entry->IsVirtualDisk = 1; + + VhdxPhysicalDiskSectorSize* physical_sector_size = reinterpret_cast(ret.data() + physical_sector_size_entry->Offset); + physical_sector_size->PhysicalSectorSize = sector_size; + + VhdxMetadataTableEntry* page83_data_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + + 4 * sizeof(VhdxMetadataTableEntry)); + makeVirtualDiskIdGUID(page83_data_entry->ItemId); + page83_data_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + sizeof(VhdxVirtualDiskLogicalSectorSize) + sizeof(VhdxPhysicalDiskSectorSize); + page83_data_entry->Length = sizeof(VhdxVirtualDiskId); + page83_data_entry->IsRequired = 1; + page83_data_entry->IsVirtualDisk = 1; + + VhdxVirtualDiskId* virtual_disk_id = reinterpret_cast(ret.data() + page83_data_entry->Offset); + secureRandomGuid(virtual_disk_id->VirtualDiskId); + + if (!parent_data_uuid.empty()) + { + ++header->EntryCount; + + VhdxMetadataTableEntry* parent_locator_entry = reinterpret_cast(ret.data() + sizeof(VhdxMetadataTableHeader) + + 5 * sizeof(VhdxMetadataTableEntry)); + makeParentLocatorGUID(parent_locator_entry->ItemId); + parent_locator_entry->Offset = 64 * 1024 + sizeof(VhdxFileParameters) + sizeof(VhdxVirtualDiskSize) + + sizeof(VhdxVirtualDiskLogicalSectorSize) + sizeof(VhdxPhysicalDiskSectorSize) + sizeof(VhdxVirtualDiskId); + parent_locator_entry->Length = static_cast<_u32>(parent_locator_size); + parent_locator_entry->IsRequired = 1; + + VhdxParentLocatorHeader* parent_locator_header = reinterpret_cast(ret.data() + parent_locator_entry->Offset); + + parent_locator_header->KeyValueCount = static_cast<_u16>(parent_loc_entries.size()); + makeVhdxParentLocatorGUID(parent_locator_header->LocatorType); + + size_t entry_pos = parent_locator_entry->Offset + sizeof(VhdxParentLocatorHeader); + size_t str_pos = parent_locator_entry->Offset + sizeof(VhdxParentLocatorHeader) + + sizeof(VhdxParentLocatorEntry) * parent_loc_entries.size(); + + for (auto it: parent_loc_entries) + { + VhdxParentLocatorEntry* entry = reinterpret_cast(ret.data() + entry_pos); + entry_pos += sizeof(VhdxParentLocatorEntry); + + entry->KeyOffset = static_cast<_u32>(str_pos - parent_locator_entry->Offset); + entry->KeyLength = static_cast<_u16>(it.first.size()); + memcpy(ret.data() + str_pos, it.first.data(), it.first.size()); + str_pos += it.first.size(); + + entry->ValueOffset = static_cast<_u32>(str_pos - parent_locator_entry->Offset); + entry->ValueLength = static_cast<_u16>(it.second.size()); + memcpy(ret.data() + str_pos, it.second.data(), it.second.size()); + str_pos += it.second.size(); + } + + assert(str_pos == ret.size()); + } + + return ret; + } + +#pragma pack(1) + struct LogEntryHeader + { + _u32 signature; + _u32 Checksum; + _u32 EntryLength; + _u32 Tail; + int64 SequenceNumber; + _u32 DescriptorCount; + _u32 Reserved; + VhdxGUID LogGuid; + int64 FlushedFileOffset; + int64 LastFileOffset; + }; + + struct LogZeroDescriptor + { + _u32 signature; + _u32 Reserved; + int64 ZeroLength; + int64 FileOffset; + int64 SequenceNumber; + }; + + struct LogDataDescriptor + { + _u32 signature; + char TrailingBytes[4]; + char LeadingBytes[8]; + int64 FileOffset; + int64 SequenceNumber; + }; + + struct LogDataSector + { + _u32 signature; + _u32 SequenceHigh; + char data[4084]; + _u32 SequenceLow; + }; +#pragma pack() + + + struct LogData + { + int64 offset; + char data[4096]; + }; + + struct LogEntry + { + std::vector to_zero; + std::vector to_write; + int64 sequence_number = -1; + int64 length; + int64 fsize; + int64 new_fsize; + uint64 tail_pos; + }; + + typedef union + { + struct { + _u32 LowPart; + _u32 HighPart; + }; + struct { + _u32 LowPart; + _u32 HighPart; + } u; + int64 QuadPart; + } SSequence; + + LogEntry readLogEntry(IFile* f, const VhdxGUID& log_guid, int64 off) + { + LogEntry loge; + + std::vector buf(4096); + + if (f->Read(off, buf.data(), static_cast<_u32>(buf.size())) != buf.size()) + { + Server->Log("Error reading log entry header. " + os_last_error_str(), LL_WARNING); + return loge; + } + + std::string signature(buf.data(), 4); + + if (signature != "loge") + return loge; + + LogEntryHeader* header = reinterpret_cast(buf.data()); + + if (!equalsGUID(header->LogGuid, log_guid)) + return loge; + + loge.length = header->EntryLength; + std::vector entry_buf(header->EntryLength); + + if (f->Read(off, entry_buf.data(), static_cast<_u32>(entry_buf.size())) != entry_buf.size()) + { + Server->Log("Error reading log entry (size=" + std::to_string(header->EntryLength) + "). " + + os_last_error_str(), LL_WARNING); + return loge; + } + + _u32 checksum = header->Checksum; + + header = reinterpret_cast(entry_buf.data()); + header->Checksum = 0; + + _u32 checksum_calc = crc32c(reinterpret_cast(entry_buf.data()), entry_buf.size()); + + if (checksum_calc != checksum) + { + Server->Log("Log entry checksum is wrong", LL_WARNING); + return loge; + } + + int64 entry_seq = header->SequenceNumber; + + loge.fsize = header->FlushedFileOffset; + loge.new_fsize = header->LastFileOffset; + loge.tail_pos = header->Tail; + + int64 desc_off = 4096; + if (header->DescriptorCount > 126) + { + desc_off += ((header->DescriptorCount - 126) / 128 ) *4096; + if ( (header->DescriptorCount - 126) % 128 != 0) + desc_off += 4096; + } + + for (int64 i = 0; i < header->DescriptorCount; ++i) + { + char* desc_ptr = entry_buf.data() + 64 + i * 32; + std::string desc_sig(desc_ptr, 4); + + if (desc_sig == "zero") + { + LogZeroDescriptor* zero_desc = reinterpret_cast(desc_ptr); + if (entry_seq != zero_desc->SequenceNumber) + { + Server->Log("Zero log entry sequence number is wrong", LL_WARNING); + return loge; + } + + loge.to_zero.push_back(*zero_desc); + } + else if (desc_sig == "desc") + { + LogDataDescriptor* data_desc = reinterpret_cast(desc_ptr); + if (entry_seq != data_desc->SequenceNumber) + { + Server->Log("Data log entry sequence number is wrong", LL_WARNING); + return loge; + } + + LogDataSector* data_sec = reinterpret_cast(entry_buf.data() + desc_off); + + std::string data_sec_sig(entry_buf.data() + desc_off, 4); + + if (data_sec_sig != "data") + { + Server->Log("Data log entry signature is wrong", LL_WARNING); + return loge; + } + + SSequence seq; + seq.QuadPart = data_desc->SequenceNumber; + + if (data_sec->SequenceHigh != seq.HighPart) + { + Server->Log("Data log entry high sequence number is wrong", LL_WARNING); + return loge; + } + + if (data_sec->SequenceLow != seq.LowPart) + { + Server->Log("Data log entry low sequence number is wrong", LL_WARNING); + return loge; + } + + LogData log_data; + log_data.offset = data_desc->FileOffset; + memcpy(log_data.data, data_desc->LeadingBytes, 8); + memcpy(log_data.data + 8, data_sec->data, sizeof(data_sec->data)); + memcpy(log_data.data + 8 + sizeof(data_sec->data), data_desc->TrailingBytes, 4); + + loge.to_write.push_back(log_data); + } + else + { + Server->Log("Unknown log entry signature", LL_WARNING); + return loge; + } + } + + loge.sequence_number = entry_seq; + return loge; + } +} + +VHDXFile::VHDXFile(const std::string& fn, bool pRead_only, uint64 pDstsize, unsigned int pBlocksize, bool fast_mode, bool compress, size_t compress_n_threads) + : dst_size(pDstsize), fast_mode(fast_mode), read_only(pRead_only) +{ + is_open = open(fn, compress, compress_n_threads); +} + +VHDXFile::VHDXFile(const std::string& fn, const std::string& parent_fn, bool pRead_only, + bool fast_mode, bool compress, uint64 pDstsize, size_t compress_n_threads) + : fast_mode(fast_mode), read_only(pRead_only), dst_size(pDstsize), + parent_fn(parent_fn) +{ + if (!FileExists(fn)) + { + parent = std::make_unique(parent_fn, true, + 0); + + if (!parent->isOpen()) + { + Server->Log("Error opening VHDX parent at \"" + parent_fn + "\"", LL_ERROR); + return; + } + + dst_size = parent->getSize(); + + if (pDstsize > 0 && pDstsize != dst_size) + { + dst_size = pDstsize; + } + } + + is_open = open(fn, compress, compress_n_threads); +} + +VHDXFile::~VHDXFile() +{ + if (!is_open) + return; + + if (!read_only) + { + finish(); + } +} + +bool VHDXFile::Seek(_i64 offset) +{ + spos = offset; + return true; +} + +bool VHDXFile::Read(char* buffer, size_t bsize, size_t& read) +{ + bool has_read_error = false; + read = Read(spos, buffer, static_cast<_u32>(bsize), &has_read_error); + spos += read; + return !has_read_error; +} + +_u32 VHDXFile::Write(const char* buffer, _u32 bsize, bool* has_error) +{ + _u32 rc = Write(spos, buffer, bsize, has_error); + spos += rc; + return rc; +} + +bool VHDXFile::isOpen(void) +{ + return is_open; +} + +uint64 VHDXFile::getSize(void) +{ + return Size(); +} + +uint64 VHDXFile::usedSize() +{ + uint64 ret = 0; + for (int64 i = 0; i < dst_size; i += block_size) + { + _u32 block = getBatEntry(spos, block_size, sector_size); + + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + + if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT || + bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) + ret += block_size; + } + + return ret; +} + +std::string VHDXFile::getFilename(void) +{ + return file->getFilename(); +} + +bool VHDXFile::has_sector(_i64 sector_size) +{ + if (!has_sector_int(spos)) + { + if (parent.get() != nullptr) + return parent->has_sector_int(spos); + } + + return true; +} + +bool VHDXFile::this_has_sector(_i64 sector_size) +{ + return has_sector_int(spos); +} + +unsigned int VHDXFile::getBlocksize() +{ + return block_size; +} + +bool VHDXFile::finish() +{ + if (!finished) + { + finished = true; + + if (read_only) + return true; + + bool ret = syncInt(true); + + if (ret && parent.get()!=nullptr) + { + ret = parent->finish(); + } + + CompressedFile* compfile = dynamic_cast(file); + if (compfile != NULL) + { + if (compfile->finish()) + { + finished = true; + return true; + } + } + } + return true; +} + +bool VHDXFile::trimUnused(_i64 fs_offset, _i64 trim_blocksize, ITrimCallback* trim_callback) +{ + return true; +} + +bool VHDXFile::syncBitmap(_i64 fs_offset) +{ + return true; +} + +bool VHDXFile::makeFull(_i64 fs_offset, IVHDWriteCallback* write_callback) +{ + FileWrapper devfile(this, fs_offset); + std::auto_ptr bitmap_source; + + bitmap_source.reset(new ClientBitmap(backing_file->getFilename() + ".cbitmap")); + + if (bitmap_source->hasError()) + { + Server->Log("Error reading client bitmap. Falling back to reading bitmap from NTFS", LL_WARNING); + + bitmap_source.reset(new FSNTFS(&devfile, IFSImageFactory::EReadaheadMode_None, false, NULL)); + } + + if (bitmap_source->hasError()) + { + Server->Log("Error opening NTFS bitmap. Cannot convert incremental to full image.", LL_WARNING); + return false; + } + + unsigned int bitmap_blocksize = static_cast(bitmap_source->getBlocksize()); + + std::vector buffer; + buffer.resize(sector_size); + + int64 ntfs_blocks_per_vhd_sector = block_size / bitmap_blocksize; + + for (int64 ntfs_block = 0, n_ntfs_blocks = devfile.Size() / bitmap_blocksize; + ntfs_block < n_ntfs_blocks; ntfs_block += ntfs_blocks_per_vhd_sector) + { + bool has_vhd_sector = false; + for (int64 i = ntfs_block; + i < ntfs_block + ntfs_blocks_per_vhd_sector + && i < n_ntfs_blocks; ++i) + { + if (bitmap_source->hasBlock(i)) + { + has_vhd_sector = true; + break; + } + } + + if (has_vhd_sector) + { + int64 block_pos = fs_offset + ntfs_block * bitmap_blocksize; + int64 max_block_pos = (std::min)(fs_offset + ntfs_block * bitmap_blocksize + block_size, + fs_offset + n_ntfs_blocks * bitmap_blocksize); + for (int64 i = block_pos; i < max_block_pos; i += sector_size) + { + Seek(i); + + if (!has_block(false) + && has_block(true)) + { + bool has_error = false; + if (Read(buffer.data(), sector_size) != sector_size) + { + Server->Log("Error converting incremental to full image. Cannot read from parent VHDX file at position " + convert(i), LL_WARNING); + return false; + } + + if (!write_callback->writeVHD(i, buffer.data(), sector_size)) + { + Server->Log("Error converting incremental to full image. Cannot write to VHDX file at position " + convert(i), LL_WARNING); + return false; + } + } + } + } + else + { + int64 block_pos = ntfs_block * bitmap_blocksize; + int64 max_block_pos = (std::min)(ntfs_block * bitmap_blocksize + block_size, + n_ntfs_blocks * bitmap_blocksize); + + write_callback->emptyVHDBlock(block_pos, max_block_pos); + } + } + + parent.reset(); + parent_fn.clear(); + + std::vector meta_region = getMetaRegion(dst_size, block_size, sector_size, + std::string(), std::string(), std::string()); + + if (file->Write(meta_region_offset, meta_region.data(), static_cast<_u32>(meta_region.size())) != meta_region.size()) + return false; + + return true; +} + +bool VHDXFile::setUnused(_i64 unused_start, _i64 unused_end) +{ + if (!Seek(unused_start)) + { + Server->Log("Error while sseking to " + convert(unused_end) + " in VHDX file." + "Size is " + convert(dst_size) + " -2", LL_ERROR); + return false; + } + + if (read_only) + { + Server->Log("VHDX file is read only -2", LL_ERROR); + return false; + } + + if (unused_end > dst_size) + { + Server->Log("VHDX file is not large enough. Want to trim till " + + convert(unused_end) + " but size is " + convert(dst_size), LL_ERROR); + return false; + } + + std::vector zero_buf; + + while (unused_start< unused_end) + { + int64 block = getBatEntry(unused_start, block_size, sector_size); + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + + if (unused_start % block_size == 0 && + unused_start + block_size <= unused_end) + { + bat_entry->State = PAYLOAD_BLOCK_ZERO; + unused_start += block_size; + continue; + } + + _u32 curr_sector_size = sector_size; + if (unused_start % sector_size != 0) + { + curr_sector_size = sector_size - unused_start % sector_size; + } + + size_t wantwrite = (std::min)(static_cast(curr_sector_size), + static_cast(unused_end - unused_start)); + + bool copy_prev = false; + + if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + bool set; + if (!isSectorSet(unused_start, set)) + { + return false; + } + + if (!set) + { + if (!setSector(unused_start)) + { + return false; + } + + copy_prev = true; + } + else + { + if (zero_buf.size() != wantwrite) + { + zero_buf.resize(wantwrite); + } + _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + unused_start % block_size, + zero_buf.data(), static_cast<_u32>(wantwrite)); + } + + unused_start += wantwrite; + } + else if (bat_entry->State == PAYLOAD_BLOCK_UNDEFINED || + bat_entry->State == PAYLOAD_BLOCK_UNMAPPED || + bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) + { + if (!allocateBatBlockFull(block)) + { + return false; + } + + bat_entry->State = PAYLOAD_BLOCK_PARTIALLY_PRESENT; + + if (!setSector(unused_start)) + { + return false; + } + + unused_start += wantwrite; + + copy_prev = true; + } + else if (bat_entry->State != PAYLOAD_BLOCK_ZERO) + { + Server->Log("Unknown bat entry state " + std::to_string(bat_entry->State), LL_ERROR); + return false; + } + + if (copy_prev && curr_sector_size < sector_size && + parent.get()!=nullptr) + { + std::vector prev_buf(sector_size - curr_sector_size); + + int64 prev_pos = (unused_start / sector_size) * sector_size; + + _u32 rc = parent->Read(prev_pos, + prev_buf.data(), static_cast<_u32>(prev_buf.size())); + + if (rc != prev_buf.size()) + return false; + + rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + prev_pos % block_size, + prev_buf.data(), static_cast<_u32>(prev_buf.size())); + + if (rc != prev_buf.size()) + return false; + } + if (copy_prev && unused_start + wantwrite == unused_end && + unused_end % sector_size != 0) + { + std::vector prev_buf(sector_size - unused_end%sector_size); + + int64 prev_pos = unused_end; + + _u32 rc = parent->Read(prev_pos, + prev_buf.data(), static_cast<_u32>(prev_buf.size())); + + if (rc != prev_buf.size()) + return false; + + rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + prev_pos % block_size, + prev_buf.data(), static_cast<_u32>(prev_buf.size())); + + if (rc != prev_buf.size()) + return false; + } + } + + return true; +} + +bool VHDXFile::setBackingFileSize(_i64 fsize) +{ + if (file != backing_file.get()) + { + return false; + } + + fsize += 1 * 1024 * 1024; + fsize += bat_region.Length; + fsize += curr_header.LogLength; + fsize += meta_table_region.Length; + + if (fsize > backing_file->Size()) + { + return backing_file->Resize(fsize); + } + + return false; +} + +std::string VHDXFile::Read(_u32 tr, bool* has_error) +{ + std::string ret = Read(spos, tr, has_error); + spos += ret.size(); + return ret; +} + +std::string VHDXFile::Read(int64 spos, _u32 tr, bool* has_error) +{ + std::string ret; + ret.resize(tr); + + _u32 rc = Read(spos, &ret[0], tr, has_error); + if (rc < tr) + ret.resize(rc); + + return ret; +} + +_u32 VHDXFile::Read(char* buffer, _u32 bsize, bool* has_error) +{ + _u32 rc = Read(spos, buffer, bsize, has_error); + spos += rc; + return rc; +} + +_u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) +{ + if (spos> dst_size) + { + if (has_error != nullptr) + *has_error = true; + + return 0; + } + else if (spos + bsize >= dst_size) + { + bsize = static_cast<_u32>(dst_size - spos); + } + + _u32 read = 0; + while (bsize - read > 0) + { + _u32 block = getBatEntry(spos, block_size, sector_size); + + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + + if (bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) + { + _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); + + _u32 rc = file->Read(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + buffer + read, toread); + + read += rc; + spos += rc; + + if (rc < toread) + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + + continue; + } + + if (parent.get()==nullptr) + { + _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); + + if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + else if (bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT || + bat_entry->State == PAYLOAD_BLOCK_UNDEFINED || + bat_entry->State == PAYLOAD_BLOCK_ZERO || + bat_entry->State == PAYLOAD_BLOCK_UNMAPPED) + { + toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); + + memset(buffer+read, 0, toread); + read += toread; + spos += toread; + } + else + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + } + else + { + _u32 toread; + if (bat_entry->State != PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); + } + else + { + toread = (std::min)(sector_size - static_cast<_u32>(spos % sector_size), bsize - read); + } + + if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + bool set; + if (!isSectorSet(spos, set)) + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + + _u32 rc; + if (set) + { + rc = file->Read(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + buffer + read, toread); + } + else + { + rc = parent->Read(spos, buffer + read, toread); + } + + read += rc; + spos += rc; + + if (rc < toread) + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + } + else if (bat_entry->State == PAYLOAD_BLOCK_UNDEFINED || + bat_entry->State == PAYLOAD_BLOCK_ZERO || + bat_entry->State == PAYLOAD_BLOCK_UNMAPPED) + { + memset(buffer + read, 0, toread); + read += toread; + spos += toread; + } + else if (bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) + { + _u32 rc = parent->Read(spos, buffer + read, toread); + + read += rc; + spos += rc; + + if (rc < toread) + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + } + else + { + if (has_error != nullptr) + *has_error = true; + + return read; + } + } + } + return read; +} + +_u32 VHDXFile::Write(const std::string& tw, bool* has_error) +{ + _u32 rc = Write(spos, tw.data(), static_cast<_u32>(tw.size()), has_error); + spos += rc; + return rc; +} + +_u32 VHDXFile::Write(int64 spos, const std::string& tw, bool* has_error) +{ + return Write(spos, tw.data(), static_cast<_u32>(tw.size()), has_error); +} + +_u32 VHDXFile::Write(int64 spos, const char* buffer, _u32 bsize, bool* has_error) +{ + if (spos > dst_size) + { + if (has_error != nullptr) + *has_error = true; + + return 0; + } + else if (spos + bsize >= dst_size) + { + bsize = static_cast<_u32>(dst_size - spos); + } + + if (!data_write_uuid_updated) + { + randomGuid(curr_header.DataWriteGuid); + + data_write_uuid_updated = true; + + if (!fast_mode && !updateHeader()) + { + if (has_error != nullptr) + *has_error = true; + + return 0; + } + } + + _u32 written = 0; + while (bsize - written > 0) + { + int64 block = getBatEntry(spos, block_size, sector_size); + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + + if (bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) + { + _u32 towrite = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - written); + + _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + buffer + written, towrite); + + written += rc; + spos += rc; + + if (rc < towrite) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + + continue; + } + + if (parent.get()==nullptr) + { + _u32 towrite = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - written); + + if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + else if (bat_entry->State != PAYLOAD_BLOCK_NOT_PRESENT && + bat_entry->State != PAYLOAD_BLOCK_UNDEFINED && + bat_entry->State != PAYLOAD_BLOCK_ZERO && + bat_entry->State != PAYLOAD_BLOCK_UNMAPPED) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + + if (!allocateBatBlockFull(block)) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + + _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + buffer + written, towrite); + + written += rc; + spos += rc; + + if (rc < towrite) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + } + else + { + _u32 towrite = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - written); + + if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) + { + if (!setSector(spos, spos+towrite)) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + } + else if (bat_entry->State != PAYLOAD_BLOCK_NOT_PRESENT && + bat_entry->State != PAYLOAD_BLOCK_UNDEFINED && + bat_entry->State != PAYLOAD_BLOCK_ZERO && + bat_entry->State != PAYLOAD_BLOCK_UNMAPPED) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + else + { + if (!allocateBatBlockFull(block)) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + + bat_entry->State = PAYLOAD_BLOCK_PARTIALLY_PRESENT; + + if (!setSector(spos, spos+towrite)) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + } + + + _u32 rc = file->Write(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + buffer + written, towrite); + + written += rc; + spos += rc; + + if (rc < towrite) + { + if (has_error != nullptr) + *has_error = true; + + return written; + } + } + } + return written; +} + +_i64 VHDXFile::Size(void) +{ + return dst_size; +} + +_i64 VHDXFile::RealSize() +{ + return static_cast<_i64>(usedSize()); +} + +bool VHDXFile::PunchHole(_i64 spos, _i64 size) +{ + return false; +} + +bool VHDXFile::Sync() +{ + return syncInt(false); +} + +bool VHDXFile::syncInt(bool full) +{ + { + std::lock_guard lock(pending_sector_bitmaps_mutex); + + for (_u32 sector_block : pending_sector_bitmaps) + { + VhdxBatEntry* sector_bat_entry = reinterpret_cast(bat_buf.data()) + sector_block; + + if (sector_bat_entry->State != PAYLOAD_BLOCK_FULLY_PRESENT) + { + Server->Log("Sector bitmap bat entry not fully present when syncing", LL_WARNING); + return false; + } + + auto it_sector_bitmap = sector_bitmap_bufs.find(sector_block); + if (it_sector_bitmap == sector_bitmap_bufs.end()) + { + assert(false); + return false; + } + + if (file->Write(sector_bat_entry->FileOffsetMB * 1024 * 1024, + it_sector_bitmap->second.data(), block_size) != block_size) + { + Server->Log("Error writing pending sector bitmap block. " + os_last_error_str(), LL_WARNING); + return false; + } + } + + pending_sector_bitmaps.clear(); + } + + + bool retry; + do + { + retry = false; + + std::unique_lock lock(log_mutex); + + int64 stop_idx = -1; + if (!fast_mode) + { + int64 new_flushed_vhdx_size = file->Size(); + if (flushed_vhdx_size != new_flushed_vhdx_size) + { + if (!file->Sync()) + { + Server->Log("Error syncing VHDX backing file -1. " + os_last_error_str(), LL_WARNING); + return false; + } + + flushed_vhdx_size = new_flushed_vhdx_size; + } + + int64 b_idx = -1; + int64 last_log_idx = -1; + for (int64 entry_idx : pending_bat_entries) + { + int64 c_b_idx = (entry_idx * sizeof(int64)) / log_sector_size; + + if (b_idx != c_b_idx) + { + b_idx = c_b_idx; + + bool full = false; + if (!logWrite(bat_region.FileOffset + c_b_idx * log_sector_size, + bat_buf.data() + c_b_idx * log_sector_size, log_sector_size, -1, full)) + { + if (full) + { + stop_idx = entry_idx; + retry = true; + break; + } + else + { + Server->Log("Error logging VHDX BAT write", LL_WARNING); + return false; + } + } + } + } + + if (!file->Sync()) + { + Server->Log("Error syncing VHDX backing file -2. " + os_last_error_str(), LL_WARNING); + return false; + } + } + + int64 b_idx = -1; + for (auto it = pending_bat_entries.begin(); it != pending_bat_entries.end();) + { + int64 entry_idx = *it; + + if (entry_idx == stop_idx) + { + break; + } + + int64 c_b_idx = (entry_idx * sizeof(int64)) / log_sector_size; + + if (b_idx != c_b_idx) + { + b_idx = c_b_idx; + + _u32 rc = file->Write(bat_region.FileOffset + c_b_idx * log_sector_size, + bat_buf.data() + c_b_idx * log_sector_size, log_sector_size); + + if (rc != log_sector_size) + return false; + } + + if (stop_idx != -1) + { + auto it_prev = it; + ++it; + pending_bat_entries.erase(it_prev); + } + else + { + ++it; + } + } + + if(stop_idx==-1) + pending_bat_entries.clear(); + + if (fast_mode) + { + if (!file->Sync()) + { + Server->Log("Error syncing VHDX backing file -3. " + os_last_error_str(), LL_WARNING); + return false; + } + } + + } while (retry); + + if (full && !fast_mode) + { + if (!file->Sync()) + { + Server->Log("Error syncing VHDX backing file -4. " + os_last_error_str(), LL_WARNING); + return false; + } + + zeroGUID(curr_header.LogGuid); + + if (!updateHeader()) + return false; + } + + return true; +} + +void VHDXFile::getDataWriteGUID(VhdxGUID& g) +{ + copyGUID(curr_header.DataWriteGuid, g); +} + +bool VHDXFile::createNew() +{ + memset(&curr_header, 0, sizeof(curr_header)); + memcpy(&curr_header, "head", 4); + curr_header.SequenceNumber = 1; + secureRandomGuid(curr_header.FileWriteGuid); + secureRandomGuid(curr_header.DataWriteGuid); + data_write_uuid_updated = true; + curr_header.Version = 1; + curr_header.LogOffset = 1 * 1024 * 1024; + curr_header.LogLength = 1 * 1024 * 1024; + curr_header.Checksum = crc32c(reinterpret_cast(&curr_header), sizeof(curr_header)); + + log_pos = 0; + log_start_pos = 0; + + block_size = 1 * 1024 * 1024; + vhdx_params.BlockSize = block_size; + sector_size = 512; + + std::vector ident = getFileIdentifier(); + + if (file->Write(0, ident.data(), static_cast<_u32>(ident.size())) != ident.size()) + { + Server->Log("Error writing new ident. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (file->Write(64 * 1024, reinterpret_cast(&curr_header), sizeof(curr_header)) != sizeof(curr_header)) + { + Server->Log("Error writing new header 1. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (file->Write(128 * 1024, reinterpret_cast(&curr_header), sizeof(curr_header)) != sizeof(curr_header)) + { + Server->Log("Error writing new header 2. " + os_last_error_str(), LL_WARNING); + return false; + } + + curr_header_pos = 64 * 1024; + + std::vector region_table = getVhdxRegionTable(dst_size, block_size, sector_size); + + meta_table_region.FileOffset = meta_region_offset; + meta_table_region.Length = 1 * 1024 * 1024; + + bat_region.FileOffset = bat_table_offset; + bat_region.Length = getBatLength(dst_size, block_size, sector_size); + + if (file->Write(192 * 1024, region_table.data(), static_cast<_u32>(region_table.size())) != region_table.size()) + { + Server->Log("Error writing new region table 1. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (file->Write(256 * 1024, region_table.data(), static_cast<_u32>(region_table.size())) != region_table.size()) + { + Server->Log("Error writing new region table 2. " + os_last_error_str(), LL_WARNING); + return false; + } + + std::string parent_data_uuid; + std::string parent_abs_path; + std::string parent_rel_path; + if (parent.get() != nullptr) + { + VhdxGUID g; + parent->getDataWriteGUID(g); + parent_data_uuid = strGUID(g); + + if (parent_fn.find("..") == 0) + { + parent_rel_path = greplace("/", "\\", parent_fn); + + std::string curr_dir = ExtractFilePath(file->getFilename()); + parent_abs_path = parent_fn; + + while (next(parent_abs_path, 0, "..\\")) + { + curr_dir = ExtractFilePath(file->getFilename()); + parent_abs_path.erase(0, 3); + } + + if (!curr_dir.empty() && curr_dir[0] == '\\') + { + curr_dir.erase(0, 1); + } + + parent_abs_path = os_file_prefix(parent_abs_path + "\\" + curr_dir); + } + else + { + parent_abs_path = os_file_prefix(parent_fn); + + std::string fn = file->getFilename(); + std::string cparent_fn = parent_fn; + + while (fn.find("\\") != std::string::npos + && cparent_fn.find("\\") != std::string::npos + && getuntil("\\", fn)==getuntil("\\", cparent_fn)) + { + fn = getafter("\\", fn); + cparent_fn = getafter("\\", cparent_fn); + } + + parent_rel_path = cparent_fn; + for (char ch : fn) + { + if (ch == '\\') + parent_rel_path += "..\\"; + } + } + } + + std::vector meta_region = getMetaRegion(dst_size, block_size, sector_size, + parent_data_uuid, parent_rel_path, parent_abs_path); + + if (file->Write(meta_region_offset, meta_region.data(), static_cast<_u32>(meta_region.size())) != meta_region.size()) + { + Server->Log("Error writing new metadata region. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (file == backing_file.get() && + !backing_file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) + { + Server->Log("Error writing new bat region. " + os_last_error_str(), LL_WARNING); + return false; + } + + allocated_size = file->Size(); + + next_payload_pos = bat_region.FileOffset + bat_region.Length; + + bat_buf.resize(bat_region.Length); + + return true; +} + +bool VHDXFile::updateHeader() +{ + ++curr_header.SequenceNumber; + + curr_header.Checksum = 0; + curr_header.Checksum = crc32c(reinterpret_cast(&curr_header), sizeof(curr_header)); + + if (file->Write(curr_header_pos, reinterpret_cast(&curr_header), sizeof(curr_header)) != sizeof(curr_header)) + { + Server->Log("Error writing VHDX header to pos " + std::to_string(curr_header_pos) + ". " + os_last_error_str()); + return false; + } + + if (!file->Sync()) + { + Server->Log("Error syncing VHDX backing file after updating header. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (curr_header_pos == 64 * 1024) + curr_header_pos = 128 * 1024; + else + curr_header_pos = 64 * 1024; + + return true; +} + +bool VHDXFile::replayLog() +{ + VHDXFile::LogSequence seq = findLogSequence(); + + if (seq.max_sequence == 0) + { + Server->Log("Could not find VHDX log sequence -1", LL_WARNING); + return false; + } + + if (seq.entries.empty()) + { + Server->Log("Could not find VHDX log sequence -2", LL_WARNING); + return false; + } + + LogEntry head_entry = readLogEntry(file, curr_header.LogGuid, seq.entries[seq.entries.size()-1]); + + if (file->Size() < head_entry.fsize) + { + Server->Log("VHDX size smaller than expected from log expected="+std::to_string(head_entry.fsize) + +" got="+std::to_string(file->Size()), LL_WARNING); + return false; + } + + for (int64 entry_pos : seq.entries) + { + LogEntry loge = readLogEntry(file, curr_header.LogGuid, entry_pos); + + if (loge.sequence_number == -1) + { + Server->Log("Error reading log entry while replaying log", LL_WARNING); + return false; + } + + if (file->Size() < loge.fsize) + { + Server->Log("VHDX size smaller than expected from log entry expected=" + std::to_string(loge.fsize) + + " got=" + std::to_string(file->Size()), LL_WARNING); + return false; + } + + for (LogZeroDescriptor& zero_desc : loge.to_zero) + { + std::vector zero_buf(zero_desc.ZeroLength); + if (file->Write(zero_desc.FileOffset, zero_buf.data(), static_cast<_u32>(zero_buf.size())) != zero_buf.size()) + { + Server->Log("Error writing zeroes from log. " + os_last_error_str(), LL_WARNING); + return false; + } + } + + for (LogData& log_data : loge.to_write) + { + if (file->Write(log_data.offset, log_data.data, sizeof(log_data.data)) != sizeof(log_data.data)) + { + Server->Log("Error writing data from log. " + os_last_error_str(), LL_WARNING); + return false; + } + } + + log_sequence_num = loge.sequence_number + 1; + } + + int64 new_fsize = -1; + if (file->Size() < head_entry.new_fsize && + file == backing_file.get()) + { + if (backing_file->Resize(head_entry.new_fsize, false)) + new_fsize = head_entry.new_fsize; + } + + if (!file->Sync()) + { + Server->Log("Error syncing after writing log. " + os_last_error_str()); + return false; + } + + if (new_fsize >= 0) + flushed_vhdx_size = new_fsize; + + zeroGUID(curr_header.LogGuid); + + return updateHeader(); +} + +bool VHDXFile::readHeader() +{ + std::string ident = file->Read(0LL, 8); + + if (ident != "vhdxfile") + { + Server->Log("VHDX header tag wrong", LL_WARNING); + return false; + } + + VhdxHeader header1, header2; + + if (file->Read(64LL * 1024, reinterpret_cast(&header1), sizeof(header1)) != sizeof(header1)) + { + Server->Log("Could not read VHDX header 1. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (file->Read(128LL * 1024, reinterpret_cast(&header2), sizeof(header2)) != sizeof(header2)) + { + Server->Log("Could not read VHDX header 2. " + os_last_error_str(), LL_WARNING); + return false; + } + + VhdxHeader* sel_header = nullptr; + + if (checkHeader(file, header1)) + { + sel_header = &header1; + } + + if (checkHeader(file, header2) && + header2.SequenceNumber > header1.SequenceNumber) + { + sel_header = &header2; + } + + if (sel_header == nullptr) + { + Server->Log("Both VHDX headers are invalid", LL_WARNING); + return false; + } + + memcpy(&curr_header, sel_header, sizeof(curr_header)); + + return true; +} + +bool VHDXFile::readRegionTable(int64 off) +{ + std::vector region_buf(64 * 1024); + + if (file->Read(off, region_buf.data(), static_cast<_u32>(region_buf.size())) != region_buf.size()) + { + Server->Log("Error reading VHDX region table. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (std::string(region_buf.data(), 4) != "regi") + { + Server->Log("VHDX region table tag wrong", LL_WARNING); + return false; + } + + VhdxRegionTableHeader* header = reinterpret_cast(region_buf.data()); + + _u32 ccrc = header->Checksum; + + header->Checksum = 0; + + if (crc32c(reinterpret_cast(region_buf.data()), region_buf.size()) != ccrc) + { + Server->Log("VHDX region table checksum wrong", LL_WARNING); + return false; + } + + VhdxGUID meta_table_guid; + makeMetaTableGUID(meta_table_guid); + VhdxGUID bat_guid; + makeBatGUID(bat_guid); + + unsigned int found = 0; + + for (_u32 i = 0; i < header->EntryCount; ++i) + { + VhdxRegionTableEntry* entry = reinterpret_cast(region_buf.data() + sizeof(VhdxRegionTableHeader) + + i*sizeof(VhdxRegionTableEntry)); + + if (equalsGUID(entry->Guid, meta_table_guid)) + { + memcpy(&meta_table_region, entry, sizeof(meta_table_region)); + if (found & 1) + { + Server->Log("Found metadata table region entry twice", LL_WARNING); + return false; + } + found |= 1; + } + else if (equalsGUID(entry->Guid, bat_guid)) + { + memcpy(&bat_region, entry, sizeof(bat_region)); + if (found & 2) + { + Server->Log("Found BAT table region entry twice", LL_WARNING); + return false; + } + found |= 2; + } + else + { + Server->Log("Unknown region table entry " + strGUID(entry->Guid), LL_WARNING); + return false; + } + } + + if ((found ^ (1 | 2)) != 0) + { + Server->Log("Did not find required region table entry. Found="+std::to_string(found), LL_WARNING); + return false; + } + + return true; +} + +bool VHDXFile::readBat() +{ + bat_buf.resize(bat_region.Length); + + const _u32 read_size = 512 * 1024; + + for (_u32 i = 0; i < bat_region.Length; i += read_size) + { + _u32 toread = (std::min)(read_size, bat_region.Length - i); + if (file->Read(bat_region.FileOffset + i, bat_buf.data() + i, toread) != toread) + { + Server->Log("Error reading VHDX BAT at pos " + std::to_string(bat_region.FileOffset + i) + + " toread " + std::to_string(toread) + ". " + os_last_error_str(), LL_WARNING); + return false; + } + } + + return true; +} + +bool VHDXFile::readMeta() +{ + std::vector meta_table(64 * 1024); + + if (meta_table_region.Length < 64 * 1024) + { + Server->Log("Meta table region length smaller than 64KiB", LL_WARNING); + return false; + } + + if (file->Read(meta_table_region.FileOffset, meta_table.data(), static_cast<_u32>(meta_table.size())) != meta_table.size()) + { + Server->Log("Error reading VHDX meta table from pos " + + std::to_string(meta_table_region.FileOffset) + ". " + os_last_error_str(), LL_WARNING); + return false; + } + + VhdxMetadataTableHeader* table_header = reinterpret_cast(meta_table.data()); + + std::string ident(meta_table.data(), 8); + + if (ident != "metadata") + { + Server->Log("Meta table ident wrong", LL_WARNING); + return false; + } + + sector_size = 0; + physical_sector_size = 0; + vhdx_params.BlockSize = 0; + dst_size = -1; + + VhdxGUID parent_linkage_guid = {}; + VhdxGUID file_parameters_guid, virtual_disk_size_guid, logical_sector_size_guid, + physical_sector_size_guid, virtual_disk_id_guid, parent_locator_guid; + + makeFileParametersGUID(file_parameters_guid); + makeVirtualDiskSizeGUID(virtual_disk_size_guid); + makeLogicalSectorSizeGUID(logical_sector_size_guid); + makePhysicalSectorSizeGUID(physical_sector_size_guid); + makeVirtualDiskIdGUID(virtual_disk_id_guid); + makeParentLocatorGUID(parent_locator_guid); + + std::string rel_parent_path; + std::string volume_parent_path; + std::string absolute_win32_parent_path; + + for (unsigned short i = 0; i < table_header->EntryCount; ++i) + { + if (32 + i * 32 + 32 > meta_table.size()) + { + Server->Log("Meta table not large enough", LL_WARNING); + return false; + } + + VhdxMetadataTableEntry* table_entry = reinterpret_cast(meta_table.data() + 32 + i * 32); + + if (table_entry->Offset < 64 * 1024) + { + Server->Log("Meta table offset wrong: " + std::to_string(table_entry->Offset), LL_WARNING); + return false; + } + if (table_entry->Offset + table_entry->Length > meta_table_region.Length) + { + Server->Log("Meta table offset+length wrong: " + std::to_string(table_entry->Offset + table_entry->Length), LL_WARNING); + return false; + } + + std::vector entry_buf(table_entry->Length); + + if (file->Read(meta_table_region.FileOffset + table_entry->Offset, + entry_buf.data(), static_cast<_u32>(entry_buf.size())) != entry_buf.size()) + { + Server->Log("Error reading meta table entry. " + os_last_error_str(), LL_WARNING); + return false; + } + + if (equalsGUID(table_entry->ItemId, file_parameters_guid)) + { + if (entry_buf.size() < sizeof(VhdxFileParameters)) + { + Server->Log("VhdxFileParameters entry not large enough", LL_WARNING); + return false; + } + + memcpy(&vhdx_params, entry_buf.data(), sizeof(vhdx_params)); + } + else if (equalsGUID(table_entry->ItemId, virtual_disk_size_guid)) + { + if (entry_buf.size() < sizeof(VhdxVirtualDiskSize)) + { + Server->Log("VhdxVirtualDiskSize entry not large enough", LL_WARNING); + return false; + } + + VhdxVirtualDiskSize* virtual_disk_size = reinterpret_cast(entry_buf.data()); + + dst_size = virtual_disk_size->VirtualDiskSize; + } + else if (equalsGUID(table_entry->ItemId, physical_sector_size_guid)) + { + if (entry_buf.size() < sizeof(VhdxPhysicalDiskSectorSize)) + { + Server->Log("VhdxPhysicalDiskSectorSize entry not large enough", LL_WARNING); + return false; + } + + VhdxPhysicalDiskSectorSize* physical_disk_sector_size = reinterpret_cast(entry_buf.data()); + + physical_sector_size = physical_disk_sector_size->PhysicalSectorSize; + } + else if (equalsGUID(table_entry->ItemId, logical_sector_size_guid)) + { + if (entry_buf.size() < sizeof(VhdxVirtualDiskLogicalSectorSize)) + { + Server->Log("VhdxVirtualDiskLogicalSectorSize entry not large enough", LL_WARNING); + return false; + } + + VhdxVirtualDiskLogicalSectorSize* logical_disk_sector_size = reinterpret_cast(entry_buf.data()); + + sector_size = logical_disk_sector_size->LogicalSectorSize; + } + else if (equalsGUID(table_entry->ItemId, virtual_disk_id_guid)) + { + if (entry_buf.size() < sizeof(VhdxVirtualDiskId)) + { + Server->Log("VhdxVirtualDiskId entry not large enough", LL_WARNING); + return false; + } + + VhdxVirtualDiskId* virtual_disk_id = reinterpret_cast(entry_buf.data()); + } + else if (equalsGUID(table_entry->ItemId, parent_locator_guid)) + { + if (entry_buf.size() < sizeof(VhdxParentLocatorHeader)) + { + Server->Log("Parent locator entry not large enough", LL_WARNING); + return false; + } + + VhdxParentLocatorHeader* parent_locator_header = reinterpret_cast(entry_buf.data()); + + VhdxGUID vhdx_parent_locator_guid; + makeVhdxParentLocatorGUID(vhdx_parent_locator_guid); + + if (!equalsGUID(parent_locator_header->LocatorType, vhdx_parent_locator_guid)) + { + Server->Log("Unknown parent locator type " + strGUID(parent_locator_header->LocatorType), LL_WARNING); + return false; + } + + for (unsigned short i = 0; i < parent_locator_header->KeyValueCount; ++i) + { + VhdxParentLocatorEntry* parent_locator_entry = reinterpret_cast(entry_buf.data() + 20 + i * 12); + + if (parent_locator_entry->KeyOffset + parent_locator_entry->KeyLength >= entry_buf.size() + || parent_locator_entry->KeyOffset>10*1024*1024) + { + Server->Log("Parent locator entry key offset not plausible: "+std::to_string(parent_locator_entry->KeyOffset)+ + " length: "+std::to_string(parent_locator_entry->KeyLength), + LL_WARNING); + return false; + } + + if (parent_locator_entry->ValueOffset + parent_locator_entry->ValueLength >= entry_buf.size() + || parent_locator_entry->ValueOffset > 10 * 1024 * 1024) + { + Server->Log("Parent locator entry key offset not plausible: " + std::to_string(parent_locator_entry->ValueOffset)+ + " length: " + std::to_string(parent_locator_entry->ValueLength), + LL_WARNING); + return false; + } + + std::string key_vw(entry_buf.data() + parent_locator_entry->KeyOffset, parent_locator_entry->KeyLength); + std::string value_vw(entry_buf.data() + parent_locator_entry->ValueOffset, parent_locator_entry->ValueLength); + + std::string key_v = Server->ConvertFromUTF16(key_vw); + std::string value_v = Server->ConvertFromUTF16(value_vw); + + if (key_v == "parent_linkage") + { + if (!parseStrGuid(value_v, parent_linkage_guid)) + { + Server->Log("Error parsing parent linkage GUID " + value_v, LL_WARNING); + return false; + } + } + else if (key_v == "relative_path") + { + rel_parent_path = value_v; + } + else if (key_v == "volume_path") + { + volume_parent_path = value_v; + } + else if (key_v == "absolute_win32_path") + { + absolute_win32_parent_path = value_v; + } + } + } + else if(table_entry->IsRequired) + { + Server->Log("Required table entry " + strGUID(table_entry->ItemId) + " not suppoerted", LL_WARNING); + return false; + } + } + + if (sector_size == 0 || + physical_sector_size == 0 || + vhdx_params.BlockSize == 0 || + dst_size == -1) + { + Server->Log("Missing VHDX parameter. sector_size=" + std::to_string(sector_size) + + " physical_sector_size=" + std::to_string(physical_sector_size) + + " vhdx_params.BlockSize=" + std::to_string(vhdx_params.BlockSize)+ + " dst_size=" + std::to_string(dst_size), LL_WARNING); + return false; + } + + block_size = vhdx_params.BlockSize; + + if (vhdx_params.HasParent) + { + if (isZeroGUID(parent_linkage_guid)) + { + Server->Log("Parent linkage GUID is zero", LL_WARNING); + return false; + } + + if (FileExists(absolute_win32_parent_path)) + { + parent.reset(new VHDXFile(absolute_win32_parent_path, + true, 0)); + } + else if (FileExists(rel_parent_path)) + { + parent.reset(new VHDXFile(rel_parent_path, + true, 0)); + } + + if (parent.get() == nullptr || + !parent->isOpen()) + { + Server->Log("Could not open parent vhdx at \"" + absolute_win32_parent_path + "\" or " + "\"" + rel_parent_path + "\"", LL_WARNING); + return false; + } + + VhdxGUID dwg; + parent->getDataWriteGUID(dwg); + + if (!equalsGUID(dwg, parent_linkage_guid)) + { + Server->Log("Parent linkage GUID differs. Got " + strGUID(dwg) + " expected " + strGUID(parent_linkage_guid), LL_WARNING); + return false; + } + } + + return true; +} + +bool VHDXFile::allocateBatBlockFull(int64 block) +{ + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + + bat_entry->State = PAYLOAD_BLOCK_FULLY_PRESENT; + + int64 new_pos = next_payload_pos.fetch_add(block_size, std::memory_order_relaxed); + + if (new_pos > file->Size()) + { + allocated_size = new_pos + block_size + allocate_size_add_size; + + if (file == backing_file.get() && + !backing_file->Resize(allocated_size, false)) + { + Server->Log("Error resizing backing file to new allocated size " + + std::to_string(allocated_size) + ". " + os_last_error_str(), + LL_WARNING); + return false; + } + } + + assert(new_pos % (1 * 1024 * 1024) == 0); + bat_entry->FileOffsetMB = new_pos / (1 * 1024 * 1024); + bat_entry->Reserved = 0; + + { + std::unique_lock lock(log_mutex); + pending_bat_entries.insert(block); + } + + return true; +} + +void VHDXFile::calcNextPayloadPos() +{ + int64 next_pos = 1 * 1024 * 1024; + + next_pos = (std::max)(next_pos, + static_cast(bat_region.FileOffset + bat_region.Length)); + + next_pos = (std::max)(next_pos, + static_cast(curr_header.LogOffset + curr_header.LogLength)); + + next_pos = (std::max)(next_pos, + static_cast(meta_table_region.FileOffset + meta_table_region.Length)); + + _u32 bat_entries = getBatEntries(dst_size, block_size, sector_size); + + for (_u32 i = 0; i < bat_entries; ++i) + { + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + i; + next_pos = (std::max)(next_pos, + static_cast(bat_entry->FileOffsetMB*1024*1024 + block_size)); + } + + next_payload_pos = next_pos; +} + +bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_threads) +{ + backing_file.reset(Server->openFile(fn, read_only ? MODE_READ : MODE_RW_CREATE)); + + if (backing_file.get() == nullptr) + { + Server->Log("Error opening VHDX backing file at \"" + + fn + "\". " + os_last_error_str(), LL_WARNING); + return false; + } + + if (backing_file->Size() == 0) + { + if (read_only) + { + Server->Log("Read only vhdx file has zero size", LL_WARNING); + return false; + } + + if (compress) + { + compressed_file = std::make_unique(backing_file.get(), + false, read_only, compress_n_threads); + + if (compressed_file->hasError()) + { + Server->Log("Error opening VHDX compressed file -1", LL_WARNING); + return false; + } + + file = compressed_file.get(); + } + else + { + file = backing_file.get(); + } + + return createNew(); + } + else + { + if (check_if_compressed()) + { + compressed_file = std::make_unique(backing_file.get(), + true, read_only, compress_n_threads); + + if (compressed_file->hasError()) + { + Server->Log("Error opening VHDX compressed file -2", LL_WARNING); + return false; + } + + file = compressed_file.get(); + } + else + { + file = backing_file.get(); + } + + if (!readHeader()) + { + Server->Log("Error reading VHDX header", LL_WARNING); + return false; + } + + if (!readRegionTable(192 * 1024) && + !readRegionTable(256 * 1024)) + { + Server->Log("Error reading any VHDX region table", LL_WARNING); + return false; + } + + if (!readBat()) + { + Server->Log("Error reading any VHDX bat", LL_WARNING); + return false; + } + + if (!readMeta()) + { + Server->Log("Error reading any VHDX metadata", LL_WARNING); + return false; + } + + if (read_only && !isZeroGUID(curr_header.LogGuid)) + { + Server->Log("VHDX is opened read only but has log entries", LL_WARNING); + return false; + } + + if (!read_only && !isZeroGUID(curr_header.LogGuid)) + { + if (!replayLog()) + { + Server->Log("Error replaying VHDX log", LL_WARNING); + return false; + } + } + + calcNextPayloadPos(); + + allocated_size = backing_file->Size(); + + secureRandomGuid(curr_header.FileWriteGuid); + + flushed_vhdx_size = allocated_size; + + if (!fast_mode && !updateHeader()) + { + return false; + } + + return true; + } +} + +bool VHDXFile::has_sector_int(int64 spos) +{ + if (spos >= dst_size) + return true; + + _u32 block = getBatEntry(spos, block_size, sector_size); + + VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + + return bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT || + bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT; +} + +VHDXFile::LogSequence VHDXFile::findLogSequence() +{ + LogSequence max_seq; + max_seq.max_sequence = 0; + for (uint64 log_pos = curr_header.LogOffset; + log_pos < curr_header.LogOffset + curr_header.LogLength;) + { + LogSequence seq = findLogSequence(log_pos); + if (seq.max_sequence > max_seq.max_sequence) + max_seq = seq; + } + + return max_seq; +} + +VHDXFile::LogSequence& VHDXFile::validateSequence(LogSequence& seq) +{ + if (seq.entries.empty()) + return seq; + + LogEntry head = readLogEntry(file, curr_header.LogGuid, + static_cast(seq.entries[seq.entries.size() - 1])); + + if (head.sequence_number == -1) + { + seq.entries.clear(); + return seq; + } + + if (curr_header.LogOffset + head.tail_pos != seq.entries[0]) + { + seq.entries.clear(); + return seq; + } + + return seq; +} + +VHDXFile::LogSequence VHDXFile::findLogSequence(uint64& off) +{ + int64 expected_seq = 0; + VHDXFile::LogSequence seq; + + while (true) + { + LogEntry loge = readLogEntry(file, curr_header.LogGuid, static_cast(off)); + + if (loge.sequence_number == -1) + { + off += 4096; + return validateSequence(seq); + } + + if (expected_seq != 0 && expected_seq != loge.sequence_number) + { + return validateSequence(seq); + } + + seq.entries.push_back(off); + seq.max_sequence = loge.sequence_number; + seq.fsize = loge.fsize; + + off += loge.length; + off = (off - curr_header.LogOffset) % curr_header.LogLength + curr_header.LogOffset; + + expected_seq = loge.sequence_number + 1; + } +} + +bool VHDXFile::logWrite(int64 off, const char* buf, size_t bsize, + int64 new_dst_size, bool& full) +{ + if (bsize > 126 * log_sector_size) + { + assert(false); + return false; + } + + assert(bsize % log_sector_size == 0); + + if (isZeroGUID(curr_header.LogGuid)) + { + randomGuid(curr_header.LogGuid); + log_pos = 0; + log_start_pos = 0; + + if (!updateHeader()) + return false; + } + + size_t desc_count = roundUp(bsize, size_t{ log_sector_size }) / log_sector_size; + + std::vector log_entry(log_sector_size + roundUp(bsize, size_t{ log_sector_size } ) ); + + if (log_pos + log_entry.size() > curr_header.LogLength) + { + full = true; + return false; + } + + LogEntryHeader* header = reinterpret_cast(log_entry.data()); + + memcpy(&header->signature, "loge", 4); + + header->Checksum = 0; + header->EntryLength = static_cast<_u32>(log_entry.size()); + header->DescriptorCount = static_cast<_u32>(desc_count); + header->Tail = static_cast<_u32>(log_start_pos); + header->FlushedFileOffset = flushed_vhdx_size; + if (new_dst_size <= 0) + header->LastFileOffset = header->FlushedFileOffset; + else + header->LastFileOffset = new_dst_size; + copyGUID(curr_header.LogGuid, header->LogGuid); + header->SequenceNumber = log_sequence_num; + + ++log_sequence_num; + + for (size_t i = 0; i < desc_count; ++i) + { + LogDataDescriptor* data_desc = reinterpret_cast(log_entry.data() + 64 + i * 32); + + memcpy(&data_desc->signature, "desc", 4); + data_desc->FileOffset = off + i * log_sector_size; + memcpy(data_desc->LeadingBytes, buf + i * log_sector_size, 8); + memcpy(data_desc->TrailingBytes, buf + i * log_sector_size + (log_sector_size - 4), 4); + data_desc->SequenceNumber = header->SequenceNumber; + } + + for (size_t i = 0; i < bsize; i += log_sector_size) + { + LogDataSector* data_sec = reinterpret_cast(log_entry.data() + log_sector_size + i * log_sector_size); + + memcpy(&data_sec->signature, "data", 4); + SSequence seq; + seq.QuadPart = header->SequenceNumber; + data_sec->SequenceLow = seq.LowPart; + data_sec->SequenceHigh = seq.HighPart; + memcpy(data_sec->data, buf + i + 8, log_sector_size - 8 - 4); + } + + header->Checksum = crc32c(reinterpret_cast(log_entry.data()), log_entry.size()); + + if (file->Write(curr_header.LogOffset + log_pos, log_entry.data(), + static_cast<_u32>(log_entry.size())) != log_entry.size()) + { + Server->Log("Error writing VHDX log entry. " + os_last_error_str(), LL_WARNING); + return false; + } + + log_pos += log_entry.size(); + + return true; +} + +char* VHDXFile::getSectorBitmap(_u32 sector_block, uint64 FileOffsetMB) +{ + std::unique_lock lock(sector_bitmap_mutex); + auto it_sector_bitmap = sector_bitmap_bufs.find(sector_block); + if (it_sector_bitmap == sector_bitmap_bufs.end()) + { + lock.unlock(); + + std::vector sector_bitmap_buf(block_size); + + if (file->Read(FileOffsetMB * 1024 * 1024, + sector_bitmap_buf.data(), + static_cast<_u32>(sector_bitmap_buf.size())) != block_size) + { + Server->Log("Reading sector bitmap from mb offset " + std::to_string(FileOffsetMB) + + " failed. " + os_last_error_str(), LL_ERROR); + return nullptr; + } + + lock.lock(); + + if (sector_bitmap_bufs.find(sector_block) == sector_bitmap_bufs.end()) + { + sector_bitmap_bufs[sector_block] = sector_bitmap_buf; + } + + it_sector_bitmap = sector_bitmap_bufs.find(sector_block); + + lock.unlock(); + } + + return it_sector_bitmap->second.data(); +} + +char* VHDXFile::addZeroBitmap(_u32 sector_block) +{ + std::unique_lock lock(sector_bitmap_mutex); + auto it_sector_bitmap = sector_bitmap_bufs.find(sector_block); + if (it_sector_bitmap == sector_bitmap_bufs.end()) + { + std::vector sector_bitmap_buf(block_size); + return sector_bitmap_bufs.insert(std::make_pair(sector_block, sector_bitmap_buf)).first->second.data(); + } + + return it_sector_bitmap->second.data(); +} + +bool VHDXFile::isSectorSet(int64 spos, bool& set) +{ + _u32 sector_block = getSectorBitmapEntry(spos, block_size, sector_size); + VhdxBatEntry* sector_bat_entry = reinterpret_cast(bat_buf.data()) + sector_block; + + if (sector_bat_entry->State != PAYLOAD_BLOCK_FULLY_PRESENT) + { + Server->Log("Sector bitmap " + std::to_string(sector_block) + " not fully present", LL_WARNING); + return false; + } + + char* sector_bitmap = getSectorBitmap(sector_block, sector_bat_entry->FileOffsetMB); + + if (sector_bitmap == nullptr) + { + Server->Log("Error reading sector bitmap of sector block " + std::to_string(sector_block), LL_ERROR); + return false; + } + + set = isSectorSetInt(sector_bitmap, spos, block_size, sector_size); + + return true; +} + +bool VHDXFile::setSector(int64 spos) +{ + return setSector(spos, spos + sector_size); +} + +bool VHDXFile::setSector(int64 start, int64 end) +{ + _u32 sector_block = getSectorBitmapEntry(start, block_size, sector_size); + + VhdxBatEntry* sector_bat_entry = reinterpret_cast(bat_buf.data()) + sector_block; + + if (sector_bat_entry->State != PAYLOAD_BLOCK_FULLY_PRESENT && + sector_bat_entry->State != PAYLOAD_BLOCK_NOT_PRESENT) + { + Server->Log("Sector bitmap " + std::to_string(sector_block) + " wrong state " + +std::to_string(sector_bat_entry->State), LL_WARNING); + return false; + } + + char* sector_bitmap; + if (sector_bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) + { + if (!allocateBatBlockFull(sector_block)) + return false; + + sector_bitmap = addZeroBitmap(sector_block); + } + else + { + sector_bitmap = getSectorBitmap(sector_block, sector_bat_entry->FileOffsetMB); + } + + if (sector_bitmap == nullptr) + return false; + + setSectorInt(sector_bitmap, start, end, block_size, sector_size); + + std::lock_guard lock(pending_sector_bitmaps_mutex); + + pending_sector_bitmaps.insert(sector_block); + + return true; +} + +bool VHDXFile::check_if_compressed() +{ + const char header_magic[] = "URBACKUP COMPRESSED FILE"; + std::string magic = backing_file->Read(0LL, sizeof(header_magic) - 1); + + return magic == std::string(header_magic); +} + +bool VHDXFile::has_block(bool use_parent) +{ + if (!has_sector_int(spos)) + { + if (use_parent && parent.get() != nullptr) + return parent->has_block(true); + + return false; + } + + return true; +} diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h new file mode 100644 index 000000000..e66540c84 --- /dev/null +++ b/fsimageplugin/vhdxfile.h @@ -0,0 +1,182 @@ +#pragma once + +#include "../Interface/Server.h" +#include "../Interface/File.h" +#include "IVHDFile.h" + +#include +#include +#include +#include + +class CompressedFile; + +typedef char VhdxGUID[16]; + +#pragma pack(1) +struct VhdxHeader +{ + _u32 Signature; + _u32 Checksum; + uint64 SequenceNumber; + VhdxGUID FileWriteGuid; + VhdxGUID DataWriteGuid; + VhdxGUID LogGuid; + unsigned short LogVersion; + unsigned short Version; + _u32 LogLength; + uint64 LogOffset; + char Reserved[4016]; +}; + +struct VhdxRegionTableEntry +{ + VhdxGUID Guid; + uint64 FileOffset; + _u32 Length; + _u32 Required : 1; + _u32 Reserved : 31; +}; + +struct VhdxFileParameters +{ + _u32 BlockSize; + _u32 LeaveBlocksAllocated : 1; + _u32 HasParent : 1; + _u32 Reserved : 30; +}; + +struct VhdxBatEntry +{ + uint64 State : 3; + uint64 Reserved : 17; + uint64 FileOffsetMB : 44; +}; +#pragma pack() + +class VHDXFile : public IVHDFile, public IFile +{ +public: + + VHDXFile(const std::string& fn, bool pRead_only, uint64 pDstsize, unsigned int pBlocksize = 2 * 1024 * 1024, bool fast_mode = false, bool compress = false, size_t compress_n_threads = 0); + VHDXFile(const std::string& fn, const std::string& parent_fn, bool pRead_only, bool fast_mode = false, bool compress = false, uint64 pDstsize = 0, size_t compress_n_threads = 0); + ~VHDXFile(); + + virtual bool Seek(_i64 offset) override; + virtual bool Read(char* buffer, size_t bsize, size_t& read) override; + virtual _u32 Write(const char* buffer, _u32 bsize, bool* has_error = NULL) override; + virtual bool isOpen(void) override; + virtual uint64 getSize(void) override; + virtual uint64 usedSize(void) override; + virtual std::string getFilename(void) override; + virtual bool has_sector(_i64 sector_size = -1) override; + virtual bool this_has_sector(_i64 sector_size = -1) override; + virtual unsigned int getBlocksize() override; + virtual bool finish() override; + virtual bool trimUnused(_i64 fs_offset, _i64 trim_blocksize, ITrimCallback* trim_callback) override; + virtual bool syncBitmap(_i64 fs_offset) override; + virtual bool makeFull(_i64 fs_offset, IVHDWriteCallback* write_callback) override; + virtual bool setUnused(_i64 unused_start, _i64 unused_end) override; + virtual bool setBackingFileSize(_i64 fsize) override; + + virtual std::string Read(_u32 tr, bool* has_error = NULL) override; + virtual std::string Read(int64 spos, _u32 tr, bool* has_error = NULL) override; + virtual _u32 Read(char* buffer, _u32 bsize, bool* has_error = NULL) override; + virtual _u32 Read(int64 spos, char* buffer, _u32 bsize, bool* has_error = NULL) override; + virtual _u32 Write(const std::string& tw, bool* has_error = NULL) override; + virtual _u32 Write(int64 spos, const std::string& tw, bool* has_error = NULL) override; + virtual _u32 Write(int64 spos, const char* buffer, _u32 bsiz, bool* has_error = NULL) override; + virtual _i64 Size(void) override; + virtual _i64 RealSize() override; + virtual bool PunchHole(_i64 spos, _i64 size) override; + virtual bool Sync() override; + + void getDataWriteGUID(VhdxGUID& g); + +private: + bool createNew(); + bool updateHeader(); + bool replayLog(); + bool readHeader(); + bool readRegionTable(int64 off); + bool readBat(); + bool readMeta(); + bool allocateBatBlockFull(int64 block); + void calcNextPayloadPos(); + bool open(const std::string& fn, bool compress, size_t compress_n_threads); + bool syncInt(bool full); + + bool has_sector_int(int64 spos); + + struct LogSequence + { + std::vector entries; + int64 max_sequence = -1; + int64 fsize = -1; + int64 new_fsize = -1; + }; + + LogSequence findLogSequence(); + LogSequence& validateSequence(LogSequence& seq); + LogSequence findLogSequence(uint64& off); + + bool logWrite(int64 off, const char* buf, size_t bsize, int64 new_dst_size, bool& full); + + char* getSectorBitmap(_u32 sector_block, uint64 FileOffsetMB); + char* addZeroBitmap(_u32 sector_block); + + bool isSectorSet(int64 spos, bool& set); + bool setSector(int64 spos); + bool setSector(int64 start, int64 end); + + bool check_if_compressed(); + + bool has_block(bool use_parent); + + VhdxHeader curr_header; + int64 curr_header_pos; + + VhdxRegionTableEntry meta_table_region; + VhdxRegionTableEntry bat_region; + + VhdxFileParameters vhdx_params; + + std::vector bat_buf; + std::set pending_bat_entries; + + std::unique_ptr backing_file; + IFile* file; + std::unique_ptr compressed_file; + int64 allocated_size; + bool is_open = false; + int64 dst_size; + int64 flushed_vhdx_size; + bool data_write_uuid_updated = false; + + std::mutex log_mutex; + + _u32 sector_size; + _u32 physical_sector_size; + _u32 block_size; + + std::atomic next_payload_pos; + + int64 log_start_pos; + int64 log_pos; + int64 log_sequence_num = 1; + + int64 spos = 0; + + bool fast_mode; + bool read_only; + bool finished = false; + + std::unique_ptr parent; + std::string parent_fn; + + std::mutex sector_bitmap_mutex; + std::map<_u32, std::vector > sector_bitmap_bufs; + + std::mutex pending_sector_bitmaps_mutex; + std::set<_u32> pending_sector_bitmaps; +}; diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index e26cce06a..d822a2f06 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -1,6 +1,6 @@ /************************************************************************* * UrBackup - Client/Server backup system -* Copyright (C) 2011-2016 Martin Raiber +* Copyright (C) 2011-2021 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 @@ -352,6 +352,11 @@ void ClientMain::operator ()(void) { curr_image_version = 0; } + else if (server_settings->getImageFileFormat() == image_file_format_vhdx + || server_settings->getImageFileFormat() == image_file_format_vhdxz) + { + curr_image_version = 2; + } else { curr_image_version = 1; @@ -676,6 +681,11 @@ void ClientMain::operator ()(void) { curr_image_version = 0; } + else if (server_settings->getImageFileFormat() == image_file_format_vhdxz + || server_settings->getImageFileFormat() == image_file_format_vhdx) + { + curr_image_version = 2; + } else { curr_image_version = 1; diff --git a/urbackupserver/ImageBackup.cpp b/urbackupserver/ImageBackup.cpp index 59859596c..c9fb12111 100644 --- a/urbackupserver/ImageBackup.cpp +++ b/urbackupserver/ImageBackup.cpp @@ -1,6 +1,6 @@ /************************************************************************* * UrBackup - Client/Server backup system -* Copyright (C) 2011-2016 Martin Raiber +* Copyright (C) 2011-2021 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 @@ -1053,11 +1053,28 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent { image_format = IFSImageFactory::ImageFormat_RawCowFile; } + else if (image_file_format == image_file_format_vhdx) + { + image_format = IFSImageFactory::ImageFormat_VHDX; + } + else if (image_file_format == image_file_format_vhdxz) + { + image_format = IFSImageFactory::ImageFormat_CompressedVHDX; + } else //default { image_format = IFSImageFactory::ImageFormat_CompressedVHD; } + if ((image_format == IFSImageFactory::ImageFormat_VHDX || + image_format == IFSImageFactory::ImageFormat_CompressedVHDX) && + drivesize + mbr_size > 64LL * 1024 * 1024 * 1024 * 1024) + { + ServerLogger::Log(logid, "Volume is too large for VHDX files with " + PrettyPrintBytes(drivesize + mbr_size) + + ". VHDX files have a maximum size of 64TiB. Please use another image file format.", LL_ERROR); + goto do_image_cleanup; + } + if(!has_parent) { r_vhdfile=image_fak->createVHDFile(os_file_prefix(imagefn), false, drivesize+mbr_size, @@ -1159,7 +1176,8 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent } if (vhd_size>0 && vhd_size >= 2040LL * 1024 * 1024 * 1024 - && image_file_format != image_file_format_cowraw) + && (image_file_format == image_file_format_vhd + || image_file_format == image_file_format_vhdz) ) { ServerLogger::Log(logid, "Data on volume is too large for VHD files with " + PrettyPrintBytes(vhd_size) + ". VHD files have a maximum size of 2040GB. Please use another image file format.", LL_ERROR); @@ -2107,6 +2125,14 @@ std::string ImageBackup::constructImagePath(const std::string &letter, std::stri { imgpath+=".vhd"; } + else if (image_file_format == image_file_format_vhdx) + { + imgpath += ".vhdx"; + } + else if (image_file_format == image_file_format_vhdxz) + { + imgpath += ".vhdxz"; + } else if(image_file_format==image_file_format_cowraw) { imgpath+=".raw"; diff --git a/urbackupserver/server_settings.h b/urbackupserver/server_settings.h index a16e945c2..c173b3bf2 100644 --- a/urbackupserver/server_settings.h +++ b/urbackupserver/server_settings.h @@ -12,6 +12,8 @@ namespace const char* image_file_format_vhd = "vhd"; const char* image_file_format_vhdz = "vhdz"; const char* image_file_format_cowraw = "cowraw"; + const char* image_file_format_vhdx = "vhdx"; + const char* image_file_format_vhdxz = "vhdxz"; const char* full_image_style_full = "full"; const char* full_image_style_synthetic = "synthetic"; diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 912a278c4..74f3ae444 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,76 +1,76 @@ -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
     

    ").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



    ").f(ctx.get(["tAlert script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tPreparing restore. Please be patient..."], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAccess denied"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong or you do not have the required rights to access this file or folder."], false),ctx,"h").x(ctx.get(["errcode"], false),ctx,{"block":body_1},{}).w("

    ").f(ctx.get(["tLogin with username and password"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("(").f(ctx.get(["errcode"], false),ctx,"h").w(")");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClients"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tError while accessing backups"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong:"], false),ctx,"h").w(" ").f(ctx.get(["err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); -(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.w("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanging password failed:"], false),ctx,"h").w("
    ").f(ctx.get(["fail_reason"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").f(ctx.get(["internet_server"], false),ctx,"h").w(" -k internet_server_port -v ").f(ctx.get(["internet_server_port"], false),ctx,"h").w(" -k computername -v \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" -k internet_authkey -v ").f(ctx.get(["new_authkey"], false),ctx,"h").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the internet server to:"], false),ctx,"h").w(" ").f(ctx.get(["internet_server"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the internet server port to:"], false),ctx,"h").w(" ").f(ctx.get(["internet_server_port"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the computer name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").f(ctx.get(["internet_server"], false),ctx,"h").w(" -k internet_server_port -v ").f(ctx.get(["internet_server_port"], false),ctx,"h").w(" -k computername -v \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" -k internet_authkey -v ").f(ctx.get(["new_authkey"], false),ctx,"h").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["database_error_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["generic_text"], false),ctx,{"block":body_1},{}).f(ctx.get(["ext_text"], false),ctx,"h",["s"]).x(ctx.get(["stop_show_key"], false),ctx,{"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["dir_error_text"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_2.__dustBody=!0;return body_0;})(); -(function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["creating_filesindex_text"], false),ctx,"h").w("
    ").f(ctx.get(["tNumber of file entries processed"], false),ctx,"h").w(": ").f(ctx.get(["processed_file_entries"], false),ctx,"h").w("
    ").f(ctx.get(["tPercent finished"], false),ctx,"h").w(": ").f(ctx.get(["percent_finished"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

    ").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["id"], false),ctx,"h").w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["is_image"], false),ctx,{"else":body_1,"block":body_4},{}).w("").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["duration"], false),ctx,"h").w("").f(ctx.get(["size"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("-");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("Path: ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("Volume: ").f(ctx.get(["details"], false),ctx,"h");}body_4.__dustBody=!0;return body_0;})(); -(function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLast activities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tID"], false),ctx,"h").w("").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tStarting time"], false),ctx,"h").w("").f(ctx.get(["tRequired time"], false),ctx,"h").w("").f(ctx.get(["tUsed Storage"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLog"], false),ctx,"h").w(": (").f(ctx.get(["name"], false),ctx,"h").w(")
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tLevel"], false),ctx,"h").w("").f(ctx.get(["tTime"], false),ctx,"h").w("").f(ctx.get(["tMessage"], false),ctx,"h").w("

    ").f(ctx.get(["tBack"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["level"], false),ctx,"h").w("
    ").f(ctx.get(["time"], false),ctx,"h").w("
    ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("login",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("logs_report_mail",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["report_single_mail"], false),ctx,"h").w(" -");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLogs"], false),ctx,"h").w("
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tErrors"], false),ctx,"h").w("").f(ctx.get(["tWarnings"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("
    ").f(ctx.get(["tLive Log"], false),ctx,"h").w("
    ").f(ctx.get(["tReports"], false),ctx,"h").w("
    ").x(ctx.get(["has_user"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tYou need to create a user to be able to send reports"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

     
    +
    ").x(ctx.get(["can_report_script_edit"], false),ctx,{"block":body_3},{}).w("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("

    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThere is a new version of UrBackup server available"], false),ctx,"h").w(" (").f(ctx.get(["new_version_number"], false),ctx,"h").w("). Download it here.
    ").f(ctx.get(["tOk. Stop showing this."], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["time"], false),ctx,"h").w("").f(ctx.get(["errors"], false),ctx,"h").w("
    ").f(ctx.get(["warnings"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["nospc_fatal_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("

    ").f(ctx.get(["tReport script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); -(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tETA"], false),ctx,"h").w("").f(ctx.get(["tSpeed"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["ONLY_WIN32_BEGIN"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["ONLY_WIN32_END"], false),ctx,"h",["s"]).w("
    MBit/s
     
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest Mail sent successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSaved settings successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["msg"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["rights"], false),ctx,"h").w("").x(ctx.get(["can_change"], false),ctx,{"block":body_1},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w(" ");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo Users"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["tBackup Statistics"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_2},{}).w("\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["tAll"], false),ctx,"h").w("
    ").f(ctx.get(["tSum"], false),ctx,"h").w("
    ").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["images_total"], false),ctx,"h").w("
    ").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["files_total"], false),ctx,"h").w("
    ").f(ctx.get(["tAll"], false),ctx,"h").w("").f(ctx.get(["used_total"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_3},{}).w("
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_4},{}).w("
    ").f(ctx.get(["tStorage allocation"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_5},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ");}body_5.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tDownload client for Windows"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["tDownload client for Mac OS X"], false),ctx,"h");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tDownload client for Linux"], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["hostname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSelect all"], false),ctx,"h").w("").f(ctx.get(["tSelect none"], false),ctx,"h").w("").f(ctx.get(["rem_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["tRemove selected"], false),ctx,"h").w("").f(ctx.get(["rem_stop"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["groupname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w(" ").x(ctx.get(["online_add_status"], false),ctx,{"block":body_2},{}).w(" ").x(ctx.get(["reset_client_uid"], false),ctx,{"block":body_3},{}).w("").f(ctx.get(["status"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastseen"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h").f(ctx.get(["start_file_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastbackup_image"], false),ctx,"h").f(ctx.get(["start_image_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["file_ok_t"], false),ctx,"h").w("").f(ctx.get(["image_ok_t"], false),ctx,"h").w("").f(ctx.get(["ip"], false),ctx,"h").w("").f(ctx.get(["client_version_string"], false),ctx,"h").w("").f(ctx.get(["os_version_string"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("(").f(ctx.get(["status"], false),ctx,"h",["s"]).w(")");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tAllow new client"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tmpdir_error_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_2},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_3},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("min-width: 2em;");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["upgrade_error_text"], false),ctx,"h").w("
    ").f(ctx.get(["tCurrent version"], false),ctx,"h").w(": ").f(ctx.get(["curr_db_version"], false),ctx,"h").w("
    ").f(ctx.get(["tTarget version"], false),ctx,"h").w(": ").f(ctx.get(["target_db_version"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.w("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").f(ctx.get(["virus_error_path"], false),ctx,"h").w(" ).").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAbout UrBackup"]),ctx,"h").write("
    UrBackup Server ").reference(ctx._get(false, ["version"]),ctx,"h").write("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}return body_0;})(); +(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAdd client"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDownload the client from:"]),ctx,"h").write(" www.urbackup.org

    ").reference(ctx._get(false, ["tIf you want a client to use multiple backup servers this server's identity is:"]),ctx,"h").write(" ").reference(ctx._get(false, ["server_identity"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tFor security reasons check/add following line in the file server_idents.txt on your client:"]),ctx,"h").write("

    ").reference(ctx._get(false, ["server_pubkey"]),ctx,"h",["s"]).write("



    ");}return body_0;})(); +(function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tEdit alert scripts"]),ctx,"h").write("
     

    ").reference(ctx._get(false, ["tAlert script parameters"]),ctx,"h").write("

    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("



    ").reference(ctx._get(false, ["tAlert script"]),ctx,"h").write("

    \t\t

    ").exists(ctx._get(false, ["saved_ok"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("
    Saved script successfully.
    ");}return body_0;})(); +(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tName:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLabel:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDefault value:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tType:"]),ctx,"h").write("
     
    ");}return body_0;})(); +(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write(" > ").reference(ctx._get(false, ["cpath"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_2},null).write("").section(ctx._get(false, ["items"]),ctx,{"block":body_3},null).write("
     ").reference(ctx._get(false, ["tFile"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tCreated"]),ctx,"h").write("").reference(ctx._get(false, ["tLast modified"]),ctx,"h").write("").reference(ctx._get(false, ["tLast accessed"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tVersion"]),ctx,"h").write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["size"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["creat"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["mod"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["access"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["backuptime"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_version"]),ctx,{"block":body_4},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_5},null).write("");}function body_4(chk,ctx){return chk.write("").reference(ctx._get(false, ["version"]),ctx,"h").write("");}function body_5(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAccess denied"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong or you do not have the required rights to access this file or folder."]),ctx,"h").exists(ctx._get(false, ["errcode"]),ctx,{"block":body_1},null).write("

    ").reference(ctx._get(false, ["tLogin with username and password"]),ctx,"h").write("

    ");}function body_1(chk,ctx){return chk.write("(").reference(ctx._get(false, ["errcode"]),ctx,"h").write(")");}return body_0;})(); +(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").exists(ctx._get(false, ["backups"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["backup_images"]),ctx,{"block":body_11},null).notexists(ctx._get(false, ["backups"]),ctx,{"block":body_20},null).write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tFile backups"]),ctx,"h").write("

    ").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_3},null).write("").section(ctx._get(false, ["backups"]),ctx,{"block":body_4},null).write("
     ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tArchived"]),ctx,"h").write("?
    ");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("");}function body_4(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["incr"]),ctx,"h").write("").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_5},null).write("");}function body_5(chk,ctx){return chk.write("").notexists(ctx._get(false, ["is_archived"]),ctx,{"block":body_6},null).write("");}function body_6(chk,ctx){return chk.notexists(ctx._get(false, ["disable_delete"]),ctx,{"block":body_7},null);}function body_7(chk,ctx){return chk.exists(ctx._get(false, ["can_delete"]),ctx,{"block":body_8},null);}function body_8(chk,ctx){return chk.exists(ctx._get(false, ["delete_pending"]),ctx,{"else":body_9,"block":body_10},null);}function body_9(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["tDelete"]),ctx,"h").write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tBackup is marked for deletion. Do not delete"]),ctx,"h").write(" ").reference(ctx._get(false, ["tDelete now"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tImage backups"]),ctx,"h").write("

    \t\t\t\t").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_12},null).write("").section(ctx._get(false, ["backup_images"]),ctx,{"block":body_13},null).write("
     ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume"]),ctx,"h").write("").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tArchived"]),ctx,"h").write("?
    ");}function body_12(chk,ctx){return chk.write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("");}function body_13(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["letter"]),ctx,"h").write("").reference(ctx._get(false, ["incr"]),ctx,"h").write("").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_14},null).write("");}function body_14(chk,ctx){return chk.write("").notexists(ctx._get(false, ["is_archived"]),ctx,{"block":body_15},null).write("");}function body_15(chk,ctx){return chk.notexists(ctx._get(false, ["disable_delete"]),ctx,{"block":body_16},null);}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_delete"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.exists(ctx._get(false, ["delete_pending"]),ctx,{"else":body_18,"block":body_19},null);}function body_18(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["tDelete"]),ctx,"h").write("");}function body_19(chk,ctx){return chk.write("").reference(ctx._get(false, ["tBackup is marked for deletion. Do not delete"]),ctx,"h").write(" ").reference(ctx._get(false, ["tDelete now"]),ctx,"h").write("");}function body_20(chk,ctx){return chk.notexists(ctx._get(false, ["backup_images"]),ctx,{"block":body_21},null);}function body_21(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tNo backups"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tNo backups of this client yet"]),ctx,"h");}return body_0;})(); +(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClients"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h",["s"]).write("");}return body_0;})(); +(function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tError while accessing backups"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong:"]),ctx,"h").write(" ").reference(ctx._get(false, ["err"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write(" > ").reference(ctx._get(false, ["cpath"]),ctx,"h",["s"]).write("
    ").section(ctx._get(false, ["image_backup_info"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["can_mount"]),ctx,{"else":body_4,"block":body_11},null).exists(ctx._get(false, ["download_zip"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_14},null).write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tImage backup information"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tId"]),ctx,"h").write(": ").reference(ctx._get(false, ["id"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write(": ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write(": ").reference(ctx._get(false, ["incr"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSize"]),ctx,"h").write(": ").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tVolume"]),ctx,"h").write(": ").reference(ctx._get(false, ["letter"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tArchived"]),ctx,"h").write(": ").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tVolume size"]),ctx,"h").write(": ").reference(ctx._get(false, ["volume_size"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPartition style"]),ctx,"h").write(": ").reference(ctx._get(false, ["part_table"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDisk number"]),ctx,"h").write(": ").reference(ctx._get(false, ["disk_number"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPartition number"]),ctx,"h").write(": ").reference(ctx._get(false, ["partition_number"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tFile system type"]),ctx,"h").write(": ").reference(ctx._get(false, ["fs_type"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tVolume name"]),ctx,"h").write(": ").reference(ctx._get(false, ["volume_name"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSerial number"]),ctx,"h").write(": ").reference(ctx._get(false, ["serial_number"]),ctx,"h").write("
    ").exists(ctx._get(false, ["linux_image_restore"]),ctx,{"block":body_3},null).write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tRestore Linux image"]),ctx,"h").write("");}function body_4(chk,ctx){return chk.notexists(ctx._get(false, ["no_files"]),ctx,{"block":body_5},null);}function body_5(chk,ctx){return chk.exists(ctx._get(false, ["mount_failed"]),ctx,{"else":body_6,"block":body_10},null);}function body_6(chk,ctx){return chk.write("").section(ctx._get(false, ["files"]),ctx,{"block":body_7},null).write("
     ").reference(ctx._get(false, ["tFile"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tCreated"]),ctx,"h").write("").reference(ctx._get(false, ["tLast modified"]),ctx,"h").write("").reference(ctx._get(false, ["tLast accessed"]),ctx,"h").write(" 
    ");}function body_7(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["size"]),ctx,"h").write("").reference(ctx._get(false, ["creat"]),ctx,"h").write("").reference(ctx._get(false, ["mod"]),ctx,"h").write("").reference(ctx._get(false, ["access"]),ctx,"h").write("").exists(ctx._get(false, ["list_items"]),ctx,{"block":body_8},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_9},null).write("");}function body_8(chk,ctx){return chk.write("").reference(ctx._get(false, ["tList"]),ctx,"h").write("");}function body_9(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore"]),ctx,"h").write("");}function body_10(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMounting image failed. Please see server log file for details."]),ctx,"h").write("
    ").reference(ctx._get(false, ["mount_errmsg"]),ctx,"h").write("
    ");}function body_11(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tMount image"]),ctx,"h").write("").exists(ctx._get(false, ["os_mount"]),ctx,{"block":body_12},null).write("
    ");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."]),ctx,"h").write("");}function body_13(chk,ctx){return chk.write("").reference(ctx._get(false, ["tDownload folder as ZIP"]),ctx,"h").write("");}function body_14(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore folder to client"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.write("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}return body_0;})(); +(function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange password"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient added successfully"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tAdded new client with name:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDefault authentication key:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("

    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Windows"]),ctx,"h").write("
    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Linux"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tInstall it directly in the terminal via:"]),ctx,"h").write("

      TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

      ").reference(ctx._get(false, ["tWith Docker (web interface accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").reference(ctx._get(false, ["tWith Docker (web interface not accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").reference(ctx._get(false, ["tAlternatively after you installed the client from:"]),ctx,"h").write(" https://www.urbackup.org/download.html

      • ").reference(ctx._get(false, ["tGo to the settings screen on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tEnable the internet mode on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server port to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the computer name to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the authentication key to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tWith the command line:"]),ctx,"h").write("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}return body_0;})(); +(function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanging password failed:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["fail_reason"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["generic_text"]),ctx,{"block":body_1},null).reference(ctx._get(false, ["ext_text"]),ctx,"h",["s"]).exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["dir_error_text"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["database_error_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["creating_filesindex_text"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tNumber of file entries processed"]),ctx,"h").write(": ").reference(ctx._get(false, ["processed_file_entries"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPercent finished"]),ctx,"h").write(": ").reference(ctx._get(false, ["percent_finished"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThis server has discovered clients which are currently not configured to use this server."]),ctx,"h").write(" ").reference(ctx._get(false, ["tSee here for details on how this can happen."]),ctx,"h").write("

    ").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null);}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tOk. Dismiss this hint."]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanged password successfully"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["id"]),ctx,"h").write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["is_image"]),ctx,{"else":body_1,"block":body_4},null).write("").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["duration"]),ctx,"h").write("").reference(ctx._get(false, ["size"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("-");}function body_3(chk,ctx){return chk.write("Path: ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_4(chk,ctx){return chk.write("Volume: ").reference(ctx._get(false, ["details"]),ctx,"h");}return body_0;})(); +(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tUrBackup live log"]),ctx,"h").write(": ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
     
    ");}function body_1(chk,ctx){return chk.write("g.logid=").reference(ctx._get(false, ["logid"]),ctx,"h").write(";");}return body_0;})(); +(function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLast activities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tID"]),ctx,"h").write("").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tStarting time"]),ctx,"h").write("").reference(ctx._get(false, ["tRequired time"]),ctx,"h").write("").reference(ctx._get(false, ["tUsed Storage"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["time"]),ctx,"h").write("  ").reference(ctx._get(false, ["loglevel"]),ctx,"h").write("  ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); +(function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLog"]),ctx,"h").write(": (").reference(ctx._get(false, ["name"]),ctx,"h").write(")
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tLevel"]),ctx,"h").write("").reference(ctx._get(false, ["tTime"]),ctx,"h").write("").reference(ctx._get(false, ["tMessage"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tBack"]),ctx,"h").write("

    ");}return body_0;})(); +(function(){dust.register("login",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); +(function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tPreparing restore. Please be patient..."]),ctx,"h").write("
     
    ");}return body_0;})(); +(function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); +(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["time"]),ctx,"h").write("").reference(ctx._get(false, ["errors"]),ctx,"h").write("
    ").reference(ctx._get(false, ["warnings"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); +(function(){dust.register("logs_report_mail",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["report_single_mail"]),ctx,"h").write(" -");}return body_0;})(); +(function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["level"]),ctx,"h").write("
    ").reference(ctx._get(false, ["time"]),ctx,"h").write("
    ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); +(function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); +(function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo entries for this filter"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThere is a new version of UrBackup server available"]),ctx,"h").write(" (").reference(ctx._get(false, ["new_version_number"]),ctx,"h").write("). Download it here.
    ").reference(ctx._get(false, ["tOk. Stop showing this."]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo entries for this filter"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["image"]),ctx,{"else":body_1,"block":body_6},null).exists(ctx._get(false, ["show_details"]),ctx,{"block":body_7},null).exists(ctx._get(false, ["backups_interrupted"]),ctx,{"block":body_8},null).write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_10},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_11},null).write("
    ").exists(ctx._get(false, ["f_total_bytes"]),ctx,{"block":body_12},null).write("").reference(ctx._get(false, ["eta"]),ctx,"h").write("").exists(ctx._get(false, ["paused"]),ctx,{"else":body_13,"block":body_14},null).write("").reference(ctx._get(false, ["queue"]),ctx,"h").write("").exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_16},null).exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_18},null).write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["client_update"]),ctx,{"else":body_2,"block":body_5},null);}function body_2(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_3,"block":body_4},null);}function body_3(chk,ctx){return chk.write("-");}function body_4(chk,ctx){return chk.reference(ctx._get(false, ["tPath:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_5(chk,ctx){return chk.reference(ctx._get(false, ["tTo version:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["tVolume:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_7(chk,ctx){return chk.reference(ctx._get(false, ["details"]),ctx,"h");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackups interrupted"]),ctx,"h");}function body_9(chk,ctx){return chk.write("min-width: 2em;");}function body_10(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_11(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["f_done_bytes"]),ctx,"h").write(" / ").reference(ctx._get(false, ["f_total_bytes"]),ctx,"h").write("
    ");}function body_13(chk,ctx){return chk.reference(ctx._get(false, ["speed"]),ctx,"h");}function body_14(chk,ctx){return chk.reference(ctx._get(false, ["tPaused"]),ctx,"h");}function body_15(chk,ctx){return chk.write("");}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.write(" ");}function body_18(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tRestore Linux image"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tTo restore your Linux disk please enter following in a terminal:"]),ctx,"h").write("

    TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_restore_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}return body_0;})(); +(function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["nospc_fatal_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tReport script"]),ctx,"h").write("

    \t\t

    ").exists(ctx._get(false, ["saved_ok"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("
    Saved script successfully.
    ");}return body_0;})(); +(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["nospc_stalled_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_every"]),ctx,"h").write("").reference(ctx._get(false, ["archive_for"]),ctx,"h").write("").reference(ctx._get(false, ["archive_window"]),ctx,"h").write("").reference(ctx._get(false, ["archive_backup_type_str"]),ctx,"h").write("").reference(ctx._get(false, ["archive_letters_str"]),ctx,"h").write("").exists(ctx._get(false, ["show_archive_timeleft"]),ctx,{"block":body_1},null).write("").exists(ctx._get(false, ["source_group"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["source_here"]),ctx,{"block":body_3},null).write("");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_timeleft"]),ctx,"h").write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("disabled");}return body_0;})(); +(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tGroup"]),ctx,"h").write(" ").reference(ctx._get(false, ["groupname"]),ctx,"h").write("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("
    ");}function body_1(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); +(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ").reference(ctx._get(false, ["tNo activities"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest Mail sent successfully"]),ctx,"h").write(".
    ");}return body_0;})(); +(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSending test mail failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["mail_err"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSaved settings successfully"]),ctx,"h").write(".
    ");}return body_0;})(); +(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tClient"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("

    ").exists(ctx._get(false, ["groupmod"]),ctx,{"block":body_1},null).write("
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}function body_1(chk,ctx){return chk.write("
    Member of group
    ");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["tPermissions"]),ctx,"h").write("
  • ");}return body_0;})(); +(function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["msg"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange password for user"]),ctx,"h").write(": ").reference(ctx._get(false, ["username"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange rights for user"]),ctx,"h").write(": ").reference(ctx._get(false, ["username"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tDomain"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tTranslation"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tNew domain"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.write("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").exists(ctx._get(false, ["test_login"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["test_login_ok"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_err"]),ctx,"h").write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login succeeded. Rights of user:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_rights"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["rights"]),ctx,"h").write("").exists(ctx._get(false, ["can_change"]),ctx,{"block":body_1},null).write("");}function body_1(chk,ctx){return chk.write(" ");}return body_0;})(); +(function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLogs"]),ctx,"h").write("
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tErrors"]),ctx,"h").write("").reference(ctx._get(false, ["tWarnings"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLive Log"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tReports"]),ctx,"h").write("
    ").exists(ctx._get(false, ["has_user"]),ctx,{"else":body_1,"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tYou need to create a user to be able to send reports"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

     
    +
    ").exists(ctx._get(false, ["can_report_script_edit"]),ctx,{"block":body_3},null).write("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}function body_3(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tUsername"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["ONLY_WIN32_BEGIN"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["ONLY_WIN32_END"]),ctx,"h",["s"]).write("
    MBit/s
     
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}return body_0;})(); +(function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_1},null).write("
    ").reference(ctx._get(false, ["tBackup Statistics"]),ctx,"h").write("
    ").notexists(ctx._get(false, ["maximized"]),ctx,{"block":body_2},null).write("\t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tImages"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles"]),ctx,"h").write("").reference(ctx._get(false, ["tAll"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSum"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tImages"]),ctx,"h").write("").reference(ctx._get(false, ["images_total"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tFiles"]),ctx,"h").write("").reference(ctx._get(false, ["files_total"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tAll"]),ctx,"h").write("").reference(ctx._get(false, ["used_total"]),ctx,"h").write("
    ").notexists(ctx._get(false, ["maximized"]),ctx,{"block":body_3},null).write("
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_4},null).write("
    ").reference(ctx._get(false, ["tStorage allocation"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_5},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("
    ");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ");}return body_0;})(); +(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tETA"]),ctx,"h").write("").reference(ctx._get(false, ["tSpeed"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ");}return body_0;})(); +(function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo Users"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackup status"]),ctx,"h").write("
    ").reference(ctx._get(false, ["nospc_fatal"]),ctx,"h",["s"]).reference(ctx._get(false, ["nospc_stalled"]),ctx,"h",["s"]).reference(ctx._get(false, ["database_error"]),ctx,"h",["s"]).reference(ctx._get(false, ["endian_info"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tGroup name"]),ctx,"h").write("").reference(ctx._get(false, ["tOnline"]),ctx,"h").write("").reference(ctx._get(false, ["tStatus"]),ctx,"h").write("").reference(ctx._get(false, ["tLast seen"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("").reference(ctx._get(false, ["tLast image backup"]),ctx,"h").write("").reference(ctx._get(false, ["tFile backup status"]),ctx,"h").write("").reference(ctx._get(false, ["tImage backup status"]),ctx,"h").write("").reference(ctx._get(false, ["tIP"]),ctx,"h").write("").reference(ctx._get(false, ["tClient version"]),ctx,"h").write("").reference(ctx._get(false, ["tOperating System"]),ctx,"h").write("
    ").exists(ctx._get(false, ["status_can_show_all"]),ctx,{"block":body_2},null).reference(ctx._get(false, ["modify_clients"]),ctx,"h",["s"]).exists(ctx._get(false, ["has_client_download"]),ctx,{"block":body_3},null).exists(ctx._get(false, ["allow_add_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["removed_clients_table"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["status_extra_clients"]),ctx,{"block":body_8},null).write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["status_client_download_windows"]),ctx,"h",["s"]).reference(ctx._get(false, ["status_client_download_linux"]),ctx,"h",["s"]).write("
    ");}function body_4(chk,ctx){return chk.write("");}function body_5(chk,ctx){return chk.write("
    ").section(ctx._get(false, ["removed_clients"]),ctx,{"block":body_6},null).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write(" 
    ");}function body_6(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["tThis client is going to be removed. "]),ctx,"h").write(" ").exists(ctx._get(false, ["remove_client"]),ctx,{"block":body_7},null).reference(ctx._get(false, ["tClients are removed during the cleanup in the cleanup time window. "]),ctx,"h").write("");}function body_7(chk,ctx){return chk.write("").reference(ctx._get(false, ["tStop removing client"]),ctx,"h").write(". ");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient discovery hints"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["extra_clients_rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tHostname/IP"]),ctx,"h").write("").reference(ctx._get(false, ["tOnline"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["hostname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["groupname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write(" ").exists(ctx._get(false, ["online_add_status"]),ctx,{"block":body_2},null).write(" ").exists(ctx._get(false, ["reset_client_uid"]),ctx,{"block":body_3},null).write("").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastseen"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h").reference(ctx._get(false, ["start_file_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastbackup_image"]),ctx,"h").reference(ctx._get(false, ["start_image_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["file_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["image_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["ip"]),ctx,"h").write("").reference(ctx._get(false, ["client_version_string"]),ctx,"h").write("").reference(ctx._get(false, ["os_version_string"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("(").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write(")");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tAllow new client"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.write("");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Windows"]),ctx,"h");}function body_2(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Mac OS X"]),ctx,"h");}function body_3(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Linux"]),ctx,"h");}return body_0;})(); +(function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_2},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_3},null).write("
    ");}function body_1(chk,ctx){return chk.write("min-width: 2em;");}function body_2(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_3(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}return body_0;})(); +(function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tmpdir_error_text"]),ctx,"h").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage of"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ");}return body_0;})(); +(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSelect all"]),ctx,"h").write("").reference(ctx._get(false, ["tSelect none"]),ctx,"h").write("").reference(ctx._get(false, ["rem_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["tRemove selected"]),ctx,"h").write("").reference(ctx._get(false, ["rem_stop"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); +(function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.write("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").reference(ctx._get(false, ["virus_error_path"]),ctx,"h").write(" ).").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["upgrade_error_text"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tCurrent version"]),ctx,"h").write(": ").reference(ctx._get(false, ["curr_db_version"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tTarget version"]),ctx,"h").write(": ").reference(ctx._get(false, ["target_db_version"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["images"]),ctx,"h").write("").reference(ctx._get(false, ["files"]),ctx,"h").write("").reference(ctx._get(false, ["used"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); +(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.exists(ctx._get(false, ["client_settings"]),ctx,{"else":body_1,"block":body_2},null).write("
    ").reference(ctx._get(false, ["thours"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    \t\t\t\t
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDays"]),ctx,"h").write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_6},null).write("\t\t\t").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_7},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_8},null).write("
    ").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tArchive every"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive for"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive window"]),ctx,"h").write(" ?").reference(ctx._get(false, ["tBackup type"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume letters"]),ctx,"h").write("").reference(ctx._get(false, ["tNext archival"]),ctx,"h").write("  
     ").exists(ctx._get(false, ["archive_global"]),ctx,{"block":body_9},null).reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("\t\t
    ").exists(ctx._get(false, ["can_edit_scripts"]),ctx,{"block":body_10},null).write("
    \t\t\t
    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("
    MBit/s
    ").reference(ctx._get(false, ["internet_settings_start"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_11},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_12},null).write("
    KBit/s
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_16},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_17},null).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_18},null).write("
    ").reference(ctx._get(false, ["internet_settings_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    \t\t\t
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["client_settings"]),ctx,{"block":body_19},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMin"]),ctx,"h").write("
    ");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_7(chk,ctx){return chk.write("
    ");}function body_8(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_9(chk,ctx){return chk.write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tEdit scripts"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("
    ");}function body_12(chk,ctx){return chk.notexists(ctx._get(false, ["global_settings"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["with_authkey"]),ctx,{"block":body_14},null);}function body_13(chk,ctx){return chk.write("
    ");}function body_14(chk,ctx){return chk.write("
    ");}function body_15(chk,ctx){return chk.write("
    KBit/s
    ");}function body_16(chk,ctx){return chk.write("
    ");}function body_17(chk,ctx){return chk.write("
    ");}function body_18(chk,ctx){return chk.write("
    ");}function body_19(chk,ctx){return chk.write("
    ");}return body_0;})(); diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 824c67497..87f4ee2b7 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -3443,7 +3443,7 @@ function show_settings2(data) data.settings=addSelectSelected(full_image_style_params, "local_full_image_style", data.settings); data.settings=addSelectSelected(full_image_style_params, "internet_full_image_style", data.settings); - var image_file_format_params = ["vhdz", "vhd"]; + var image_file_format_params = ["vhdz", "vhd", "vhdxz", "vhdx"]; if(data.cowraw_available) { data.settings.cowraw_available=true; @@ -3539,7 +3539,7 @@ function show_settings2(data) data.settings=addSelectSelected(transfer_mode_params1, "local_image_transfer_mode", data.settings); data.settings=addSelectSelected(transfer_mode_params1, "internet_image_transfer_mode", data.settings); - var image_file_format_params = ["vhdz", "vhd"]; + var image_file_format_params = ["vhdz", "vhd", "vhdxz", "vhdx"]; if(data.cowraw_available) { data.settings.cowraw_available=true; diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index 3e5b438ee..60477a70e 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -168,8 +168,10 @@
    From 4bdfbac3d64c9f334765dbba82530348de9686f2 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 1 May 2021 20:23:45 +0200 Subject: [PATCH 057/469] Don't update header on read only mode --- fsimageplugin/dllmain.cpp | 171 ++++++++++++++++++++++++++++--------- fsimageplugin/vhdxfile.cpp | 13 ++- fsimageplugin/vhdxfile.h | 6 ++ 3 files changed, 147 insertions(+), 43 deletions(-) diff --git a/fsimageplugin/dllmain.cpp b/fsimageplugin/dllmain.cpp index 580fdd3ec..57897777c 100644 --- a/fsimageplugin/dllmain.cpp +++ b/fsimageplugin/dllmain.cpp @@ -71,6 +71,7 @@ extern IServer* Server; #include "win_dialog.h" #endif #include "FileWrapper.h" +#include "FSImageFactory.h" #ifdef __linux__ #include @@ -130,6 +131,28 @@ namespace } } + if (findextension(fn) == "vhdxz") + { + std::auto_ptr vhdfile(new VHDXFile(fn, true, 0)); + + if (vhdfile->isOpen() && vhdfile->getParent() != NULL) + { + if (vhdfile->isCompressed()) + { + std::string parent_fn = vhdfile->getParent()->getFilename(); + vhdfile.reset(); + Server->Log("Decompressing parent VHDX \"" + parent_fn + "\"...", LL_INFO); + bool b = decompress_vhd(parent_fn, parent_fn); + + if (!b) + { + Server->Log("Error decompressing parent VHDX", LL_ERROR); + return false; + } + } + } + } + { CompressedFile compFile(fn, MODE_READ, 0); @@ -191,22 +214,22 @@ namespace IVHDFile* open_device_file(std::string device_verify, bool read_only=true, int64 dst_size = 0, - std::string parent_fn=std::string()) + std::string parent_fn=std::string(), bool fast_mode=false) { std::string ext = strlower(findextension(device_verify)); if(ext=="vhd" || ext=="vhdz") { if(parent_fn.empty()) - return new VHDFile(device_verify, read_only, dst_size); + return new VHDFile(device_verify, read_only, dst_size, 2*1024*1024, fast_mode); else - return new VHDFile(device_verify, parent_fn, read_only); + return new VHDFile(device_verify, parent_fn, read_only, fast_mode); } else if (ext == "vhdx" || ext == "vhdxz") { if (parent_fn.empty()) - return new VHDXFile(device_verify, read_only, dst_size); + return new VHDXFile(device_verify, read_only, dst_size, 2 * 1024 * 1024, fast_mode); else - return new VHDXFile(device_verify, parent_fn, read_only); + return new VHDXFile(device_verify, parent_fn, read_only, fast_mode); } #if !defined(_WIN32) && !defined(__APPLE__) else if(ext=="raw") @@ -254,7 +277,7 @@ namespace if(fn.empty()) { - Server->Log("No input VHD", LL_ERROR); + Server->Log("No input VHD(X)", LL_ERROR); return false; } @@ -295,9 +318,13 @@ namespace mbrdatas.push_back(mbrdata); } - std::string mbr = mbrdatas[0].mbr_data; + if (mbrdatas.empty()) + { + Server->Log("Could not open any MBR data", LL_ERROR); + return false; + } - partition* partitions = reinterpret_cast(&mbr[446]); + SMBRData& mbrdata = mbrdatas[0]; std::string skip_s=Server->getServerParameter("skip"); int skip=1024*512; @@ -305,7 +332,6 @@ namespace { skip=atoi(skip_s.c_str()); } - std::vector input_files; std::vector input_fs; @@ -318,6 +344,12 @@ namespace int64 total_size = 0; int64 total_written = 0; + FSImageFactory img_fak; + + bool gpt_style = false; + std::vector partitions = img_fak.readPartitions(mbrdata.mbr_data, + mbrdata.gpt_header, mbrdata.gpt_table, gpt_style); + for(size_t i=0;iSize(); } - partition* cpart = partitions + (mbrdatas[i].partition_number-1); - - /*unsigned char chs_expected[3] = { 0xfe, 0xff, 0xff }; - if(memcmp(cpart->chs_begin, chs_expected, 3)!=0 - || memcmp(cpart->chs_end, chs_expected,3)!=0) + if (mbrdatas[i].partition_number >= partitions.size()) { - Server->Log(L"MBR partition of Volume "+mbrdatas[i].volume_name+L" does not use LBA addressing scheme", LL_ERROR); + Server->Log("Partitions number of input file " + fn[i] + + " larger than parsed partitions from MBR/GPT", LL_ERROR); return false; - }*/ + } - cpart->start_sector=little_endian(cpart->start_sector); - cpart->nr_sector=little_endian(cpart->nr_sector); + IFSImageFactory::SPartition partition = partitions[mbrdatas[i].partition_number]; + + total_size = (std::max)(total_size, partition.offset + partition.length); + } - total_size = (std::max)(total_size, cpart->start_sector*c_sector_size + cpart->nr_sector*c_sector_size); + if (gpt_style) + { + total_size = (std::max)(total_size, mbrdata.backup_gpt_header_pos + 512); } - VHDFile vhdout(output, false, total_size, 2*1024*1024, true, false); - if(!vhdout.isOpen()) + int64 curr_pos=0; + + std::unique_ptr vhdout(open_device_file(output, false, total_size, std::string(), true)); + IFile* vhdout_file = reinterpret_cast(vhdout.get()); + + if (vhdout.get()==nullptr || !vhdout->isOpen()) { - Server->Log("Error opening output VHD-File \""+output+"\"", LL_ERROR); + Server->Log("Error opening output VHD(X)-File \"" + output + "\"", LL_ERROR); return false; } - int64 curr_pos=0; - - vhdout.Seek(0); + vhdout->Seek(0); - if(vhdout.Write(mbr.data(), static_cast<_u32>(mbr.size()))!=static_cast<_u32>(mbr.size())) + if(vhdout->Write(mbrdata.mbr_data.data(), static_cast<_u32>(mbrdata.mbr_data.size())) + !=static_cast<_u32>(mbrdata.mbr_data.size())) { Server->Log("Error writing MBR", LL_ERROR); return false; } + if (mbrdata.gpt_style) + { + Server->Log("Writing GPT header..."); + if (vhdout_file->Write(mbrdata.gpt_header_pos, mbrdata.gpt_header) + != mbrdata.gpt_header.size()) + { + Server->Log("Writing GPT header failed. " + os_last_error_str(), LL_ERROR); + } + + Server->Log("Writing GPT table..."); + if (vhdout_file->Write(mbrdata.gpt_table_pos, mbrdata.gpt_table) + != mbrdata.gpt_table.size()) + { + Server->Log("Writing GPT table failed. " + os_last_error_str(), LL_ERROR); + } + + if (mbrdata.backup_gpt_header_pos != -1) + { + Server->Log("Writing GPT backup header..."); + if (vhdout_file->Write(mbrdata.backup_gpt_header_pos, mbrdata.backup_gpt_header) + != mbrdata.backup_gpt_header.size()) + { + Server->Log("Writing GPT backup header failed. " + os_last_error_str(), LL_ERROR); + } + } + + if (mbrdata.backup_gpt_table_pos != -1) + { + Server->Log("Writing GPT backup table..."); + if (vhdout_file->Write(mbrdata.backup_gpt_table_pos, mbrdata.backup_gpt_table) + != mbrdata.backup_gpt_table.size()) + { + Server->Log("Writing backup GPT table failed. " + os_last_error_str(), LL_ERROR); + } + } + } + + 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 (vhdout_file->Write(mbrdata.extra_data_pos, mbrdata.extra_data) != mbrdata.extra_data.size()) + { + Server->Log("Writing extra data failed. " + os_last_error_str(), LL_ERROR); + } + } + for(size_t i=0;iLog("Writing "+fn[i]+" into output VHD..."); + IFSImageFactory::SPartition cpart = partitions[mbrdatas[i].partition_number]; - partition* cpart = partitions + (mbrdatas[i].partition_number-1); - int64 out_pos = cpart->start_sector*c_sector_size; - int64 max_pos = out_pos + cpart->nr_sector*c_sector_size; + int64 out_pos = cpart.offset; + int64 max_pos = cpart.offset + cpart.length; + + Server->Log("Writing "+fn[i]+" into output VHD(x)... Partition=" + + convert(mbrdatas[i].partition_number)+" offset=" + convert(out_pos) + +" length="+PrettyPrintBytes(cpart.length), LL_ERROR); if(input_fs[i]!=NULL) { @@ -398,8 +484,8 @@ namespace { fs_buffer fsb(input_fs[i], buf); - vhdout.Seek(out_pos); - if(vhdout.Write(buf->getBuf(), static_cast<_u32>(blocksize))!=static_cast<_u32>(blocksize)) + vhdout->Seek(out_pos); + if(vhdout->Write(buf->getBuf(), static_cast<_u32>(blocksize))!=static_cast<_u32>(blocksize)) { Server->Log("Error writing to VHD output file", LL_ERROR); return false; @@ -424,7 +510,7 @@ namespace std::vector buf; buf.resize(32768); - vhdout.Seek(out_pos); + vhdout->Seek(out_pos); if(out_pos+input_files[i]->Size()>max_pos) { @@ -446,7 +532,7 @@ namespace return false; } - if(vhdout.Write(buf.data(), read)!=read) + if(vhdout->Write(buf.data(), read)!=read) { Server->Log("Error writing to VHD output file (2)", LL_ERROR); return false; @@ -633,9 +719,9 @@ DLLEXPORT void LoadActions(IServer* pServer) if(decompress=="SelectViaGUI") { std::string filter; - filter += "Compressed image files (*.vhdz)"; + filter += "Compressed image files (*.vhdz;*.vhdxz)"; filter += '\0'; - filter += "*.vhdz"; + filter += "*.vhdz;*.vhdxz"; filter += '\0'; filter += '\0'; std::vector res = file_via_dialog("Please select compressed image file to decompress", @@ -662,7 +748,8 @@ DLLEXPORT void LoadActions(IServer* pServer) #endif std::string targetName = decompress; - if(findextension(decompress)!="vhdz" && findextension(decompress)!="urz") + if(findextension(decompress)!="vhdz" && findextension(decompress)!="urz" + && findextension(decompress) != "vhdxz") { Server->Log("Unknown file extension: "+findextension(decompress), LL_ERROR); exit(1); @@ -688,9 +775,9 @@ DLLEXPORT void LoadActions(IServer* pServer) if(assemble=="SelectViaGUI") { std::string filter; - filter += "Image files (*.vhdz;*.vhd)"; + filter += "Image files (*.vhdz;*.vhd;*.vhdxz;*.vhdx)"; filter += '\0'; - filter += "*.vhdz;*.vhd"; + filter += "*.vhdz;*.vhd;*.vhdxz;*.vhdx"; filter += '\0'; /*filter += L"Image files (*.vhd)"; filter += '\0'; @@ -710,10 +797,14 @@ DLLEXPORT void LoadActions(IServer* pServer) filter += '\0'; filter += "*.vhd"; filter += '\0'; + filter += "Image file v2 (*.vhdx)"; + filter += '\0'; + filter += "*.vhdx"; + filter += '\0'; filter += '\0'; std::vector output_files = file_via_dialog("Please select where to save the output image", - filter, false, false, "vhd"); + filter, false, false, "vhdx"); if(!output_files.empty()) { diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index ded4f620c..fcfa1b15d 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1204,7 +1204,7 @@ bool VHDXFile::setBackingFileSize(_i64 fsize) if (fsize > backing_file->Size()) { - return backing_file->Resize(fsize); + return backing_file->Resize(fsize, false); } return false; @@ -1747,6 +1747,10 @@ void VHDXFile::getDataWriteGUID(VhdxGUID& g) copyGUID(curr_header.DataWriteGuid, g); } +bool VHDXFile::isCompressed() { + return file == compressed_file.get(); +} + bool VHDXFile::createNew() { memset(&curr_header, 0, sizeof(curr_header)); @@ -1889,6 +1893,8 @@ bool VHDXFile::createNew() bool VHDXFile::updateHeader() { + assert(!read_only); + ++curr_header.SequenceNumber; curr_header.Checksum = 0; @@ -2569,11 +2575,12 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre allocated_size = backing_file->Size(); - secureRandomGuid(curr_header.FileWriteGuid); + if(!read_only) + secureRandomGuid(curr_header.FileWriteGuid); flushed_vhdx_size = allocated_size; - if (!fast_mode && !updateHeader()) + if (!read_only && !fast_mode && !updateHeader()) { return false; } diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h index e66540c84..d0475773f 100644 --- a/fsimageplugin/vhdxfile.h +++ b/fsimageplugin/vhdxfile.h @@ -93,6 +93,12 @@ class VHDXFile : public IVHDFile, public IFile void getDataWriteGUID(VhdxGUID& g); + VHDXFile* getParent() { + return parent.get(); + } + + bool isCompressed(); + private: bool createNew(); bool updateHeader(); From 4d77cf401a698709016d165b0303e6175cc43871 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 1 May 2021 20:25:33 +0200 Subject: [PATCH 058/469] Use GPT for larger volume images --- urbackupserver/ImageBackup.cpp | 169 +++++++++++++++++++++++++++++++-- urbackupserver/ImageBackup.h | 3 +- 2 files changed, 163 insertions(+), 9 deletions(-) diff --git a/urbackupserver/ImageBackup.cpp b/urbackupserver/ImageBackup.cpp index c9fb12111..41e346fd7 100644 --- a/urbackupserver/ImageBackup.cpp +++ b/urbackupserver/ImageBackup.cpp @@ -38,6 +38,7 @@ #include "../Interface/Pipe.h" #include "../urbackupcommon/fileclient/tcpstack.h" #include "../urbackupcommon/mbrdata.h" +#include "../common/miniz.h" #include "server_ping.h" #include "snapshot_helper.h" #include "server.h" @@ -1066,25 +1067,34 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent image_format = IFSImageFactory::ImageFormat_CompressedVHD; } + int64 partition_table_add = mbr_size; + + if (drivesize + partition_table_add > 2LL * 1024 * 1024 * 1024 * 1024) + { + //GPT backup + partition_table_add += 1 * 1024 * 1024 - (drivesize + partition_table_add) % (1 * 1024 * 1024); + partition_table_add += 2 * sector_size; + } + if ((image_format == IFSImageFactory::ImageFormat_VHDX || image_format == IFSImageFactory::ImageFormat_CompressedVHDX) && - drivesize + mbr_size > 64LL * 1024 * 1024 * 1024 * 1024) + drivesize + partition_table_add > 64LL * 1024 * 1024 * 1024 * 1024) { - ServerLogger::Log(logid, "Volume is too large for VHDX files with " + PrettyPrintBytes(drivesize + mbr_size) + + ServerLogger::Log(logid, "Volume is too large for VHDX files with " + PrettyPrintBytes(drivesize + partition_table_add) + ". VHDX files have a maximum size of 64TiB. Please use another image file format.", LL_ERROR); goto do_image_cleanup; } if(!has_parent) { - r_vhdfile=image_fak->createVHDFile(os_file_prefix(imagefn), false, drivesize+mbr_size, + r_vhdfile=image_fak->createVHDFile(os_file_prefix(imagefn), false, drivesize+ partition_table_add, (unsigned int)vhd_blocksize*blocksize, true, image_format, server_settings->getSettings()->image_compress_threads); } else { r_vhdfile=image_fak->createVHDFile(os_file_prefix(imagefn), pParentvhd, false, - true, image_format, drivesize + mbr_size, + true, image_format, drivesize + partition_table_add, server_settings->getSettings()->image_compress_threads); } @@ -1117,7 +1127,7 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent } vhdfile=new ServerVHDWriter(r_vhdfile, blocksize, 5000, clientid, server_settings->getSettings()->use_tmpfiles_images, - mbr_offset, hashfile, vhd_blocksize*blocksize, logid, drivesize + (int64)mbr_size); + mbr_offset, hashfile, vhd_blocksize*blocksize, logid, drivesize + partition_table_add); vhdfile_ticket = Server->getThreadPool()->execute(vhdfile, "image backup writer"); blockdata=vhdfile->getBuffer(); @@ -1139,7 +1149,8 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent if (!disk_backup) { - mbr_offset = writeMBR(vhdfile, drivesize); + bool use_gpt = (drivesize + mbr_offset) > 2LL * 1024 * 1024 * 1024 * 1024; + mbr_offset = writeMBR(vhdfile, drivesize, use_gpt); if (mbr_offset == 0) { ServerLogger::Log(logid, "Error writing image MBR", LL_ERROR); @@ -1147,6 +1158,15 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent } else { + if (use_gpt) + { + if (!writeGPT(vhdfile, drivesize, static_cast(mbr_offset))) + { + ServerLogger::Log(logid, "Error writing image GPT", LL_ERROR); + goto do_image_cleanup; + } + } + vhdfile->setMbrOffset(mbr_offset); } } @@ -1904,7 +1924,7 @@ bool ImageBackup::doImage(const std::string &pLetter, const std::string &pParent return false; } -unsigned int ImageBackup::writeMBR(ServerVHDWriter* vhdfile, uint64 volsize) +unsigned int ImageBackup::writeMBR(ServerVHDWriter* vhdfile, uint64 volsize, bool gpt_protective) { unsigned char *mbr=(unsigned char *)vhdfile->getBuffer(); if(mbr==NULL) @@ -1927,7 +1947,7 @@ unsigned int ImageBackup::writeMBR(ServerVHDWriter* vhdfile, uint64 volsize) partition[1]=0xfe; partition[2]=0xff; partition[3]=0xff; - partition[4]=0x07; //ntfs + partition[4]= gpt_protective ? 0xEE : 0x07; //ntfs partition[5]=0xfe; partition[6]=0xff; partition[7]=0xff; @@ -1936,6 +1956,11 @@ unsigned int ImageBackup::writeMBR(ServerVHDWriter* vhdfile, uint64 volsize) partition[10]=0x00; partition[11]=0x00; + if (gpt_protective) + { + volsize = 2LL * 1024 * 1024 * 1024 * 1024; + } + unsigned int sectors=(unsigned int)(volsize/((uint64)sector_size)); sectors = little_endian(sectors); memcpy(&partition[12], §ors, sizeof(unsigned int) ); @@ -1965,6 +1990,134 @@ unsigned int ImageBackup::writeMBR(ServerVHDWriter* vhdfile, uint64 volsize) return 1024*512; } +namespace +{ + void randomGUID(char* g) + { + Server->randomFill(g, 16); + g[6] = 0x40 | (g[6] & 0xf); + g[8] = 0x80 | (g[8] & 0x3f); + } + + void reorderGUID(char* g) + { + *reinterpret_cast(&g[0]) = big_endian(*reinterpret_cast(&g[0])); + *reinterpret_cast(&g[4]) = big_endian(*reinterpret_cast(&g[4])); + *reinterpret_cast(&g[6]) = big_endian(*reinterpret_cast(&g[6])); + } +} + +bool ImageBackup::writeGPT(ServerVHDWriter* vhdfile, uint64 volsize, unsigned int mbr_offset) +{ +#pragma pack(push) +#pragma pack(1) + struct EfiHeader + { + uint64 signature; + _u32 revision; + _u32 header_size; + _u32 header_crc; + _u32 reserved; + int64 current_lba; + int64 backup_lba; + int64 first_lba; + int64 last_lba; + char disk_guid[16]; + int64 partition_table_lba; + _u32 num_parition_entries; + _u32 partition_entry_size; + _u32 partition_table_crc; + }; + + struct GPTPartition + { + char partition_type_guid[16]; + char unique_partition_guid[16]; + int64 first_lba; + int64 last_lba; + uint64 flags; + char name[72]; + }; +#pragma pack(pop) + + char* header_buf = vhdfile->getBuffer(); + if (header_buf == NULL) + return false; + + char* table_buf = vhdfile->getBuffer(); + if (table_buf == NULL) + return false; + + char* header2_buf = vhdfile->getBuffer(); + if (header2_buf == NULL) + return false; + + char* table_buf2 = vhdfile->getBuffer(); + if (table_buf2 == NULL) + return false; + + memset(header_buf, 0, 512); + + EfiHeader* efi_header = reinterpret_cast(header_buf); + + memcpy(efi_header, "EFI PART", 8); + + efi_header->revision = 0x00010000; + efi_header->header_size = sizeof(EfiHeader); + efi_header->current_lba = 1; + + uint64 vol_size_ru = mbr_offset + volsize; + vol_size_ru += 1 * 1024 * 1024 - (vol_size_ru % (1 * 1024 * 1024)); + + efi_header->backup_lba = ( vol_size_ru + sector_size)/sector_size; + efi_header->first_lba = 3; + efi_header->last_lba = efi_header->backup_lba - 1; + randomGUID(efi_header->disk_guid); + efi_header->partition_table_lba = 2; + efi_header->num_parition_entries = 1; + efi_header->partition_entry_size = 128; + + memset(table_buf, 0, 512); + GPTPartition* partition = reinterpret_cast(table_buf); + + partition->first_lba = mbr_offset / sector_size; + partition->last_lba = (mbr_offset + volsize) / sector_size; + randomGUID(partition->unique_partition_guid); + unsigned char data_partition_guid[16] = { 0xEB, 0xD0, 0xA0, 0xA2, + 0xB9, 0xE5, 0x44, 0x33, 0x87, 0xC0, 0x68, 0xB6, 0xB7, + 0x26, 0x99, 0xC7 }; + memcpy(partition->partition_type_guid, data_partition_guid, 16); + reorderGUID(partition->partition_type_guid); + const std::string name = Server->ConvertToUTF16("VOLUME BACKUP"); + memcpy(partition->name, name.data(), name.size()); + + efi_header->partition_table_crc = mz_crc32(MZ_CRC32_INIT, + reinterpret_cast(partition), sizeof(GPTPartition)); + + efi_header->header_crc = mz_crc32(MZ_CRC32_INIT, + reinterpret_cast(efi_header), efi_header->header_size); + + memcpy(table_buf2, table_buf, 512); + memcpy(header2_buf, header_buf, 512); + + EfiHeader* efi_header2 = reinterpret_cast(header2_buf); + + efi_header2->header_crc = 0; + efi_header2->current_lba = efi_header->backup_lba; + efi_header2->backup_lba = 1; + efi_header2->partition_table_lba = efi_header2->current_lba - 1; + + efi_header2->header_crc = mz_crc32(MZ_CRC32_INIT, + reinterpret_cast(efi_header2), efi_header2->header_size); + + vhdfile->writeBuffer(efi_header->current_lba * sector_size, header_buf, 512); + vhdfile->writeBuffer(efi_header->partition_table_lba* sector_size, table_buf, 512); + vhdfile->writeBuffer(efi_header2->partition_table_lba* sector_size, table_buf2, 512); + vhdfile->writeBuffer(efi_header2->current_lba* sector_size, header2_buf, 512); + + return true; +} + int64 ImageBackup::updateNextblock(int64 nextblock, int64 currblock, sha256_ctx *shactx, unsigned char *zeroblockdata, bool parent_fn, IFile *hashfile, IFile *parenthashfile, unsigned int blocksize, int64 mbr_offset, int64 vhd_blocksize, bool& warned_about_parenthashfile_error, int64 empty_vhdblock_start, diff --git a/urbackupserver/ImageBackup.h b/urbackupserver/ImageBackup.h index 99ae4c133..a4625e036 100644 --- a/urbackupserver/ImageBackup.h +++ b/urbackupserver/ImageBackup.h @@ -51,7 +51,8 @@ class ImageBackup : public Backup bool doImage(const std::string &pLetter, const std::string &pParentvhd, int incremental, int incremental_ref, bool transfer_checksum, std::string image_file_format, bool transfer_bitmap, bool transfer_prev_cbitmap); - unsigned int writeMBR(ServerVHDWriter* vhdfile, uint64 volsize); + unsigned int writeMBR(ServerVHDWriter* vhdfile, uint64 volsize, bool gpt_protective); + bool writeGPT(ServerVHDWriter* vhdfile, uint64 volsize, unsigned int mbr_offset); int64 updateNextblock(int64 nextblock, int64 currblock, sha256_ctx* shactx, unsigned char* zeroblockdata, bool parent_fn, IFile* hashfile, IFile* parenthashfile, unsigned int blocksize, int64 mbr_offset, int64 vhd_blocksize, bool &warned_about_parenthashfile_error, int64 empty_vhdblock_start, From db997912ac95dcdcebbe78d9669f0ae52e6ee864 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 1 May 2021 20:26:01 +0200 Subject: [PATCH 059/469] Fix vhdx selection --- urbackupserver/www/js/templates.js | 76 +++++++++---------- .../www/templates/settings_inv_row.htm | 4 +- 2 files changed, 40 insertions(+), 40 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 74f3ae444..7766c45c3 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,76 +1,76 @@ -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAbout UrBackup"]),ctx,"h").write("
    UrBackup Server ").reference(ctx._get(false, ["version"]),ctx,"h").write("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}return body_0;})(); -(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAdd client"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDownload the client from:"]),ctx,"h").write(" www.urbackup.org

    ").reference(ctx._get(false, ["tIf you want a client to use multiple backup servers this server's identity is:"]),ctx,"h").write(" ").reference(ctx._get(false, ["server_identity"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tFor security reasons check/add following line in the file server_idents.txt on your client:"]),ctx,"h").write("

    ").reference(ctx._get(false, ["server_pubkey"]),ctx,"h",["s"]).write("



    ");}return body_0;})(); (function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tEdit alert scripts"]),ctx,"h").write("
     

    ").reference(ctx._get(false, ["tAlert script parameters"]),ctx,"h").write("

    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("



    ").reference(ctx._get(false, ["tAlert script"]),ctx,"h").write("

    \t\t

    ").exists(ctx._get(false, ["saved_ok"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("
    Saved script successfully.
    ");}return body_0;})(); +(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAdd client"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDownload the client from:"]),ctx,"h").write(" www.urbackup.org

    ").reference(ctx._get(false, ["tIf you want a client to use multiple backup servers this server's identity is:"]),ctx,"h").write(" ").reference(ctx._get(false, ["server_identity"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tFor security reasons check/add following line in the file server_idents.txt on your client:"]),ctx,"h").write("

    ").reference(ctx._get(false, ["server_pubkey"]),ctx,"h",["s"]).write("



    ");}return body_0;})(); (function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tName:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLabel:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDefault value:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tType:"]),ctx,"h").write("
     
    ");}return body_0;})(); (function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write(" > ").reference(ctx._get(false, ["cpath"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_2},null).write("").section(ctx._get(false, ["items"]),ctx,{"block":body_3},null).write("
     ").reference(ctx._get(false, ["tFile"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tCreated"]),ctx,"h").write("").reference(ctx._get(false, ["tLast modified"]),ctx,"h").write("").reference(ctx._get(false, ["tLast accessed"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tVersion"]),ctx,"h").write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["size"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["creat"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["mod"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["access"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["backuptime"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_version"]),ctx,{"block":body_4},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_5},null).write("");}function body_4(chk,ctx){return chk.write("").reference(ctx._get(false, ["version"]),ctx,"h").write("");}function body_5(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAccess denied"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong or you do not have the required rights to access this file or folder."]),ctx,"h").exists(ctx._get(false, ["errcode"]),ctx,{"block":body_1},null).write("

    ").reference(ctx._get(false, ["tLogin with username and password"]),ctx,"h").write("

    ");}function body_1(chk,ctx){return chk.write("(").reference(ctx._get(false, ["errcode"]),ctx,"h").write(")");}return body_0;})(); (function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").exists(ctx._get(false, ["backups"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["backup_images"]),ctx,{"block":body_11},null).notexists(ctx._get(false, ["backups"]),ctx,{"block":body_20},null).write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tFile backups"]),ctx,"h").write("

    ").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_3},null).write("").section(ctx._get(false, ["backups"]),ctx,{"block":body_4},null).write("
     ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tArchived"]),ctx,"h").write("?
    ");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("");}function body_4(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["incr"]),ctx,"h").write("").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_5},null).write("");}function body_5(chk,ctx){return chk.write("").notexists(ctx._get(false, ["is_archived"]),ctx,{"block":body_6},null).write("");}function body_6(chk,ctx){return chk.notexists(ctx._get(false, ["disable_delete"]),ctx,{"block":body_7},null);}function body_7(chk,ctx){return chk.exists(ctx._get(false, ["can_delete"]),ctx,{"block":body_8},null);}function body_8(chk,ctx){return chk.exists(ctx._get(false, ["delete_pending"]),ctx,{"else":body_9,"block":body_10},null);}function body_9(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["tDelete"]),ctx,"h").write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tBackup is marked for deletion. Do not delete"]),ctx,"h").write(" ").reference(ctx._get(false, ["tDelete now"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tImage backups"]),ctx,"h").write("

    \t\t\t\t").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_12},null).write("").section(ctx._get(false, ["backup_images"]),ctx,{"block":body_13},null).write("
     ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume"]),ctx,"h").write("").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tArchived"]),ctx,"h").write("?
    ");}function body_12(chk,ctx){return chk.write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("");}function body_13(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["letter"]),ctx,"h").write("").reference(ctx._get(false, ["incr"]),ctx,"h").write("").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_14},null).write("");}function body_14(chk,ctx){return chk.write("").notexists(ctx._get(false, ["is_archived"]),ctx,{"block":body_15},null).write("");}function body_15(chk,ctx){return chk.notexists(ctx._get(false, ["disable_delete"]),ctx,{"block":body_16},null);}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_delete"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.exists(ctx._get(false, ["delete_pending"]),ctx,{"else":body_18,"block":body_19},null);}function body_18(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["tDelete"]),ctx,"h").write("");}function body_19(chk,ctx){return chk.write("").reference(ctx._get(false, ["tBackup is marked for deletion. Do not delete"]),ctx,"h").write(" ").reference(ctx._get(false, ["tDelete now"]),ctx,"h").write("");}function body_20(chk,ctx){return chk.notexists(ctx._get(false, ["backup_images"]),ctx,{"block":body_21},null);}function body_21(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tNo backups"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tNo backups of this client yet"]),ctx,"h");}return body_0;})(); -(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClients"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tPreparing restore. Please be patient..."]),ctx,"h").write("
     
    ");}return body_0;})(); (function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h",["s"]).write("");}return body_0;})(); -(function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tError while accessing backups"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong:"]),ctx,"h").write(" ").reference(ctx._get(false, ["err"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write(" > ").reference(ctx._get(false, ["cpath"]),ctx,"h",["s"]).write("
    ").section(ctx._get(false, ["image_backup_info"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["can_mount"]),ctx,{"else":body_4,"block":body_11},null).exists(ctx._get(false, ["download_zip"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_14},null).write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tImage backup information"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tId"]),ctx,"h").write(": ").reference(ctx._get(false, ["id"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write(": ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write(": ").reference(ctx._get(false, ["incr"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSize"]),ctx,"h").write(": ").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tVolume"]),ctx,"h").write(": ").reference(ctx._get(false, ["letter"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tArchived"]),ctx,"h").write(": ").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tVolume size"]),ctx,"h").write(": ").reference(ctx._get(false, ["volume_size"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPartition style"]),ctx,"h").write(": ").reference(ctx._get(false, ["part_table"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDisk number"]),ctx,"h").write(": ").reference(ctx._get(false, ["disk_number"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPartition number"]),ctx,"h").write(": ").reference(ctx._get(false, ["partition_number"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tFile system type"]),ctx,"h").write(": ").reference(ctx._get(false, ["fs_type"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tVolume name"]),ctx,"h").write(": ").reference(ctx._get(false, ["volume_name"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSerial number"]),ctx,"h").write(": ").reference(ctx._get(false, ["serial_number"]),ctx,"h").write("
    ").exists(ctx._get(false, ["linux_image_restore"]),ctx,{"block":body_3},null).write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tRestore Linux image"]),ctx,"h").write("");}function body_4(chk,ctx){return chk.notexists(ctx._get(false, ["no_files"]),ctx,{"block":body_5},null);}function body_5(chk,ctx){return chk.exists(ctx._get(false, ["mount_failed"]),ctx,{"else":body_6,"block":body_10},null);}function body_6(chk,ctx){return chk.write("").section(ctx._get(false, ["files"]),ctx,{"block":body_7},null).write("
     ").reference(ctx._get(false, ["tFile"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tCreated"]),ctx,"h").write("").reference(ctx._get(false, ["tLast modified"]),ctx,"h").write("").reference(ctx._get(false, ["tLast accessed"]),ctx,"h").write(" 
    ");}function body_7(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["size"]),ctx,"h").write("").reference(ctx._get(false, ["creat"]),ctx,"h").write("").reference(ctx._get(false, ["mod"]),ctx,"h").write("").reference(ctx._get(false, ["access"]),ctx,"h").write("").exists(ctx._get(false, ["list_items"]),ctx,{"block":body_8},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_9},null).write("");}function body_8(chk,ctx){return chk.write("").reference(ctx._get(false, ["tList"]),ctx,"h").write("");}function body_9(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore"]),ctx,"h").write("");}function body_10(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMounting image failed. Please see server log file for details."]),ctx,"h").write("
    ").reference(ctx._get(false, ["mount_errmsg"]),ctx,"h").write("
    ");}function body_11(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tMount image"]),ctx,"h").write("").exists(ctx._get(false, ["os_mount"]),ctx,{"block":body_12},null).write("
    ");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."]),ctx,"h").write("");}function body_13(chk,ctx){return chk.write("").reference(ctx._get(false, ["tDownload folder as ZIP"]),ctx,"h").write("");}function body_14(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore folder to client"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.write("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}return body_0;})(); (function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange password"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient added successfully"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tAdded new client with name:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDefault authentication key:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("

    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Windows"]),ctx,"h").write("
    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Linux"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tInstall it directly in the terminal via:"]),ctx,"h").write("

      TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

      ").reference(ctx._get(false, ["tWith Docker (web interface accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").reference(ctx._get(false, ["tWith Docker (web interface not accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").reference(ctx._get(false, ["tAlternatively after you installed the client from:"]),ctx,"h").write(" https://www.urbackup.org/download.html

      • ").reference(ctx._get(false, ["tGo to the settings screen on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tEnable the internet mode on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server port to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the computer name to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the authentication key to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tWith the command line:"]),ctx,"h").write("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}return body_0;})(); +(function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAccess denied"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong or you do not have the required rights to access this file or folder."]),ctx,"h").exists(ctx._get(false, ["errcode"]),ctx,{"block":body_1},null).write("

    ").reference(ctx._get(false, ["tLogin with username and password"]),ctx,"h").write("

    ");}function body_1(chk,ctx){return chk.write("(").reference(ctx._get(false, ["errcode"]),ctx,"h").write(")");}return body_0;})(); +(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.write("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}return body_0;})(); (function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanging password failed:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["fail_reason"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanged password successfully"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClients"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["generic_text"]),ctx,{"block":body_1},null).reference(ctx._get(false, ["ext_text"]),ctx,"h",["s"]).exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["dir_error_text"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient added successfully"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tAdded new client with name:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDefault authentication key:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("

    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Windows"]),ctx,"h").write("
    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Linux"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tInstall it directly in the terminal via:"]),ctx,"h").write("

      TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

      ").reference(ctx._get(false, ["tWith Docker (web interface accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").reference(ctx._get(false, ["tWith Docker (web interface not accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").reference(ctx._get(false, ["tAlternatively after you installed the client from:"]),ctx,"h").write(" https://www.urbackup.org/download.html

      • ").reference(ctx._get(false, ["tGo to the settings screen on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tEnable the internet mode on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server port to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the computer name to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the authentication key to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tWith the command line:"]),ctx,"h").write("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}return body_0;})(); +(function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tError while accessing backups"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong:"]),ctx,"h").write(" ").reference(ctx._get(false, ["err"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["database_error_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); (function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["creating_filesindex_text"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tNumber of file entries processed"]),ctx,"h").write(": ").reference(ctx._get(false, ["processed_file_entries"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPercent finished"]),ctx,"h").write(": ").reference(ctx._get(false, ["percent_finished"]),ctx,"h").write("


    ");}return body_0;})(); (function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThis server has discovered clients which are currently not configured to use this server."]),ctx,"h").write(" ").reference(ctx._get(false, ["tSee here for details on how this can happen."]),ctx,"h").write("

    ").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null);}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tOk. Dismiss this hint."]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanged password successfully"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["id"]),ctx,"h").write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["is_image"]),ctx,{"else":body_1,"block":body_4},null).write("").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["duration"]),ctx,"h").write("").reference(ctx._get(false, ["size"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("-");}function body_3(chk,ctx){return chk.write("Path: ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_4(chk,ctx){return chk.write("Volume: ").reference(ctx._get(false, ["details"]),ctx,"h");}return body_0;})(); (function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tUrBackup live log"]),ctx,"h").write(": ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
     
    ");}function body_1(chk,ctx){return chk.write("g.logid=").reference(ctx._get(false, ["logid"]),ctx,"h").write(";");}return body_0;})(); +(function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["level"]),ctx,"h").write("
    ").reference(ctx._get(false, ["time"]),ctx,"h").write("
    ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); (function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLast activities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tID"]),ctx,"h").write("").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tStarting time"]),ctx,"h").write("").reference(ctx._get(false, ["tRequired time"]),ctx,"h").write("").reference(ctx._get(false, ["tUsed Storage"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["time"]),ctx,"h").write("  ").reference(ctx._get(false, ["loglevel"]),ctx,"h").write("  ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); -(function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLog"]),ctx,"h").write(": (").reference(ctx._get(false, ["name"]),ctx,"h").write(")
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tLevel"]),ctx,"h").write("").reference(ctx._get(false, ["tTime"]),ctx,"h").write("").reference(ctx._get(false, ["tMessage"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tBack"]),ctx,"h").write("

    ");}return body_0;})(); (function(){dust.register("login",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tPreparing restore. Please be patient..."]),ctx,"h").write("
     
    ");}return body_0;})(); -(function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); -(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["time"]),ctx,"h").write("").reference(ctx._get(false, ["errors"]),ctx,"h").write("
    ").reference(ctx._get(false, ["warnings"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); (function(){dust.register("logs_report_mail",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["report_single_mail"]),ctx,"h").write(" -");}return body_0;})(); -(function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["level"]),ctx,"h").write("
    ").reference(ctx._get(false, ["time"]),ctx,"h").write("
    ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); -(function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); +(function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLog"]),ctx,"h").write(": (").reference(ctx._get(false, ["name"]),ctx,"h").write(")
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tLevel"]),ctx,"h").write("").reference(ctx._get(false, ["tTime"]),ctx,"h").write("").reference(ctx._get(false, ["tMessage"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tBack"]),ctx,"h").write("

    ");}return body_0;})(); (function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo entries for this filter"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThere is a new version of UrBackup server available"]),ctx,"h").write(" (").reference(ctx._get(false, ["new_version_number"]),ctx,"h").write("). Download it here.
    ").reference(ctx._get(false, ["tOk. Stop showing this."]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); +(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); (function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo entries for this filter"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["image"]),ctx,{"else":body_1,"block":body_6},null).exists(ctx._get(false, ["show_details"]),ctx,{"block":body_7},null).exists(ctx._get(false, ["backups_interrupted"]),ctx,{"block":body_8},null).write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_10},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_11},null).write("
    ").exists(ctx._get(false, ["f_total_bytes"]),ctx,{"block":body_12},null).write("").reference(ctx._get(false, ["eta"]),ctx,"h").write("").exists(ctx._get(false, ["paused"]),ctx,{"else":body_13,"block":body_14},null).write("").reference(ctx._get(false, ["queue"]),ctx,"h").write("").exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_16},null).exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_18},null).write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["client_update"]),ctx,{"else":body_2,"block":body_5},null);}function body_2(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_3,"block":body_4},null);}function body_3(chk,ctx){return chk.write("-");}function body_4(chk,ctx){return chk.reference(ctx._get(false, ["tPath:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_5(chk,ctx){return chk.reference(ctx._get(false, ["tTo version:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["tVolume:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_7(chk,ctx){return chk.reference(ctx._get(false, ["details"]),ctx,"h");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackups interrupted"]),ctx,"h");}function body_9(chk,ctx){return chk.write("min-width: 2em;");}function body_10(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_11(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["f_done_bytes"]),ctx,"h").write(" / ").reference(ctx._get(false, ["f_total_bytes"]),ctx,"h").write("
    ");}function body_13(chk,ctx){return chk.reference(ctx._get(false, ["speed"]),ctx,"h");}function body_14(chk,ctx){return chk.reference(ctx._get(false, ["tPaused"]),ctx,"h");}function body_15(chk,ctx){return chk.write("");}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.write(" ");}function body_18(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["time"]),ctx,"h").write("").reference(ctx._get(false, ["errors"]),ctx,"h").write("
    ").reference(ctx._get(false, ["warnings"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThere is a new version of UrBackup server available"]),ctx,"h").write(" (").reference(ctx._get(false, ["new_version_number"]),ctx,"h").write("). Download it here.
    ").reference(ctx._get(false, ["tOk. Stop showing this."]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); +(function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["nospc_stalled_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ").reference(ctx._get(false, ["tNo activities"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tETA"]),ctx,"h").write("").reference(ctx._get(false, ["tSpeed"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ");}return body_0;})(); (function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tRestore Linux image"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tTo restore your Linux disk please enter following in a terminal:"]),ctx,"h").write("

    TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_restore_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}return body_0;})(); (function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["nospc_fatal_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); +(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["image"]),ctx,{"else":body_1,"block":body_6},null).exists(ctx._get(false, ["show_details"]),ctx,{"block":body_7},null).exists(ctx._get(false, ["backups_interrupted"]),ctx,{"block":body_8},null).write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_10},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_11},null).write("
    ").exists(ctx._get(false, ["f_total_bytes"]),ctx,{"block":body_12},null).write("").reference(ctx._get(false, ["eta"]),ctx,"h").write("").exists(ctx._get(false, ["paused"]),ctx,{"else":body_13,"block":body_14},null).write("").reference(ctx._get(false, ["queue"]),ctx,"h").write("").exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_16},null).exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_18},null).write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["client_update"]),ctx,{"else":body_2,"block":body_5},null);}function body_2(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_3,"block":body_4},null);}function body_3(chk,ctx){return chk.write("-");}function body_4(chk,ctx){return chk.reference(ctx._get(false, ["tPath:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_5(chk,ctx){return chk.reference(ctx._get(false, ["tTo version:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["tVolume:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_7(chk,ctx){return chk.reference(ctx._get(false, ["details"]),ctx,"h");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackups interrupted"]),ctx,"h");}function body_9(chk,ctx){return chk.write("min-width: 2em;");}function body_10(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_11(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["f_done_bytes"]),ctx,"h").write(" / ").reference(ctx._get(false, ["f_total_bytes"]),ctx,"h").write("
    ");}function body_13(chk,ctx){return chk.reference(ctx._get(false, ["speed"]),ctx,"h");}function body_14(chk,ctx){return chk.reference(ctx._get(false, ["tPaused"]),ctx,"h");}function body_15(chk,ctx){return chk.write("");}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.write(" ");}function body_18(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["ONLY_WIN32_BEGIN"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["ONLY_WIN32_END"]),ctx,"h",["s"]).write("
    MBit/s
     
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}return body_0;})(); (function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tReport script"]),ctx,"h").write("

    \t\t

    ").exists(ctx._get(false, ["saved_ok"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("
    Saved script successfully.
    ");}return body_0;})(); -(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["nospc_stalled_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_every"]),ctx,"h").write("").reference(ctx._get(false, ["archive_for"]),ctx,"h").write("").reference(ctx._get(false, ["archive_window"]),ctx,"h").write("").reference(ctx._get(false, ["archive_backup_type_str"]),ctx,"h").write("").reference(ctx._get(false, ["archive_letters_str"]),ctx,"h").write("").exists(ctx._get(false, ["show_archive_timeleft"]),ctx,{"block":body_1},null).write("").exists(ctx._get(false, ["source_group"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["source_here"]),ctx,{"block":body_3},null).write("");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_timeleft"]),ctx,"h").write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("disabled");}return body_0;})(); -(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tGroup"]),ctx,"h").write(" ").reference(ctx._get(false, ["groupname"]),ctx,"h").write("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("
    ");}function body_1(chk,ctx){return chk.write("");}return body_0;})(); (function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ").reference(ctx._get(false, ["tNo activities"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_every"]),ctx,"h").write("").reference(ctx._get(false, ["archive_for"]),ctx,"h").write("").reference(ctx._get(false, ["archive_window"]),ctx,"h").write("").reference(ctx._get(false, ["archive_backup_type_str"]),ctx,"h").write("").reference(ctx._get(false, ["archive_letters_str"]),ctx,"h").write("").exists(ctx._get(false, ["show_archive_timeleft"]),ctx,{"block":body_1},null).write("").exists(ctx._get(false, ["source_group"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["source_here"]),ctx,{"block":body_3},null).write("");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_timeleft"]),ctx,"h").write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("disabled");}return body_0;})(); (function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest Mail sent successfully"]),ctx,"h").write(".
    ");}return body_0;})(); -(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSending test mail failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["mail_err"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSaved settings successfully"]),ctx,"h").write(".
    ");}return body_0;})(); (function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tClient"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("

    ").exists(ctx._get(false, ["groupmod"]),ctx,{"block":body_1},null).write("
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}function body_1(chk,ctx){return chk.write("
    Member of group
    ");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["tPermissions"]),ctx,"h").write("
  • ");}return body_0;})(); (function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["msg"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSending test mail failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["mail_err"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSaved settings successfully"]),ctx,"h").write(".
    ");}return body_0;})(); (function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange password for user"]),ctx,"h").write(": ").reference(ctx._get(false, ["username"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange rights for user"]),ctx,"h").write(": ").reference(ctx._get(false, ["username"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tDomain"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tTranslation"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tNew domain"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.write("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").exists(ctx._get(false, ["test_login"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["test_login_ok"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_err"]),ctx,"h").write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login succeeded. Rights of user:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_rights"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); -(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["rights"]),ctx,"h").write("").exists(ctx._get(false, ["can_change"]),ctx,{"block":body_1},null).write("");}function body_1(chk,ctx){return chk.write(" ");}return body_0;})(); -(function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLogs"]),ctx,"h").write("
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tErrors"]),ctx,"h").write("").reference(ctx._get(false, ["tWarnings"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLive Log"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tReports"]),ctx,"h").write("
    ").exists(ctx._get(false, ["has_user"]),ctx,{"else":body_1,"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tYou need to create a user to be able to send reports"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

     
    +
    ").exists(ctx._get(false, ["can_report_script_edit"]),ctx,{"block":body_3},null).write("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}function body_3(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tUsername"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["ONLY_WIN32_BEGIN"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["ONLY_WIN32_END"]),ctx,"h",["s"]).write("
    MBit/s
     
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}return body_0;})(); (function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_1},null).write("
    ").reference(ctx._get(false, ["tBackup Statistics"]),ctx,"h").write("
    ").notexists(ctx._get(false, ["maximized"]),ctx,{"block":body_2},null).write("\t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tImages"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles"]),ctx,"h").write("").reference(ctx._get(false, ["tAll"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSum"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tImages"]),ctx,"h").write("").reference(ctx._get(false, ["images_total"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tFiles"]),ctx,"h").write("").reference(ctx._get(false, ["files_total"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tAll"]),ctx,"h").write("").reference(ctx._get(false, ["used_total"]),ctx,"h").write("
    ").notexists(ctx._get(false, ["maximized"]),ctx,{"block":body_3},null).write("
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_4},null).write("
    ").reference(ctx._get(false, ["tStorage allocation"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_5},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("
    ");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tETA"]),ctx,"h").write("").reference(ctx._get(false, ["tSpeed"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ");}return body_0;})(); +(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["rights"]),ctx,"h").write("").exists(ctx._get(false, ["can_change"]),ctx,{"block":body_1},null).write("");}function body_1(chk,ctx){return chk.write(" ");}return body_0;})(); (function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo Users"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tUsername"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); +(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); (function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackup status"]),ctx,"h").write("
    ").reference(ctx._get(false, ["nospc_fatal"]),ctx,"h",["s"]).reference(ctx._get(false, ["nospc_stalled"]),ctx,"h",["s"]).reference(ctx._get(false, ["database_error"]),ctx,"h",["s"]).reference(ctx._get(false, ["endian_info"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tGroup name"]),ctx,"h").write("").reference(ctx._get(false, ["tOnline"]),ctx,"h").write("").reference(ctx._get(false, ["tStatus"]),ctx,"h").write("").reference(ctx._get(false, ["tLast seen"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("").reference(ctx._get(false, ["tLast image backup"]),ctx,"h").write("").reference(ctx._get(false, ["tFile backup status"]),ctx,"h").write("").reference(ctx._get(false, ["tImage backup status"]),ctx,"h").write("").reference(ctx._get(false, ["tIP"]),ctx,"h").write("").reference(ctx._get(false, ["tClient version"]),ctx,"h").write("").reference(ctx._get(false, ["tOperating System"]),ctx,"h").write("
    ").exists(ctx._get(false, ["status_can_show_all"]),ctx,{"block":body_2},null).reference(ctx._get(false, ["modify_clients"]),ctx,"h",["s"]).exists(ctx._get(false, ["has_client_download"]),ctx,{"block":body_3},null).exists(ctx._get(false, ["allow_add_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["removed_clients_table"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["status_extra_clients"]),ctx,{"block":body_8},null).write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["status_client_download_windows"]),ctx,"h",["s"]).reference(ctx._get(false, ["status_client_download_linux"]),ctx,"h",["s"]).write("
    ");}function body_4(chk,ctx){return chk.write("");}function body_5(chk,ctx){return chk.write("
    ").section(ctx._get(false, ["removed_clients"]),ctx,{"block":body_6},null).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write(" 
    ");}function body_6(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["tThis client is going to be removed. "]),ctx,"h").write(" ").exists(ctx._get(false, ["remove_client"]),ctx,{"block":body_7},null).reference(ctx._get(false, ["tClients are removed during the cleanup in the cleanup time window. "]),ctx,"h").write("");}function body_7(chk,ctx){return chk.write("").reference(ctx._get(false, ["tStop removing client"]),ctx,"h").write(". ");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient discovery hints"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["extra_clients_rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tHostname/IP"]),ctx,"h").write("").reference(ctx._get(false, ["tOnline"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); +(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tGroup"]),ctx,"h").write(" ").reference(ctx._get(false, ["groupname"]),ctx,"h").write("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("
    ");}function body_1(chk,ctx){return chk.write("");}return body_0;})(); +(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage of"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ");}return body_0;})(); +(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["images"]),ctx,"h").write("").reference(ctx._get(false, ["files"]),ctx,"h").write("").reference(ctx._get(false, ["used"]),ctx,"h").write("");}return body_0;})(); (function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["hostname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["groupname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write(" ").exists(ctx._get(false, ["online_add_status"]),ctx,{"block":body_2},null).write(" ").exists(ctx._get(false, ["reset_client_uid"]),ctx,{"block":body_3},null).write("").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastseen"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h").reference(ctx._get(false, ["start_file_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastbackup_image"]),ctx,"h").reference(ctx._get(false, ["start_image_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["file_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["image_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["ip"]),ctx,"h").write("").reference(ctx._get(false, ["client_version_string"]),ctx,"h").write("").reference(ctx._get(false, ["os_version_string"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("(").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write(")");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tAllow new client"]),ctx,"h").write("");}return body_0;})(); (function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.write("");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Windows"]),ctx,"h");}function body_2(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Mac OS X"]),ctx,"h");}function body_3(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Linux"]),ctx,"h");}return body_0;})(); +(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSelect all"]),ctx,"h").write("").reference(ctx._get(false, ["tSelect none"]),ctx,"h").write("").reference(ctx._get(false, ["rem_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["tRemove selected"]),ctx,"h").write("").reference(ctx._get(false, ["rem_stop"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); +(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["groupname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write(" ").exists(ctx._get(false, ["online_add_status"]),ctx,{"block":body_2},null).write(" ").exists(ctx._get(false, ["reset_client_uid"]),ctx,{"block":body_3},null).write("").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastseen"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h").reference(ctx._get(false, ["start_file_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastbackup_image"]),ctx,"h").reference(ctx._get(false, ["start_image_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["file_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["image_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["ip"]),ctx,"h").write("").reference(ctx._get(false, ["client_version_string"]),ctx,"h").write("").reference(ctx._get(false, ["os_version_string"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("(").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write(")");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tAllow new client"]),ctx,"h").write("");}return body_0;})(); (function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_2},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_3},null).write("
    ");}function body_1(chk,ctx){return chk.write("min-width: 2em;");}function body_2(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_3(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}return body_0;})(); (function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tmpdir_error_text"]),ctx,"h").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage of"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ");}return body_0;})(); -(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSelect all"]),ctx,"h").write("").reference(ctx._get(false, ["tSelect none"]),ctx,"h").write("").reference(ctx._get(false, ["rem_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["tRemove selected"]),ctx,"h").write("").reference(ctx._get(false, ["rem_stop"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); (function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.write("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").reference(ctx._get(false, ["virus_error_path"]),ctx,"h").write(" ).").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLogs"]),ctx,"h").write("
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tErrors"]),ctx,"h").write("").reference(ctx._get(false, ["tWarnings"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLive Log"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tReports"]),ctx,"h").write("
    ").exists(ctx._get(false, ["has_user"]),ctx,{"else":body_1,"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tYou need to create a user to be able to send reports"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

     
    +
    ").exists(ctx._get(false, ["can_report_script_edit"]),ctx,{"block":body_3},null).write("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}function body_3(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("");}return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAbout UrBackup"]),ctx,"h").write("
    UrBackup Server ").reference(ctx._get(false, ["version"]),ctx,"h").write("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}return body_0;})(); +(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.write("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").exists(ctx._get(false, ["test_login"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["test_login_ok"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_err"]),ctx,"h").write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login succeeded. Rights of user:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_rights"]),ctx,"h").write("
    ");}return body_0;})(); (function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["upgrade_error_text"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tCurrent version"]),ctx,"h").write(": ").reference(ctx._get(false, ["curr_db_version"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tTarget version"]),ctx,"h").write(": ").reference(ctx._get(false, ["target_db_version"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["images"]),ctx,"h").write("").reference(ctx._get(false, ["files"]),ctx,"h").write("").reference(ctx._get(false, ["used"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); -(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); -(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.exists(ctx._get(false, ["client_settings"]),ctx,{"else":body_1,"block":body_2},null).write("
    ").reference(ctx._get(false, ["thours"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    \t\t\t\t
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDays"]),ctx,"h").write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_6},null).write("\t\t\t").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_7},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_8},null).write("
    ").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tArchive every"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive for"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive window"]),ctx,"h").write(" ?").reference(ctx._get(false, ["tBackup type"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume letters"]),ctx,"h").write("").reference(ctx._get(false, ["tNext archival"]),ctx,"h").write("  
     ").exists(ctx._get(false, ["archive_global"]),ctx,{"block":body_9},null).reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("\t\t
    ").exists(ctx._get(false, ["can_edit_scripts"]),ctx,{"block":body_10},null).write("
    \t\t\t
    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("
    MBit/s
    ").reference(ctx._get(false, ["internet_settings_start"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_11},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_12},null).write("
    KBit/s
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_16},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_17},null).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_18},null).write("
    ").reference(ctx._get(false, ["internet_settings_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    \t\t\t
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["client_settings"]),ctx,{"block":body_19},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMin"]),ctx,"h").write("
    ");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_7(chk,ctx){return chk.write("
    ");}function body_8(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_9(chk,ctx){return chk.write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tEdit scripts"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("
    ");}function body_12(chk,ctx){return chk.notexists(ctx._get(false, ["global_settings"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["with_authkey"]),ctx,{"block":body_14},null);}function body_13(chk,ctx){return chk.write("
    ");}function body_14(chk,ctx){return chk.write("
    ");}function body_15(chk,ctx){return chk.write("
    KBit/s
    ");}function body_16(chk,ctx){return chk.write("
    ");}function body_17(chk,ctx){return chk.write("
    ");}function body_18(chk,ctx){return chk.write("
    ");}function body_19(chk,ctx){return chk.write("
    ");}return body_0;})(); +(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.exists(ctx._get(false, ["client_settings"]),ctx,{"else":body_1,"block":body_2},null).write("
    ").reference(ctx._get(false, ["thours"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    \t\t\t\t
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDays"]),ctx,"h").write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_6},null).write("\t\t\t").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_7},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_8},null).write("
    ").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tArchive every"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive for"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive window"]),ctx,"h").write(" ?").reference(ctx._get(false, ["tBackup type"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume letters"]),ctx,"h").write("").reference(ctx._get(false, ["tNext archival"]),ctx,"h").write("  
     ").exists(ctx._get(false, ["archive_global"]),ctx,{"block":body_9},null).reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("\t\t
    ").exists(ctx._get(false, ["can_edit_scripts"]),ctx,{"block":body_10},null).write("
    \t\t\t
    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("
    MBit/s
    ").reference(ctx._get(false, ["internet_settings_start"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_11},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_12},null).write("
    KBit/s
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_16},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_17},null).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_18},null).write("
    ").reference(ctx._get(false, ["internet_settings_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    \t\t\t
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["client_settings"]),ctx,{"block":body_19},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMin"]),ctx,"h").write("
    ");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_7(chk,ctx){return chk.write("
    ");}function body_8(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_9(chk,ctx){return chk.write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tEdit scripts"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("
    ");}function body_12(chk,ctx){return chk.notexists(ctx._get(false, ["global_settings"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["with_authkey"]),ctx,{"block":body_14},null);}function body_13(chk,ctx){return chk.write("
    ");}function body_14(chk,ctx){return chk.write("
    ");}function body_15(chk,ctx){return chk.write("
    KBit/s
    ");}function body_16(chk,ctx){return chk.write("
    ");}function body_17(chk,ctx){return chk.write("
    ");}function body_18(chk,ctx){return chk.write("
    ");}function body_19(chk,ctx){return chk.write("
    ");}return body_0;})(); diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index 60477a70e..cab31de1c 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -168,8 +168,8 @@ '; - c+='
    '; - c+='
    '; + if(typeof val.value_client != "undefined") + { + c+='
    '; + c+=''; + c+='
    '; + c+='
    '; + } /* c+='
    '; c+=''; @@ -3086,11 +3094,17 @@ function renderMergeSetting(key) $("#"+key+"_check_group").change(mergeSettingSwitch); $("#"+key+"_check_home").change(mergeSettingSwitch); - $("#"+key+"_check_client").change(mergeSettingSwitch); + if(typeof val.value_client != "undefined") + { + $("#"+key+"_check_client").change(mergeSettingSwitch); + } $("#"+key+"_check_group").bootstrapToggle(); $("#"+key+"_check_home").bootstrapToggle(); - $("#"+key+"_check_client").bootstrapToggle(); + if(typeof val.value_client != "undefined") + { + $("#"+key+"_check_client").bootstrapToggle(); + } } function renderMergeSettingSwitch(key) { @@ -3116,13 +3130,16 @@ function renderMergeSettingSwitch(key) I(key+"_check_home").checked=false; } - if(use&4) + if(I(key+"_check_client")) { - I(key+"_check_client").checked=true; - } - else - { - I(key+"_check_client").checked=false; + if(use&4) + { + I(key+"_check_client").checked=true; + } + else + { + I(key+"_check_client").checked=false; + } } } function renderSettingSwitchAll() From 25782538c4600989121a634d0da815e11a16fe84 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 30 Jun 2021 01:04:59 +0200 Subject: [PATCH 075/469] Select correct use value for old settings lists --- urbackupserver/ClientMain.cpp | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index d822a2f06..3ad041ea1 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -1998,12 +1998,9 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) std::auto_ptr sr(Server->createFileSettingsReader(tmp_fn)); - std::vector setting_names=getSettingsList(); + std::vector setting_names=getClientConfigurableSettingsList(); bool mod=false; - - std::vector only_server_settings = getOnlyServerClientSettingsList(); - bool has_use = false; for (size_t i = 0; i < setting_names.size(); ++i) @@ -2015,18 +2012,19 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) break; } } + + int def_use = c_use_group; + + if (sr->getValue("client_set_settings") == "true") + { + def_use = c_use_value_client; + } for(size_t i=0;igetValue(key + ".use", &value)) @@ -2051,14 +2049,15 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) } } else if (!has_use - && sr->getValue(key, &value)) + && sr->getValue(key, &value) + && def_use==c_use_value_client) { if (internet_connection && key == "computername") { continue; } - bool b = updateClientSetting(key, value, c_use_undefined, 0); + bool b = updateClientSetting(key, value, def_use, 0); if (b) mod = true; } From 3101ccf9fa2aeaf547f43edaba9636875a4fd7cf Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 9 Jul 2021 19:31:19 +0200 Subject: [PATCH 076/469] Fix mail retries (cherry picked from commit aac93b970ddbe11dd565db4e4cc2e17dea775c82) (cherry picked from commit f7269527e464a925ed144105cfc363192f9a2343) --- urbackupserver/Mailer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/Mailer.cpp b/urbackupserver/Mailer.cpp index 85becd872..ab3108400 100644 --- a/urbackupserver/Mailer.cpp +++ b/urbackupserver/Mailer.cpp @@ -86,7 +86,7 @@ void Mailer::operator()() } IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); - IQuery* q_get_mail = db->Prepare("SELECT id, send_to, subject, message, next_try, retry_count FROM mail_queue WHERE next_try IS NULL or next_try>=?"); + IQuery* q_get_mail = db->Prepare("SELECT id, send_to, subject, message, next_try, retry_count FROM mail_queue WHERE next_try IS NULL or next_try<=?"); IQuery* q_set_retry = db->Prepare("UPDATE mail_queue SET next_try=?, retry_count=? WHERE id=?"); IQuery* q_remove_mail = db->Prepare("DELETE FROM mail_queue WHERE id=?"); From 4fcdcd8e18b9ab32561f8203f35ec3d617fae0b5 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 20 Jul 2021 19:45:34 +0200 Subject: [PATCH 077/469] Do not trim if trim range is narrowed down to zero (cherry picked from commit 09e8f65d8f2cb2b8d3ae76f47728ee9b84776f6d) (cherry picked from commit 1ae295a1f53ba0e516d7e6865031f7dd3adbed35) --- fsimageplugin/cowfile.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/fsimageplugin/cowfile.cpp b/fsimageplugin/cowfile.cpp index 135a9563c..50c491a59 100644 --- a/fsimageplugin/cowfile.cpp +++ b/fsimageplugin/cowfile.cpp @@ -828,7 +828,8 @@ bool CowFile::trimUnused(_i64 fs_offset, _i64 trim_blocksize, ITrimCallback* tri unused_end = filesize; } - if(hasBitmapRangeNarrow(unused_start, unused_end, trim_blocksize_bytes)) + if(hasBitmapRangeNarrow(unused_start, unused_end, trim_blocksize_bytes) + && unused_end>unused_start) { if(!setUnused(unused_start, unused_end)) { @@ -849,7 +850,8 @@ bool CowFile::trimUnused(_i64 fs_offset, _i64 trim_blocksize, ITrimCallback* tri int64 unused_start = fs_offset + unused_start_block*bitmap_blocksize; int64 unused_end = filesize; - if(hasBitmapRangeNarrow(unused_start, unused_end, trim_blocksize_bytes)) + if(hasBitmapRangeNarrow(unused_start, unused_end, trim_blocksize_bytes) + && unused_end>unused_start) { if(!setUnused(unused_start, unused_end)) { From 493f32fea0bf5aae4f9db45b687fa86f764cd7bd Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 8 Mar 2021 00:48:53 +0100 Subject: [PATCH 078/469] Force unlocking of CBT mutex in case creating shadowcopy fails (cherry picked from commit cfd25682108e6f185c5afba9371fd826dcefc0b7) (cherry picked from commit 6008d2461e7d12ecac8d0026b12b6267bc0629de) --- urbackupclient/client.cpp | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 996dd8dd4..f8f125f3e 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -286,6 +286,20 @@ namespace assert(cbtMutexLocked>=0); } + void force_unlock_cbt_mutex() + { + assert(cbtMutex != NULL); + if (cbtMutex == NULL) + { + return; + } + if (cbtMutexLocked > 0) + { + ReleaseMutex(cbtMutex); + cbtMutexLocked = 0; + } + } + struct ScopedUnlockCbtMutex { ~ScopedUnlockCbtMutex() { @@ -1072,6 +1086,8 @@ void IndexThread::operator()(void) } else { + force_unlock_cbt_mutex(); + if (!disableCbt(scd->orig_target)) { VSSLog("Error disabling change block tracking for " + scd->orig_target+" (2)", LL_ERROR); @@ -1113,6 +1129,8 @@ void IndexThread::operator()(void) Server->Log("done.", LL_DEBUG); if(!b || scd->ref==NULL) { + force_unlock_cbt_mutex(); + if(scd->fileserv) { shareDir(std::string(), scd->dir, scd->target); @@ -1615,6 +1633,8 @@ IndexThread::IndexErrorInfo IndexThread::indexDirs(bool full_backup, bool simult VSS_ID ssetid; if (!start_shadowcopy_components(ssetid, &has_active_transaction)) { + force_unlock_cbt_mutex(); + index_error = true; VSSLog("Indexing Windows components failed", LL_ERROR); return IndexErrorInfo_Error; From 82bbea39bd2e295d0d0314ad32125d3b86fdb653 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 19 Feb 2021 18:29:00 +0100 Subject: [PATCH 079/469] Delete other client bitmap after having it incorporated (cherry picked from commit 512469501f310aebd9f952d0f8c23ff7db667828) (cherry picked from commit af624bb657e42fc4a948d7a493bdac495cad0fcb) --- urbackupclient/client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index f8f125f3e..fa5e34109 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -7249,6 +7249,8 @@ bool IndexThread::finishCbt(std::string volume, int shadow_id, std::string snap_ Server->deleteFile("urbackup\\hdat_file_" + conv_filename(strlower(volume)) + ".cbt"); } //for_image_backup + Server->deleteFile("urbackup\\hdat_other_" + conv_filename(strlower(volume)) + ".cbt"); + #ifndef _DEBUG b = DeviceIoControl(hVolume, IOCTL_URBCT_RESET_FINISH, NULL, 0, NULL, 0, &bytesReturned, NULL); #endif From 8d27573a81e9bfb1d7d1d83f81628c4b888f1c43 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 6 Aug 2020 10:21:42 +0200 Subject: [PATCH 080/469] Fix profile enumeration (cherry picked from commit cc3d82047bf12725ba517c02f436340a927a69f2) --- urbackupclient/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index fa5e34109..3ba4a9847 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -7000,7 +7000,7 @@ bool IndexThread::finishCbt(std::string volume, int shadow_id, std::string snap_ { wchar_t buf[300]; - for (DWORD i = 0; RegEnumKeyW(profile_list, i, buf, sizeof(buf) == ERROR_SUCCESS); ++i) + for (DWORD i = 0; RegEnumKeyW(profile_list, i, buf, sizeof(buf)) == ERROR_SUCCESS; ++i) { DWORD rsize = sizeof(buf) * sizeof(wchar_t); if (RegGetValueW(profile_list, std::wstring(buf).c_str(), L"ProfileImagePath", From 4a7e2db0101528a3c7c8d15f4923cec9af082888 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 18 Dec 2020 19:20:38 +0100 Subject: [PATCH 081/469] Continue receiving as long as pipe is readable if using compressed/encrypted pipe --- urbackupclient/ClientService.cpp | 31 +++++++++++++------------------ urbackupclient/ClientService.h | 2 ++ 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index bfa58f34e..4b467c077 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -284,6 +284,7 @@ void ClientConnector::Init(THREAD_ID pTID, IPipe *pPipe, const std::string& pEnd { tid=pTID; pipe=pPipe; + orig_pipe = pipe; state=CCSTATE_NORMAL; image_inf.thread_action=TA_NONE; image_inf.image_thread=NULL; @@ -813,6 +814,14 @@ bool ClientConnector::writeUpdateFile(IFile *datafile, std::string outfn) } void ClientConnector::ReceivePackets(IRunOtherCallback* p_run_other) +{ + do + { + ReceivePacketsInt(p_run_other); + } while (pipe != orig_pipe && pipe->isReadable()); +} + +void ClientConnector::ReceivePacketsInt(IRunOtherCallback* p_run_other) { run_other = p_run_other; @@ -944,29 +953,15 @@ void ClientConnector::ReceivePackets(IRunOtherCallback* p_run_other) } } else - { - return; + { + return; + } } - } tcpstack.AddData(cmd.data(), cmd.size()); - while(true) + while(tcpstack.getPacket(cmd) && !cmd.empty()) { - if (!tcpstack.getPacket(cmd) || cmd.empty()) - { - size_t rc = pipe->Read(&cmd, 0); - if (rc > 0) - { - tcpstack.AddData(cmd.data(), cmd.size()); - continue; - } - else - { - break; - } - } - Server->Log("ClientService cmd: "+cmd, LL_DEBUG); bool pw_ok=false; diff --git a/urbackupclient/ClientService.h b/urbackupclient/ClientService.h index 0bb3dd942..53cec6d97 100644 --- a/urbackupclient/ClientService.h +++ b/urbackupclient/ClientService.h @@ -241,6 +241,7 @@ class ClientConnector : public ICustomClient static bool updateDefaultDirsSetting(IDatabase *db, bool all_virtual_clients, int group_offset, bool update_use); private: + void ReceivePacketsInt(IRunOtherCallback* run_other); bool checkPassword(const std::string &cmd, bool& change_pw); bool saveBackupDirs(str_map &args, bool server_default, int group_offset); std::string replaceChars(std::string in); @@ -363,6 +364,7 @@ class ClientConnector : public ICustomClient unsigned int curr_result_id; IPipe *pipe; + IPipe* orig_pipe; THREAD_ID tid; ClientConnectorState state; int64 lasttime; From f9f44a9ab6d82e35bc670c5c392bc6fb4ec5cbb2 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 31 Jul 2021 16:32:10 +0200 Subject: [PATCH 082/469] Condition looping while pipe is readable --- urbackupclient/ClientService.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index ee7fb214d..bae2fc500 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -818,7 +818,11 @@ void ClientConnector::ReceivePackets(IRunOtherCallback* p_run_other) do { ReceivePacketsInt(p_run_other); - } while (pipe != orig_pipe && pipe->isReadable()); + } while (pipe != orig_pipe + && wantReceive() + && state!= CCSTATE_UPDATE_FINISH + && !do_quit + && pipe->isReadable()); } void ClientConnector::ReceivePacketsInt(IRunOtherCallback* p_run_other) From 6c89d589a814da77e749ffacd7cc5f3c6bdc3a95 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 8 May 2021 09:20:14 +0200 Subject: [PATCH 083/469] Don't require correct client uid for internet/active clients (cherry picked from commit b238f99eda26fe8cc0073906f15aee666238a702) (cherry picked from commit c7ce7abf7af14795710fd010dc2f14faf3d81063) --- urbackupserver/ClientMain.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index 3ad041ea1..f854f8b01 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -1760,9 +1760,11 @@ bool ClientMain::updateCapabilities(bool* needs_restart) if (curr_uid.exists && !curr_uid.value.empty() && it->second!=curr_uid.value && - server_settings->getSettings()->local_encrypt ) + server_settings->getSettings()->local_encrypt && + !internet_connection) { - ServerLogger::Log(logid, "Client UID changed from \"" + curr_uid.value + "\" to \"" + it->second + "\". Disallowing client because connection to client is encrypted.", LL_WARNING); + ServerLogger::Log(logid, "Client UID changed from \"" + curr_uid.value + "\" to \"" + it->second + "\". " + "Disallowing client because connection to local/passive client is encrypted.", LL_WARNING); ServerStatus::setStatusError(clientname, se_uid_changed); return false; } From 5fb8d84b28f5bdd23bfe02cb5bc14ed46c4a0d64 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 8 Aug 2021 09:40:47 +0200 Subject: [PATCH 084/469] Don't require correct client uid for internet/active clients (cherry picked from commit 6bc27673b43cd81e7f6a4b1affc053ec01fd277c) --- urbackupserver/ClientMain.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index f854f8b01..f89289f51 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -1780,7 +1780,8 @@ bool ClientMain::updateCapabilities(bool* needs_restart) ServerBackupDao::CondString curr_uid = backup_dao->getClientUid(clientid); if (curr_uid.exists && !curr_uid.value.empty() && - server_settings->getSettings()->local_encrypt) + server_settings->getSettings()->local_encrypt && + !internet_connection) { ServerLogger::Log(logid, "Client UID not received from client. Expecting \"" + curr_uid.value + "\". Disallowing client because connection to client is encrypted.", LL_WARNING); ServerStatus::setStatusError(clientname, se_uid_changed); From 49036c8096f83262ab31b9817186ea77376936c3 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 9 Aug 2021 21:15:37 +0200 Subject: [PATCH 085/469] Fix internet server url validation if empty --- urbackupserver/www/js/urbackup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 343108413..21a987a1c 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -4400,7 +4400,8 @@ function getInternetSettings() var server_regex = /(((;|^)(([\w-]+(\.[\w-]*)*)|((?!0)(?!.*\.)((1?\d?\d|25[0-5]|2[0-4]\d)(\.)){4})))+$)|(^$)|(^(ws|wss):\/\/[\w-]+([\w-]*)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$)|(^(urbackup):\/\/[\w-]+([\w-]*)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$)/i; - if(!server_regex.test(internet_server)) + if(internet_server.length>0 && internet_server!=="urbackup://" && + !server_regex.test(internet_server)) { alert(trans("validate_err_notregexp_internet_server")); return null; From c14fd9fb0d240152226df1e3c3b39f4fec82d43b Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 9 Aug 2021 22:27:01 +0200 Subject: [PATCH 086/469] Fix Linux build --- urbackupclient/client.cpp | 62 +++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 28 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 3ba4a9847..731ac86c6 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -171,72 +171,72 @@ namespace struct ON_DISK_USN_JOURNAL_DATA { - uint64 MaximumSize; + uint64 MaximumSize; uint64 AllocationDelta; uint64 UsnJournalID; int64 LowestValidUsn; }; - int64 getUsnNum( const std::string& dir, int64& sequence_id ) + int64 getUsnNum(const std::string& dir, int64& sequence_id) { - WCHAR volume_path[MAX_PATH]; + WCHAR volume_path[MAX_PATH]; BOOL ok = GetVolumePathNameW(Server->ConvertToWchar(dir).c_str(), volume_path, MAX_PATH); - if(!ok) + if (!ok) { Server->Log("GetVolumePathName(dir, volume_path, MAX_PATH) failed in getUsnNum", LL_ERROR); return -1; } - std::string vol=Server->ConvertFromWchar(volume_path); + std::string vol = Server->ConvertFromWchar(volume_path); - if(vol.size()>0) + if (vol.size() > 0) { - if(vol[vol.size()-1]=='\\') + if (vol[vol.size() - 1] == '\\') { - vol.erase(vol.size()-1,1); + vol.erase(vol.size() - 1, 1); } } - if(!vol.empty() && vol[0]!='\\') + if (!vol.empty() && vol[0] != '\\') { - vol = "\\\\.\\"+vol; + vol = "\\\\.\\" + vol; } - HANDLE hVolume=CreateFileW(Server->ConvertToWchar(vol).c_str(), GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - if(hVolume==INVALID_HANDLE_VALUE) + HANDLE hVolume = CreateFileW(Server->ConvertToWchar(vol).c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hVolume == INVALID_HANDLE_VALUE) { - Server->Log("CreateFile of volume '"+vol+"' failed. - getUsnNum", LL_ERROR); + Server->Log("CreateFile of volume '" + vol + "' failed. - getUsnNum", LL_ERROR); return -1; } USN_JOURNAL_DATA data; DWORD r_bytes; - BOOL b=DeviceIoControl(hVolume, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &data, sizeof(USN_JOURNAL_DATA), &r_bytes, NULL); + BOOL b = DeviceIoControl(hVolume, FSCTL_QUERY_USN_JOURNAL, NULL, 0, &data, sizeof(USN_JOURNAL_DATA), &r_bytes, NULL); CloseHandle(hVolume); - if(b) + if (b) { - sequence_id=data.UsnJournalID; + sequence_id = data.UsnJournalID; return data.NextUsn; } else { - std::auto_ptr journal_info(Server->openFile(vol+"\\$Extend\\$UsnJrnl:$Max", MODE_READ_SEQUENTIAL_BACKUP)); + std::auto_ptr journal_info(Server->openFile(vol + "\\$Extend\\$UsnJrnl:$Max", MODE_READ_SEQUENTIAL_BACKUP)); - if(journal_info.get()==NULL) return -1; + if (journal_info.get() == NULL) return -1; ON_DISK_USN_JOURNAL_DATA journal_data = {}; - if(journal_info->Read(reinterpret_cast(&journal_data), sizeof(journal_data))!=sizeof(journal_data)) + if (journal_info->Read(reinterpret_cast(&journal_data), sizeof(journal_data)) != sizeof(journal_data)) { return -1; } sequence_id = journal_data.UsnJournalID; - std::auto_ptr journal(Server->openFile(vol+"\\$Extend\\$UsnJrnl:$J", MODE_READ_SEQUENTIAL_BACKUP)); + std::auto_ptr journal(Server->openFile(vol + "\\$Extend\\$UsnJrnl:$J", MODE_READ_SEQUENTIAL_BACKUP)); - if(journal.get()==NULL) return -1; + if (journal.get() == NULL) return -1; return journal->Size(); } @@ -257,7 +257,7 @@ namespace { return false; } - if (cbtMutexLocked>0) + if (cbtMutexLocked > 0) { ++cbtMutexLocked; return true; @@ -283,7 +283,7 @@ namespace { ReleaseMutex(cbtMutex); } - assert(cbtMutexLocked>=0); + assert(cbtMutexLocked >= 0); } void force_unlock_cbt_mutex() @@ -314,16 +314,16 @@ namespace 0, NULL, 0, KEY_ALL_ACCESS, NULL, &urbackup_cbt_key, NULL) == ERROR_SUCCESS) { WCHAR szBuffer[8192]; - DWORD dwBufferSize = sizeof(szBuffer)*sizeof(WCHAR); + DWORD dwBufferSize = sizeof(szBuffer) * sizeof(WCHAR); ULONG nError; DWORD dwType = REG_MULTI_SZ; nError = RegQueryValueExW(urbackup_cbt_key, L"cbt_paths", 0, &dwType, (LPBYTE)szBuffer, &dwBufferSize); RegCloseKey(urbackup_cbt_key); if (ERROR_SUCCESS == nError - && dwType==REG_MULTI_SZ) + && dwType == REG_MULTI_SZ) { - std::wstring rval(szBuffer, szBuffer + dwBufferSize/sizeof(wchar_t)); + std::wstring rval(szBuffer, szBuffer + dwBufferSize / sizeof(wchar_t)); std::string strValue = Server->ConvertFromWchar(rval); std::vector toks; std::string sep; @@ -374,14 +374,20 @@ namespace 0, NULL, 0, KEY_ALL_ACCESS, NULL, &urbackup_cbt_key, NULL) == ERROR_SUCCESS) { LSTATUS status = RegSetValueExW(urbackup_cbt_key, L"cbt_paths", 0, - REG_MULTI_SZ, reinterpret_cast(data.c_str()), static_cast((data.size() + 1)*sizeof(wchar_t))); + REG_MULTI_SZ, reinterpret_cast(data.c_str()), static_cast((data.size() + 1) * sizeof(wchar_t))); RegCloseKey(urbackup_cbt_key); return status == ERROR_SUCCESS; } return false; } -#endif +#else //!_WIN32 + + void force_unlock_cbt_mutex() + { + } + +#endif //!_WIN32 #ifndef _WIN32 std::string getFolderMount(const std::string& path) From 95684b8f34e9bc9aee3f10ca243d225ab686f195 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 9 Aug 2021 23:14:43 +0200 Subject: [PATCH 087/469] Fix ZFS incremental file backups (cherry picked from commit c139b09cb59cef002c6a570848489c23fcd96418) --- urbackupserver/IncrFileBackup.cpp | 6 ++++-- urbackupserver/snapshot_helper.cpp | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/urbackupserver/IncrFileBackup.cpp b/urbackupserver/IncrFileBackup.cpp index ba6fdf908..199a1136e 100644 --- a/urbackupserver/IncrFileBackup.cpp +++ b/urbackupserver/IncrFileBackup.cpp @@ -323,9 +323,11 @@ bool IncrFileBackup::doFileBackup() } ServerLogger::Log(logid, clientname+": Creating snapshot...", LL_INFO); + + std::string snap_startup_del = zfs_file ? "" : ".startup-del"; std::string errmsg; - if(!SnapshotHelper::snapshotFileSystem(false, clientname, last.path, backuppath_single+ ".startup-del", errmsg) - || !SnapshotHelper::isSubvolume(false, clientname, backuppath_single+ ".startup-del") ) + if(!SnapshotHelper::snapshotFileSystem(false, clientname, last.path, backuppath_single+ snap_startup_del, errmsg) + || !SnapshotHelper::isSubvolume(false, clientname, backuppath_single+ snap_startup_del) ) { errmsg = trim(errmsg); ServerLogger::Log(logid, "Creating new snapshot failed (Server error) " diff --git a/urbackupserver/snapshot_helper.cpp b/urbackupserver/snapshot_helper.cpp index 4441464f5..bf1229879 100644 --- a/urbackupserver/snapshot_helper.cpp +++ b/urbackupserver/snapshot_helper.cpp @@ -58,7 +58,12 @@ bool SnapshotHelper::snapshotFileSystem(bool image, std::string clientname, std: bool SnapshotHelper::removeFilesystem(bool image, std::string clientname, std::string name) { - int rc=system((helper_name + " " + convert(BackupServer::getSnapshotMethod(image)) + " remove \""+(clientname)+"\" \""+(name)+"\"").c_str()); + if (!image && + BackupServer::getSnapshotMethod(image) == BackupServer::ESnapshotMethod_ZfsFile && + name.find(".startup-del") != std::string::npos) + name = greplace(".startup-del", "", name); + + int rc=system((helper_name + " " + convert(BackupServer::getSnapshotMethod(image)) + " remove \""+clientname+"\" \""+name+"\"").c_str()); return rc==0; } From 0c541391686ae1af77bd42a853ba4da71ed14bdc Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 11 Aug 2021 21:10:52 +0200 Subject: [PATCH 088/469] Increment version --- configure.ac_client | 2 +- configure.ac_server | 2 +- urbackupserver/www/js/urbackup.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 12094a8f6..8ef2dda51 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.15.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.16.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/configure.ac_server b/configure.ac_server index 70ca91ecd..aed98ac01 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.21.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.22.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 343108413..7dfa68005 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5,7 +5,7 @@ g.startup=true; g.no_tab_mouse_click=false; g.tabberidx=-1; g.progress_stop_id=-1; -g.current_version=2005001300; +g.current_version=2005002200; g.status_show_all=false; g.ldap_login=false; g.datatable_default_config={}; From 36dc0f53f8b887052f2949e75d4665190ac96441 Mon Sep 17 00:00:00 2001 From: Moisie2000 Date: Fri, 24 Sep 2021 16:16:45 +0100 Subject: [PATCH 089/469] Rework macOS file exclusions: - To account for the lack of a built-in list of exclusions in modern releases of macOS; - To more comprehensively exclude files which the OS prevents reading of via System Integrity Protection; - To exclude files which have an extended attribute set to prevent backups; - Moves the building of the exclusion list from the client installer into the main code (preventing the possibility of an outdated exclusion list on an updated OS); - Generates log entries showing the matching exclusion term which causes a file to not be backed up; - Creates a file at /Library/Application Support/UrBackup Client/var/urbackup/macOS_exclusion_overrides.txt to allow for exclusion terms to be overridden. (cherry picked from commit 4efd0bcbfc03182c74a3bbb9b83384f0bb3a8ac0) # Conflicts: # create_osx_installer.sh # osx_installer/buildmacOSexclusions # osx_installer/scripts2/postinstall --- create_osx_installer.sh | 5 +- osx_installer/buildmacOSexclusions | 97 ------ osx_installer/macOS_exclusion_overrides.txt | 21 ++ osx_installer/scripts2/postinstall | 7 +- urbackupclient/client.cpp | 321 +++++++++++++++++--- urbackupclient/client.h | 19 +- urbackupcommon/os_functions_lin.cpp | 22 ++ 7 files changed, 341 insertions(+), 151 deletions(-) delete mode 100755 osx_installer/buildmacOSexclusions create mode 100644 osx_installer/macOS_exclusion_overrides.txt diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 9d3763135..30b055c3a 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -50,13 +50,16 @@ make install DESTDIR=$PWD/osx-pkg2 mkdir -p "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin" mkdir -p "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS" mkdir -p "osx-pkg2/Applications/UrBackup Client.app/Contents/Resources" + cp osx_installer/info.plist "osx-pkg2/Applications/UrBackup Client.app/Contents/Info.plist" + cp osx_installer/urbackup.icns "osx-pkg2/Applications/UrBackup Client.app/Contents/Resources/" -cp osx_installer/buildmacOSexclusions "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/buildmacOSexclusions" +cp osx_installer/macOS_exclusion_overrides.txt "osx-pkg2/Applications/UrBackup Client.app/Contents/Resources/" mv "osx-pkg2/Library/Application Support" "osx-pkg/Library" rm -R "osx-pkg2/Library" mv "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/urbackupclientgui" "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/" + if !($development); then strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/urbackupclientgui" strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/sbin/urbackupclientbackend" diff --git a/osx_installer/buildmacOSexclusions b/osx_installer/buildmacOSexclusions deleted file mode 100755 index ada20049b..000000000 --- a/osx_installer/buildmacOSexclusions +++ /dev/null @@ -1,97 +0,0 @@ -#!/bin/bash - -# DEBUGGING CONTROLS -# set -x -# trap read debug - - -#### Functions - -tidy_item() -{ - local trimmed1="$(echo -e "$1" | sed -e 's/[[:blank:]\",]*$//')" #Remove whitespace, the quote and comma at the end of each line - local trimmed2="$(echo -e "$trimmed1" | sed -e 's/^[[:blank:]\"]*//')" # Remove whitespace and quote at the start of the line - echo "$trimmed2" -} - - -#### Main - -force_rebuild=false -exclude_file="/Library/Application Support/UrBackup Client/var/urbackup/macos_exclusions.txt" - - -# Check if rebuild has been forced -if [ "$1" != "" ]; then - if [[ "$1" == "-f" ]] || [[ "$1" == "--force" ]]; then - force_rebuild=true - fi -fi - - -# Check macos_exclusion file doesn't already exist -if [ -f "$exclude_file" ]; then - if !($force_rebuild); then - exit 0 - else - rm "$exclude_file" - fi -fi - - -paths_excluded=$(defaults read "/System/Library/CoreServices/backupd.bundle/Contents/Resources/StdExclusions.plist" PathsExcluded) -contents_excluded=$(defaults read "/System/Library/CoreServices/backupd.bundle/Contents/Resources/StdExclusions.plist" ContentsExcluded) -file_contents_excluded=$(defaults read "/System/Library/CoreServices/backupd.bundle/Contents/Resources/StdExclusions.plist" FileContentsExcluded) -user_paths_excluded=$(defaults read "/System/Library/CoreServices/backupd.bundle/Contents/Resources/StdExclusions.plist" UserPathsExcluded) - - -paths_excluded_array=() - -IFS=$'\n' -for item in $paths_excluded; do - if [[ "$item" != "(" ]] && [[ "$item" != ")" ]]; then # Remove the leading and trailing paratheses - tidied="$(echo -e $(tidy_item $item))" - paths_excluded_array+=("$tidied") - fi -done - -for item in $contents_excluded; do - if [[ "$item" != "(" ]] && [[ "$item" != ")" ]]; then # Remove the leading and trailing paratheses - tidied="$(echo -e $(tidy_item $item))" - paths_excluded_array+=("$tidied/*") - fi -done - -# Need to refine this -for item in $file_contents_excluded; do - if [[ "$item" != "(" ]] && [[ "$item" != ")" ]]; then # Remove the leading and trailing paratheses - tidied="$(echo -e $(tidy_item $item))" - paths_excluded_array+=("$tidied/*") - fi -done - -for item in $user_paths_excluded; do - if [[ "$item" != "(" ]] && [[ "$item" != ")" ]]; then # Remove the leading and trailing paratheses - tidied="$(echo -e $(tidy_item $item))" - paths_excluded_array+=("/Users/*/$tidied") - fi -done - -# Hardcode paths for DataVaults - -paths_excluded_array+=("/Users/*/Library/VoiceTrigger/SAT") -paths_excluded_array+=("/Users/*/Library/Containers/com.apple.mail/Data/DataVaults") -paths_excluded_array+=("/var/folders/*/*/*/com.apple.nsurlsessiond") - - -set -f -sorted_array=($(sort <<<"${paths_excluded_array[*]}")) - -mkdir -p "/Library/Application Support/UrBackup Client/var/urbackup" -touch $exclude_file - -for i in "${sorted_array[@]}"; do - echo "$i" >> $exclude_file -done - -exit 0 \ No newline at end of file diff --git a/osx_installer/macOS_exclusion_overrides.txt b/osx_installer/macOS_exclusion_overrides.txt new file mode 100644 index 000000000..b05422347 --- /dev/null +++ b/osx_installer/macOS_exclusion_overrides.txt @@ -0,0 +1,21 @@ +# macos_exclusion_overrides.txt +# +# This file determines which of the standard macOS backup exclusions, as defined +# in Urbackup, are overridden - so as to force their inclusion in backups. +# +# This should not normally be required - and indeed, forcing the backup of some items +# may produce errors in the backup if the system does not allow access to them. +# +# The specific exclusion required to be overridden can be found in the Urbackup Client +# logfile - /Library/Logs/urbackup_client_backend.log +# Any item which is excluded due to a standard backup exclusion is noted against the +# exclusion term which prevents its backup. +# +# So, for example, to override the exclusion of the .Trash folder inside each user +# account, uncomment the following entry: +# +# /Users/:/.Trash +# +# Override entries should be exactly as quoted in the Urbackup Client logfile, and +# should be entered one-per-line without a preceding # + diff --git a/osx_installer/scripts2/postinstall b/osx_installer/scripts2/postinstall index 3389b7682..3c06c0c65 100755 --- a/osx_installer/scripts2/postinstall +++ b/osx_installer/scripts2/postinstall @@ -2,6 +2,12 @@ set -e +if test ! -f "/Library/Application Support/UrBackup Client/var/urbackup/macOS_exclusion_overrides.txt" +then + cp "$2/Contents/Resources/macOS_exclusion_overrides.txt" "/Library/Application Support/UrBackup Client/var/urbackup/macOS_exclusion_overrides.txt" + chmod 755 "/Library/Application Support/UrBackup Client/var/urbackup/macOS_exclusion_overrides.txt" +fi + if test -e "$1.cfg" then cp "$1.cfg" "/Library/Application Support/UrBackup Client/var/urbackup/initial_settings.cfg" @@ -29,4 +35,3 @@ else done fi -"$2/Contents/MacOS/bin/buildmacOSexclusions" diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 731ac86c6..a1d4f3022 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -45,6 +45,13 @@ #include "../fileservplugin/chunk_settings.h" #include "ImageThread.h" #include "../common/adler32.h" +#include + +#ifdef __APPLE__ +#include +#include +#include +#endif //For truncating files #ifdef _WIN32 @@ -108,6 +115,11 @@ const char IndexThread::IndexThreadAction_UpdateCbt = 14; extern PLUGIN_ID filesrv_pluginid; +#ifdef __APPLE__ + std::vector IndexThread::macos_exclusions; + std::vector IndexThread::macos_overrides; +#endif + namespace { const int64 idletime = 60000; @@ -4391,7 +4403,7 @@ std::vector IndexThread::buildExcludeList(const std::string& val) addFileExceptions(exclude_dirs_combined); addHardExcludes(exclude_dirs_combined); #ifdef __APPLE__ - addMacosExcludes(exclude_dirs_combined); + addMacOSExcludes(exclude_dirs_combined); #endif return exclude_dirs_combined; } @@ -4526,10 +4538,20 @@ bool IndexThread::isExcluded(const std::vector& exclude_dirs, const bool b=amatch(wpath.c_str(), exclude_dirs[i].c_str()); if(b) { +#ifdef __APPLE__ + logMacOSExclude(path); +#endif return true; } } } +#ifdef __APPLE__ + if(isExcludedByXattr(path)) + { + logIsExcludedByXattr(path); + return true; + }; +#endif return false; } @@ -5024,23 +5046,222 @@ void IndexThread::addHardExcludes(std::vector& exclude_dirs) #endif } -void IndexThread::addMacosExcludes(std::vector& exclude_dirs) -{ - std::string macos_exclusion_list = getFile("urbackup/macos_exclusions.txt"); +#ifdef __APPLE__ + void IndexThread::addMacOSExcludes(std::vector& exclude_dirs) + { +// Read the overrides list + std::ifstream overrides_file("urbackup/macOS_exclusion_overrides.txt"); + if (overrides_file.is_open()) + { + std::string file_line; + while (std::getline(overrides_file, file_line)) + { + const char * file_line_prefix = &file_line[0]; + if (strncmp (file_line_prefix, "#", 1) != 0) + { + IndexThread::macos_overrides.push_back(sanitizePattern(file_line)); + } + } + overrides_file.close(); + } + + +// Exclude these items entirely + IndexThread::macos_exclusions.push_back("/.MobileBackups*"); + IndexThread::macos_exclusions.push_back("/MobileBackups.trash*"); + IndexThread::macos_exclusions.push_back("/.MobileBackups.trash*"); + IndexThread::macos_exclusions.push_back("/.Spotlight-V100*"); + IndexThread::macos_exclusions.push_back("/.TemporaryItems*"); + IndexThread::macos_exclusions.push_back("/.Trashes*"); + IndexThread::macos_exclusions.push_back("/.com.apple.backupd.mvlist.plist*"); + IndexThread::macos_exclusions.push_back("/.fseventsd*"); + IndexThread::macos_exclusions.push_back("/.hotfiles.btree*"); + IndexThread::macos_exclusions.push_back("/Backups.backupdb*"); + IndexThread::macos_exclusions.push_back("/Desktop DB*"); + IndexThread::macos_exclusions.push_back("/Desktop DF*"); + IndexThread::macos_exclusions.push_back("/Network/Servers*"); + IndexThread::macos_exclusions.push_back("/Library/Updates*"); + IndexThread::macos_exclusions.push_back("/Previous Systems*"); + IndexThread::macos_exclusions.push_back("/Users/Shared/SC Info*"); + IndexThread::macos_exclusions.push_back("/Users/Guest*"); + IndexThread::macos_exclusions.push_back("/dev*"); + IndexThread::macos_exclusions.push_back("/home*"); + IndexThread::macos_exclusions.push_back("/net*"); + IndexThread::macos_exclusions.push_back("/private/var/db/com.apple.backupd.backupVerification*"); + IndexThread::macos_exclusions.push_back("/private/var/db/efw_cache*"); + IndexThread::macos_exclusions.push_back("/private/var/db/Spotlight*"); + IndexThread::macos_exclusions.push_back("/private/var/db/Spotlight-V100*"); + IndexThread::macos_exclusions.push_back("/private/var/db/systemstats*"); + IndexThread::macos_exclusions.push_back("/private/var/lib/postfix/greylist.db*"); + + IndexThread::macos_exclusions.push_back("/.DocumentRevisions-V100*"); + IndexThread::macos_exclusions.push_back("/.HFS+ Private Directory Data*"); + IndexThread::macos_exclusions.push_back("/private/etc/kcpassword*"); + IndexThread::macos_exclusions.push_back("/private/var/db/dyld*"); + IndexThread::macos_exclusions.push_back("/private/var/db/dyld/shared_region_roots*"); + +// Backup the top level folder, but exclude all contents + IndexThread::macos_exclusions.push_back("/Volumes/*"); + IndexThread::macos_exclusions.push_back("/Network/*"); + IndexThread::macos_exclusions.push_back("/automount/*"); + IndexThread::macos_exclusions.push_back("/.vol/*"); + IndexThread::macos_exclusions.push_back("/tmp/*"); + IndexThread::macos_exclusions.push_back("/cores/*"); + IndexThread::macos_exclusions.push_back("/private/tmp/*"); + IndexThread::macos_exclusions.push_back("/private/Network/*"); + IndexThread::macos_exclusions.push_back("/private/tftpboot/*"); + IndexThread::macos_exclusions.push_back("/private/var/automount/*"); + IndexThread::macos_exclusions.push_back("/private/var/folders/*"); + IndexThread::macos_exclusions.push_back("/private/var/run/*"); + IndexThread::macos_exclusions.push_back("/private/var/tmp/*"); + IndexThread::macos_exclusions.push_back("/private/var/vm/*"); + IndexThread::macos_exclusions.push_back("/private/var/db/dhcpclient/*"); + IndexThread::macos_exclusions.push_back("/private/var/db/fseventsd/*"); + IndexThread::macos_exclusions.push_back("/Library/Caches/*"); + IndexThread::macos_exclusions.push_back("/Library/Logs/*"); + IndexThread::macos_exclusions.push_back("/System/Library/Caches/*"); + IndexThread::macos_exclusions.push_back("/System/Library/Extensions/Caches/*"); + + +// Backup folder structure, but exclude all files + IndexThread::macos_exclusions.push_back("/private/var/log/*"); + IndexThread::macos_exclusions.push_back("/private/var/spool/cups/*"); + IndexThread::macos_exclusions.push_back("/private/var/spool/fax/*"); + IndexThread::macos_exclusions.push_back("/private/var/spool/uucp/*"); + + +// Exclude these items within each user account + IndexThread::macos_exclusions.push_back("/Users/:/Library/Application Support/SyncServices/data.version*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Application Support/Ubiquity*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Caches*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Logs*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/Envelope Index*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/Envelope Index-journal*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/AvailableFeeds*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/Metadata/BackingStoreUpdateJournal*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index-journal*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/AvailableFeeds*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/BackingStoreUpdateJournal*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index-shm*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mail/V2/MailData/Envelope Index-wal*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Mirrors*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/PubSub/Database*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/PubSub/Downloads*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/PubSub/Feeds*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Safari/Icons.db*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Safari/WebpageIcons.db*"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Safari/HistoryIndex.sk*"); + IndexThread::macos_exclusions.push_back("/Users/:/.Trash*"); + + // Exclude DataVaults + IndexThread::macos_exclusions.push_back("/Users/:/Library/VoiceTrigger/SAT"); + IndexThread::macos_exclusions.push_back("/Users/:/Library/Containers/com.apple.mail/Data/DataVaults"); + IndexThread::macos_exclusions.push_back("/var/folders/:/:/:/com.apple.nsurlsessiond"); + +// Exclude volumes from macOS 10.14+ + IndexThread::macos_exclusions.push_back("/System/Volumes/*"); + + +// Filter the standard exclusions with the overrides list + std::string overridden_macos_exclusions; + for(size_t i=0; iLog("[macOS] Skipping \""+path+"\" due to macOS exclusion \""+pathMatch+"\"", LL_INFO); + } + } - std::vector macos_exclusions; - macos_exclusions = parseExcludePatterns(macos_exclusion_list); - for(size_t i=0;i exclude_dirs, std::string path) + { + std::string wpath=path; + + for(size_t i=0;i 0) { + std::string xattr_str(reinterpret_cast(xattr_out), xattr_size); + + if (xattr_str.find(backup_excludeItem_string) != std::string::npos) { + // Based on isExcluded + std::string wpath=path; + + for(size_t i=0;iLog("[macOS] Backing up \""+path+"\" due to overridden macOS extended attribute exclusion", LL_INFO); + return false; + } + } + } + // File is not overridden, and contains backup_excludeItem xattr + return true; + } + // Does not contain backup_excludeItem xattr + return false; + } else { +// Does not contain any xattr + return false; + } + } + + void IndexThread::logIsExcludedByXattr(const std::string path) + { + Server->Log("[macOS] Skipping \""+path+"\" due to macOS extended attribute exclusion", LL_INFO); + } + +#endif + void IndexThread::handleHardLinks(const std::string& bpath, const std::string& vsspath, const std::string& normalized_volume) { @@ -7949,41 +8170,41 @@ bool IndexThread::finishCbtEra(IFsFile* hdat_file, IFsFile* hdat_img, std::strin } -bool IndexThread::finishCbtEra2(IFsFile* hdat, int64 hdat_era) -{ - if (hdat != NULL) - { - std::auto_ptr hdat_file_era_new(Server->openFile(hdat->getFilename() + ".era.new", MODE_WRITE)); - - if (hdat_file_era_new.get() == NULL) - { - VSSLog("Error opening file at " + hdat->getFilename() + ".era.new. " + os_last_error_str(), LL_ERROR); - return false; - } - - if (hdat_file_era_new->Write(convert(hdat_era)) != convert(hdat_era).size()) - { - VSSLog("Error writing to " + hdat->getFilename() + ".era.new", LL_ERROR); - return false; - } - - if (!hdat_file_era_new->Sync()) - { - VSSLog("Error syncing " + hdat->getFilename() + ".era.new. " + os_last_error_str(), LL_ERROR); - return false; - } - - if (!os_rename_file(hdat->getFilename() + ".era.new", - hdat->getFilename() + ".era")) - { - VSSLog("Error renaming " + hdat->getFilename() + ".era.new to "+hdat->getFilename() + ".era. " + os_last_error_str(), LL_ERROR); - return false; - } - } - - return true; -} - +bool IndexThread::finishCbtEra2(IFsFile* hdat, int64 hdat_era) +{ + if (hdat != NULL) + { + std::auto_ptr hdat_file_era_new(Server->openFile(hdat->getFilename() + ".era.new", MODE_WRITE)); + + if (hdat_file_era_new.get() == NULL) + { + VSSLog("Error opening file at " + hdat->getFilename() + ".era.new. " + os_last_error_str(), LL_ERROR); + return false; + } + + if (hdat_file_era_new->Write(convert(hdat_era)) != convert(hdat_era).size()) + { + VSSLog("Error writing to " + hdat->getFilename() + ".era.new", LL_ERROR); + return false; + } + + if (!hdat_file_era_new->Sync()) + { + VSSLog("Error syncing " + hdat->getFilename() + ".era.new. " + os_last_error_str(), LL_ERROR); + return false; + } + + if (!os_rename_file(hdat->getFilename() + ".era.new", + hdat->getFilename() + ".era")) + { + VSSLog("Error renaming " + hdat->getFilename() + ".era.new to "+hdat->getFilename() + ".era. " + os_last_error_str(), LL_ERROR); + return false; + } + } + + return true; +} + bool IndexThread::disableCbt(std::string volume) { #ifdef _WIN32 diff --git a/urbackupclient/client.h b/urbackupclient/client.h index 460e468a2..985cd3d63 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -44,6 +44,12 @@ #include #include +#ifdef __APPLE__ +#include +#include +#include +#endif + const int c_group_vss_components = -1; const int c_group_default = 0; const int c_group_continuous = 1; @@ -549,8 +555,17 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public static void addFileExceptions(std::vector& exclude_dirs); static void addHardExcludes(std::vector& exclude_dirs); - static void addMacosExcludes(std::vector& exclude_dirs); - + +#ifdef __APPLE__ + static std::vector macos_exclusions; + static std::vector macos_overrides; + + static void addMacOSExcludes(std::vector& exclude_dirs); + static void logMacOSExclude(const std::string path); + static std::string returnExcludeDirsMatch(std::vector exclude_dirs, std::string path); + static bool isExcludedByXattr(const std::string path); + static void logIsExcludedByXattr(const std::string path); +#endif void handleHardLinks(const std::string& bpath, const std::string& vsspath, const std::string& normalized_volume); diff --git a/urbackupcommon/os_functions_lin.cpp b/urbackupcommon/os_functions_lin.cpp index ee21b396a..78a480d8e 100644 --- a/urbackupcommon/os_functions_lin.cpp +++ b/urbackupcommon/os_functions_lin.cpp @@ -128,6 +128,28 @@ std::vector getFiles(const std::string &path, bool *has_error, bool ignor if(f.name=="." || f.name==".." ) continue; +#ifdef __APPLE__ +// Cannot stat certain locations due to macOS SIP restrictions + if(upath+dirp->d_name=="/Library/Caches/com.apple.aned" || + upath+dirp->d_name=="/private/var/db/appinstalld" || + upath+dirp->d_name=="/private/var/db/ConfigurationProfiles/Store" || + upath+dirp->d_name=="/private/var/db/CoreDuet/Knowledge" || + upath+dirp->d_name=="/private/var/db/DifferentialPrivacy" || + upath+dirp->d_name=="/private/var/db/fpsd/dvp" || + upath+dirp->d_name=="/private/var/db/KernelExtensionManagement/Staging" || + upath+dirp->d_name=="/private/var/db/lockdown" || + upath+dirp->d_name=="/private/var/db/MobileIdentityService" || + upath+dirp->d_name=="/private/var/db/oah" || + upath+dirp->d_name=="/private/var/db/searchparty" || + upath+dirp->d_name=="/private/var/networkd/db" || + upath+dirp->d_name=="/private/var/protected/trustd/private" || + upath+dirp->d_name=="/System/Library/Templates/Data/private/var/db/oah") + { + Log("[macOS] Skipping \""+upath+dirp->d_name+"\" due to macOS SIP restriction", LL_INFO); + continue; + } +#endif + f.isdir=(dirp->d_type==DT_DIR); struct stat64 f_info; From 70a455a59dab5669e3d30366b1ae9f3f5e309c7e Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 5 Dec 2021 21:12:50 -0800 Subject: [PATCH 090/469] Use vcpkg for Windows dependencies --- .gitignore | 4 + CompiledServer.vcxproj | 25 +- UrBackupBackend.sln | 13 +- blockalign_src/blockalign.vcxproj | 12 + build_client.bat | 2 - build_client_backend.bat | 10 +- build_server.bat | 22 +- build_windows_ci.bat | 19 + clientctl/clientctl.vcxproj | 9 + cryptoplugin/cryptoplugin.vcxproj | 22 +- cryptoplugin/cryptopp_inc.h | 18 +- external/imdisk/imdisk.h | 1361 +++++++++++++++++ external/imdisk/imdiskver.h | 6 + external/imdisk/imdproxy.h | 131 ++ fileservplugin/fileservplugin.vcxproj | 19 +- fsimageplugin/ImdiskSrv.cpp | 4 +- fsimageplugin/fsimageplugin.vcxproj | 26 +- httpserver/httpserver.vcxproj | 12 + luaplugin/luaplugin.vcxproj | 12 + md5.h | 4 +- readme.md | 8 +- update_deps.bat | 5 - .../sysvol_test/sysvol_test.vcxproj | 12 + urbackupclient/urbackupclient.vcxproj | 33 +- urbackupcommon/sha2/sha2.h | 4 +- urbackupserver/ImageMount.cpp | 2 +- urbackupserver/urbackupserver.vcxproj | 73 +- urbackupserver_installer_win/update_data.bat | 4 +- .../urbackup_server.nsi | 2 - urlplugin/urlplugin.vcxproj | 29 +- vcpkg.json | 28 + 31 files changed, 1800 insertions(+), 131 deletions(-) create mode 100644 build_windows_ci.bat create mode 100644 external/imdisk/imdisk.h create mode 100644 external/imdisk/imdiskver.h create mode 100644 external/imdisk/imdproxy.h delete mode 100644 update_deps.bat create mode 100644 vcpkg.json diff --git a/.gitignore b/.gitignore index ca133bae1..3b458acac 100644 --- a/.gitignore +++ b/.gitignore @@ -268,3 +268,7 @@ blockalign/x64/* /blockalign/Debug/* /luaplugin/Debug/* /urbackupserver/www/templates/post/* +/vcpkg_installed +/urbackupserver/Release +/blockalign_src/Release +/luaplugin/Release diff --git a/CompiledServer.vcxproj b/CompiledServer.vcxproj index c9a2bc50d..5eeecb83d 100644 --- a/CompiledServer.vcxproj +++ b/CompiledServer.vcxproj @@ -129,6 +129,24 @@ + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + Disabled @@ -172,7 +190,7 @@ 4005;%(DisableSpecificWarnings) - ./libx64;D:\Developement\urbackup_libs\libx64;%(AdditionalLibraryDirectories) + %(AdditionalLibraryDirectories) true Console false @@ -193,7 +211,7 @@ ProgramDatabase - libx86;D:\Developement\urbackup_libs\libx86;%(AdditionalLibraryDirectories) + %(AdditionalLibraryDirectories) true Console true @@ -227,7 +245,8 @@ MachineX64 - libx64\;D:\Developement\urbackup_libs\libx64 + + Dbghelp.lib;%(AdditionalDependencies) diff --git a/UrBackupBackend.sln b/UrBackupBackend.sln index 766731a3c..ed795108d 100644 --- a/UrBackupBackend.sln +++ b/UrBackupBackend.sln @@ -76,12 +76,13 @@ Global {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|Win32.Build.0 = Debug|Win32 {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|x64.ActiveCfg = Debug|x64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|x64.Build.0 = Debug|x64 - {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|Win32.ActiveCfg = Release Server|x64 - {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|x64.ActiveCfg = Release Server|x64 - {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|x64.Build.0 = Release Server|x64 - {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|Win32.ActiveCfg = Release Server|x64 - {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|x64.ActiveCfg = Release Server|x64 - {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|x64.Build.0 = Release Server|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|Win32.ActiveCfg = Release|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|x64.ActiveCfg = Release|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|x64.Build.0 = Release|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|Win32.ActiveCfg = Release|Win32 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|Win32.Build.0 = Release|Win32 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|x64.ActiveCfg = Release|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|x64.Build.0 = Release|x64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|Win32.ActiveCfg = Debug|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|Win32.Build.0 = Debug|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|x64.ActiveCfg = Debug|x64 diff --git a/blockalign_src/blockalign.vcxproj b/blockalign_src/blockalign.vcxproj index 5dc95fb30..b2d1b04f9 100644 --- a/blockalign_src/blockalign.vcxproj +++ b/blockalign_src/blockalign.vcxproj @@ -90,6 +90,18 @@ false + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + diff --git a/build_client.bat b/build_client.bat index 320be6f6f..1ecd9f7ff 100644 --- a/build_client.bat +++ b/build_client.bat @@ -1,5 +1,3 @@ -call update_deps.bat - call checkout_client.bat if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/build_client_backend.bat b/build_client_backend.bat index 4f8f1913a..1f429a00e 100644 --- a/build_client_backend.bat +++ b/build_client_backend.bat @@ -1,17 +1,15 @@ call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" -call update_deps.bat - -msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="win32" +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="x64" +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild UrBackupBackend.sln /p:Configuration="Release Service" /p:Platform="x64" +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild UrBackupBackend.sln /p:Configuration="Release Service" /p:Platform="win32" +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% exit /b 0 \ No newline at end of file diff --git a/build_server.bat b/build_server.bat index c0ec79b71..5bb3c0be8 100644 --- a/build_server.bat +++ b/build_server.bat @@ -1,7 +1,5 @@ call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" -call update_deps.bat - git reset --hard python build\replace_versions.py if %errorlevel% neq 0 exit /b %errorlevel% @@ -9,30 +7,18 @@ if %errorlevel% neq 0 exit /b %errorlevel% copy /Y "%~dp0server-license.txt" "%~dp0urbackupserver_installer_win\data_common\server-license.txt" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="win32" +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="x64" -if %errorlevel% neq 0 exit /b %errorlevel% - -msbuild UrBackupBackend.sln /p:Configuration="Release Service" /p:Platform="x64" +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild UrBackupBackend.sln /p:Configuration="Release Service" /p:Platform="win32" +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -msbuild urbackupserver\urbackupserver.vcxproj /p:Configuration="Release Server" /p:Platform="win32" +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% -mkdir "Release Server" -copy /Y "urbackupserver\Release Server\*" "Release Server\" - -msbuild urbackupserver\urbackupserver.vcxproj /p:Configuration="Release Server" /p:Platform="x64" -if %errorlevel% neq 0 exit /b %errorlevel% - -mkdir "x64\Release Server" -copy /Y "urbackupserver\x64\Release Server\*" "x64\Release Server\" - call "%~dp0urbackupserver_installer_win/generate_msi.bat" if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/build_windows_ci.bat b/build_windows_ci.bat new file mode 100644 index 000000000..07c9dbe1c --- /dev/null +++ b/build_windows_ci.bat @@ -0,0 +1,19 @@ +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" + +SET VCPKG_CRT_LINKAGE=dynamic +SET VCPKG_LIBRARY_LINKAGE=static + +msbuild UrBackupBackend.sln /p:Configuration=Debug /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% + +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% + +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% + +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% + +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% \ No newline at end of file diff --git a/clientctl/clientctl.vcxproj b/clientctl/clientctl.vcxproj index 9570873a9..3f0587f76 100644 --- a/clientctl/clientctl.vcxproj +++ b/clientctl/clientctl.vcxproj @@ -79,6 +79,15 @@ false + + true + + + x64-windows-static-md + + + x86-windows-static-md + diff --git a/cryptoplugin/cryptoplugin.vcxproj b/cryptoplugin/cryptoplugin.vcxproj index 71ed9bbcb..50d49776f 100644 --- a/cryptoplugin/cryptoplugin.vcxproj +++ b/cryptoplugin/cryptoplugin.vcxproj @@ -90,6 +90,18 @@ + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + Disabled @@ -115,7 +127,7 @@ Disabled - $(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;%(AdditionalIncludeDirectories) + %(AdditionalIncludeDirectories) WIN32;_DEBUG;_WINDOWS;_USRDLL;CRYPTOPLUGIN_EXPORTS;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebugDLL @@ -125,7 +137,7 @@ ProgramDatabase - cryptlibd_x86_64.lib;%(AdditionalDependencies) + %(AdditionalDependencies) $(CryptoppLibDir);$(SolutionDir)/deps/libs;%(AdditionalLibraryDirectories) true Windows @@ -151,7 +163,7 @@ true MachineX86 $(CryptoppLibDir);$(SolutionDir)deps\libs;%(AdditionalLibraryDirectories) - cryptlib_x86.lib;%(AdditionalDependencies) + %(AdditionalDependencies) @@ -172,8 +184,8 @@ Windows true true - $(CryptoppLibDir);$(SolutionDir)deps\libs;%(AdditionalLibraryDirectories) - cryptlib_x86_64.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) diff --git a/cryptoplugin/cryptopp_inc.h b/cryptoplugin/cryptopp_inc.h index 4bc40c978..638f7ee9f 100644 --- a/cryptoplugin/cryptopp_inc.h +++ b/cryptoplugin/cryptopp_inc.h @@ -1,21 +1,8 @@ #ifdef _WIN32 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#define CRYPTOPP_INCLUDE_PREFIX cryptopp #else #include "../config.h" +#endif #define CRYPTOPP_INCLUDE_AES #define CRYPTOPP_INCLUDE_SHA #define CRYPTOPP_INCLUDE_MODES @@ -49,7 +36,6 @@ #if (CRYPTOPP_VERSION >= 564) #include CRYPTOPP_INCLUDE_CRC #endif -#endif namespace CryptoPPCompat { diff --git a/external/imdisk/imdisk.h b/external/imdisk/imdisk.h new file mode 100644 index 000000000..8a1719d0b --- /dev/null +++ b/external/imdisk/imdisk.h @@ -0,0 +1,1361 @@ +/* +ImDisk Virtual Disk Driver for Windows NT/2000/XP. + +Copyright (C) 2005-2015 Olof Lagerkvist. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _INC_IMDISK_ +#define _INC_IMDISK_ + +#ifndef __T +#if defined(_NTDDK_) || defined(UNICODE) || defined(_UNICODE) +#define __T(x) L ## x +#else +#define __T(x) x +#endif +#endif + +#ifndef _T +#define _T(x) __T(x) +#endif + +#include "imdiskver.h" +#define IMDISK_VERSION ((IMDISK_MAJOR_VERSION << 8) | (IMDISK_MINOR_VERSION << 4) | (IMDISK_MINOR_LOW_VERSION)) +#define IMDISK_DRIVER_VERSION 0x0103 + +#ifndef ZERO_STRUCT +#define ZERO_STRUCT { 0 } +#endif + +/// +/// Base names for device objects created in \Device +/// +#define IMDISK_DEVICE_DIR_NAME _T("\\Device") +#define IMDISK_DEVICE_BASE_NAME IMDISK_DEVICE_DIR_NAME _T("\\ImDisk") +#define IMDISK_CTL_DEVICE_NAME IMDISK_DEVICE_BASE_NAME _T("Ctl") + +/// +/// Symlinks created in \DosDevices to device objects +/// +#define IMDISK_SYMLNK_NATIVE_DIR_NAME _T("\\DosDevices") +#define IMDISK_SYMLNK_WIN32_DIR_NAME _T("\\\\?") +#define IMDISK_SYMLNK_NATIVE_BASE_NAME IMDISK_SYMLNK_NATIVE_DIR_NAME _T("\\ImDisk") +#define IMDISK_SYMLNK_WIN32_BASE_NAME IMDISK_SYMLNK_WIN32_DIR_NAME _T("\\ImDisk") +#define IMDISK_CTL_SYMLINK_NAME IMDISK_SYMLNK_NATIVE_BASE_NAME _T("Ctl") +#define IMDISK_CTL_DOSDEV_NAME IMDISK_SYMLNK_WIN32_BASE_NAME _T("Ctl") + +/// +/// The driver name and image path +/// +#define IMDISK_DRIVER_NAME _T("ImDisk") +#define IMDISK_DRIVER_PATH _T("system32\\drivers\\imdisk.sys") + +#ifndef AWEALLOC_DRIVER_NAME +#define AWEALLOC_DRIVER_NAME _T("AWEAlloc") +#endif +#ifndef AWEALLOC_DEVICE_NAME +#define AWEALLOC_DEVICE_NAME _T("\\Device\\") AWEALLOC_DRIVER_NAME +#endif + +/// +/// Global refresh event name +/// +#define IMDISK_REFRESH_EVENT_NAME _T("ImDiskRefresh") + +/// +/// Registry settings. It is possible to specify devices to be mounted +/// automatically when the driver loads. +/// +#define IMDISK_CFG_PARAMETER_KEY _T("\\Parameters") +#define IMDISK_CFG_MAX_DEVICES_VALUE _T("MaxDevices") +#define IMDISK_CFG_LOAD_DEVICES_VALUE _T("LoadDevices") +#define IMDISK_CFG_DISALLOWED_DRIVE_LETTERS_VALUE _T("DisallowedDriveLetters") +#define IMDISK_CFG_IMAGE_FILE_PREFIX _T("FileName") +#define IMDISK_CFG_SIZE_PREFIX _T("Size") +#define IMDISK_CFG_FLAGS_PREFIX _T("Flags") +#define IMDISK_CFG_DRIVE_LETTER_PREFIX _T("DriveLetter") +#define IMDISK_CFG_OFFSET_PREFIX _T("ImageOffset") + +#define KEY_NAME_HKEY_MOUNTPOINTS \ + _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MountPoints") +#define KEY_NAME_HKEY_MOUNTPOINTS2 \ + _T("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\MountPoints2") + +#define IMDISK_WINVER_MAJOR() (GetVersion() & 0xFF) +#define IMDISK_WINVER_MINOR() ((GetVersion() & 0xFF00) >> 8) + +#define IMDISK_WINVER() ((IMDISK_WINVER_MAJOR() << 8) | \ + IMDISK_WINVER_MINOR()) + +#if defined(NT4_COMPATIBLE) && !defined(_WIN64) +#define IMDISK_GTE_WIN2K() (IMDISK_WINVER_MAJOR() >= 0x05) +#else +#define IMDISK_GTE_WIN2K() TRUE +#endif + +#ifdef _WIN64 +#define IMDISK_GTE_WINXP() TRUE +#else +#define IMDISK_GTE_WINXP() (IMDISK_WINVER() >= 0x0501) +#endif + +#define IMDISK_GTE_SRV2003() (IMDISK_WINVER() >= 0x0502) + +#define IMDISK_GTE_VISTA() (IMDISK_WINVER_MAJOR() >= 0x06) + +#ifndef IMDISK_API +#ifdef IMDISK_CPL_EXPORTS +#define IMDISK_API __declspec(dllexport) +#else +#define IMDISK_API __declspec(dllimport) +#endif +#endif + +/// +/// Base value for the IOCTL's. +/// +#define FILE_DEVICE_IMDISK 0x8372 + +#define IOCTL_IMDISK_QUERY_VERSION ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x800, METHOD_BUFFERED, 0)) +#define IOCTL_IMDISK_CREATE_DEVICE ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x801, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)) +#define IOCTL_IMDISK_QUERY_DEVICE ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x802, METHOD_BUFFERED, 0)) +#define IOCTL_IMDISK_QUERY_DRIVER ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x803, METHOD_BUFFERED, 0)) +#define IOCTL_IMDISK_REFERENCE_HANDLE ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x804, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)) +#define IOCTL_IMDISK_SET_DEVICE_FLAGS ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x805, METHOD_BUFFERED, 0)) +#define IOCTL_IMDISK_REMOVE_DEVICE ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x806, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)) +#define IOCTL_IMDISK_IOCTL_PASS_THROUGH ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x807, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)) +#define IOCTL_IMDISK_FSCTL_PASS_THROUGH ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x808, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)) +#define IOCTL_IMDISK_GET_REFERENCED_HANDLE ((ULONG) CTL_CODE(FILE_DEVICE_IMDISK, 0x809, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)) + +/// +/// Bit constants for the Flags field in IMDISK_CREATE_DATA +/// + +/// Read-only device +#define IMDISK_OPTION_RO 0x00000001 + +/// Check if flags specifies read-only +#define IMDISK_READONLY(x) ((ULONG)(x) & 0x00000001) + +/// Removable, hot-plug, device +#define IMDISK_OPTION_REMOVABLE 0x00000002 + +/// Check if flags specifies removable +#define IMDISK_REMOVABLE(x) ((ULONG)(x) & 0x00000002) + +/// Specifies that image file is created with sparse attribute. +#define IMDISK_OPTION_SPARSE_FILE 0x00000004 + +/// Check if flags specifies sparse +#define IMDISK_SPARSE_FILE(x) ((ULONG)(x) & 0x00000004) + +/// Swaps each byte pair in image file. +#define IMDISK_OPTION_BYTE_SWAP 0x00000008 + +/// Check if flags specifies byte swapping +#define IMDISK_BYTE_SWAP(x) ((ULONG)(x) & 0x00000008) + +/// Device type is virtual harddisk partition +#define IMDISK_DEVICE_TYPE_HD 0x00000010 +/// Device type is virtual floppy drive +#define IMDISK_DEVICE_TYPE_FD 0x00000020 +/// Device type is virtual CD/DVD-ROM drive +#define IMDISK_DEVICE_TYPE_CD 0x00000030 +/// Device type is unknown "raw" (for use with third-party client drivers) +#define IMDISK_DEVICE_TYPE_RAW 0x00000040 + +/// Extracts the IMDISK_DEVICE_TYPE_xxx from flags +#define IMDISK_DEVICE_TYPE(x) ((ULONG)(x) & 0x000000F0) + +/// Virtual disk is backed by image file +#define IMDISK_TYPE_FILE 0x00000100 +/// Virtual disk is backed by virtual memory +#define IMDISK_TYPE_VM 0x00000200 +/// Virtual disk is backed by proxy connection +#define IMDISK_TYPE_PROXY 0x00000300 + +/// Extracts the IMDISK_TYPE_xxx from flags +#define IMDISK_TYPE(x) ((ULONG)(x) & 0x00000F00) + +// Types with proxy mode + +/// Proxy connection is direct-type +#define IMDISK_PROXY_TYPE_DIRECT 0x00000000 +/// Proxy connection is over serial line +#define IMDISK_PROXY_TYPE_COMM 0x00001000 +/// Proxy connection is over TCP/IP +#define IMDISK_PROXY_TYPE_TCP 0x00002000 +/// Proxy connection uses shared memory +#define IMDISK_PROXY_TYPE_SHM 0x00003000 + +/// Extracts the IMDISK_PROXY_TYPE_xxx from flags +#define IMDISK_PROXY_TYPE(x) ((ULONG)(x) & 0x0000F000) + +// Types with file mode + +/// Serialized I/O to an image file, done in a worker thread +#define IMDISK_FILE_TYPE_QUEUED_IO 0x00000000 +/// Direct parallel I/O to AWEAlloc driver (physical RAM), done in request +/// thread +#define IMDISK_FILE_TYPE_AWEALLOC 0x00001000 +/// Direct parallel I/O to an image file, done in request thread +#define IMDISK_FILE_TYPE_PARALLEL_IO 0x00002000 + +/// Extracts the IMDISK_FILE_TYPE_xxx from flags +#define IMDISK_FILE_TYPE(x) ((ULONG)(x) & 0x0000F000) + +/// Flag set by write request dispatchers to indicated that virtual disk has +/// been since mounted +#define IMDISK_IMAGE_MODIFIED 0x00010000 + +/// This flag causes the driver to open image files in shared write mode even +/// if the image is opened for writing. This could be useful in some cases, +/// but could easily corrupt filesystems on image files if used incorrectly. +#define IMDISK_OPTION_SHARED_IMAGE 0x00020000 +/// Check if flags indicate shared write mode +#define IMDISK_SHARED_IMAGE(x) ((ULONG)(x) & 0x00020000) + +/// Macro to determine if flags specify either virtual memory (type vm) or +/// physical memory (type file with awealloc) virtual disk drive +#define IMDISK_IS_MEMORY_DRIVE(x) \ + ((IMDISK_TYPE(x) == IMDISK_TYPE_VM) || \ + ((IMDISK_TYPE(x) == IMDISK_TYPE_FILE) && \ + (IMDISK_FILE_TYPE(x) == IMDISK_FILE_TYPE_AWEALLOC))) + +/// Specify as device number to automatically select first free. +#define IMDISK_AUTO_DEVICE_NUMBER ((ULONG)-1) + +/** +Structure used by the IOCTL_IMDISK_CREATE_DEVICE and +IOCTL_IMDISK_QUERY_DEVICE calls and by the ImDiskQueryDevice function. +*/ +typedef struct _IMDISK_CREATE_DATA +{ + /// On create this can be set to IMDISK_AUTO_DEVICE_NUMBER + ULONG DeviceNumber; + /// Total size in bytes (in the Cylinders field) and virtual geometry. + DISK_GEOMETRY DiskGeometry; + /// The byte offset in the image file where the virtual disk begins. + LARGE_INTEGER ImageOffset; + /// Creation flags. Type of device and type of connection. + ULONG Flags; + /// Drive letter (if used, otherwise zero). + WCHAR DriveLetter; + /// Length in bytes of the FileName member. + USHORT FileNameLength; + /// Dynamically-sized member that specifies the image file name. + WCHAR FileName[1]; +} IMDISK_CREATE_DATA, *PIMDISK_CREATE_DATA; + +typedef struct _IMDISK_SET_DEVICE_FLAGS +{ + ULONG FlagsToChange; + ULONG FlagValues; +} IMDISK_SET_DEVICE_FLAGS, *PIMDISK_SET_DEVICE_FLAGS; + +#define IMDISK_API_NO_BROADCAST_NOTIFY 0x00000001 +#define IMDISK_API_FORCE_DISMOUNT 0x00000002 + +#pragma pack(push) +#pragma pack(1) +typedef struct _FAT_BPB +{ + USHORT BytesPerSector; + UCHAR SectorsPerCluster; + USHORT ReservedSectors; + UCHAR NumberOfFileAllocationTables; + USHORT NumberOfRootEntries; + USHORT NumberOfSectors; + UCHAR MediaDescriptor; + USHORT SectorsPerFileAllocationTable; + USHORT SectorsPerTrack; + USHORT NumberOfHeads; + union + { + struct + { + USHORT NumberOfHiddenSectors; + USHORT TotalNumberOfSectors; + } DOS_320; + struct + { + ULONG NumberOfHiddenSectors; + ULONG TotalNumberOfSectors; + } DOS_331; + }; +} FAT_BPB, *PFAT_BPB; + +typedef struct _FAT_VBR +{ + UCHAR JumpInstruction[3]; + CHAR OEMName[8]; + FAT_BPB BPB; + UCHAR FillData[512 - 3 - 8 - sizeof(FAT_BPB) - 1 - 2]; + UCHAR PhysicalDriveNumber; + UCHAR Signature[2]; +} FAT_VBR, *PFAT_VBR; +#pragma pack(pop) + +#ifdef WINAPI + +#ifdef __cplusplus +extern "C" { +#endif + + /** + Get behaviour flags for API. + */ + IMDISK_API ULONGLONG + WINAPI + ImDiskGetAPIFlags(); + + /** + Set behaviour flags for API. Returns previously defined flag field. + + Flags New flags value to set. + */ + IMDISK_API ULONGLONG + WINAPI + ImDiskSetAPIFlags(ULONGLONG Flags); + + /** + An interactive rundll32.exe-compatible function to show the Add New Virtual + Disk dialog box with a file name already filled in. It is used by the + Windows Explorer context menus. + + hWnd Specifies a window that will be the owner window of any + MessageBox:es or similar. + + hInst Ignored. + + lpszCmdLine An ANSI string specifying the image file to mount. + + nCmdShow Ignored. + */ + IMDISK_API void + WINAPI + RunDLL_MountFile(HWND hWnd, + HINSTANCE hInst, + LPSTR lpszCmdLine, + int nCmdShow); + + /** + An interactive rundll32.exe-compatible function to remove an existing ImDisk + virtual disk. If the filesystem on the device cannot be locked and + dismounted a MessageBox is displayed that asks the user if dismount should + be forced. + + hWnd Specifies a window that will be the owner window of any + MessageBox:es or similar. + + hInst Ignored. + + lpszCmdLine An ANSI string specifying the virtual disk to remove. This + can be on the form "F:" or "F:\" (without the quotes). + + nCmdShow Ignored. + */ + IMDISK_API void + WINAPI + RunDLL_RemoveDevice(HWND hWnd, + HINSTANCE hInst, + LPSTR lpszCmdLine, + int nCmdShow); + + /** + An interactive rundll32.exe-compatible function to save a virtual or + physical drive as an image file. If the filesystem on the device cannot be + locked and dismounted a MessageBox is displayed that asks the user if the + image saving should continue anyway. + + hWnd Specifies a window that will be the owner window of any + MessageBox:es or similar. + + hInst Ignored. + + lpszCmdLine An ANSI string specifying the disk to save. This can be on + the form "F:" or "F:\" (without the quotes). + + nCmdShow Ignored. + */ + IMDISK_API void + WINAPI + RunDLL_SaveImageFile(HWND hWnd, + HINSTANCE hInst, + LPSTR lpszCmdLine, + int nCmdShow); + + /** + This function displays a MessageBox dialog with a + FormatMessage-formatted message. + + hWndParent Parent window for the MessageBox call. + + uStyle Style for the MessageBox call. + + lpTitle Window title for the MessageBox call. + + lpMessage Format string to be used in call to FormatMessage followed + by field parameters. + */ + IMDISK_API BOOL + CDECL + ImDiskMsgBoxPrintF(IN HWND hWndParent OPTIONAL, + IN UINT uStyle, + IN LPCWSTR lpTitle, + IN LPCWSTR lpMessage, ...); + + /** + Synchronously flush Windows message queue to make GUI components responsive. + */ + IMDISK_API VOID + WINAPI + ImDiskFlushWindowMessages(HWND hWnd); + + /** + Used to get a string describing a partition type. + + PartitionType Partition type from partition table. + + Name Pointer to memory that receives a string describing the + partition type. + + NameSize Size of memory area pointed to by the Name parameter. + */ + IMDISK_API VOID + WINAPI + ImDiskGetPartitionTypeName(IN BYTE PartitionType, + IN OUT LPWSTR Name, + IN DWORD NameSize); + + /** + Returns the offset in bytes to actual disk image data for some known + "non-raw" image file formats with headers. Returns TRUE if file extension + is recognized and the known offset has been stored in the variable pointed + to by the Offset parameter. Otherwise the function returns FALSE and the + value pointed to by the Offset parameter is not changed. + + ImageFile Name of raw disk image file. This does not need to be a valid + path or filename, just the extension is used by this function. + + Offset Returned offset in bytes if function returns TRUE. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetOffsetByFileExt(IN LPCWSTR ImageFile, + IN OUT PLARGE_INTEGER Offset); + + /** + Attempts to find partition information from a partition table for a raw + disk image file. If no master boot record is found this function returns + FALSE. Returns TRUE if a master boot record with a partition table is found + and values stored in the structures pointed to by the PartitionInformation + parameter. Otherwise the function returns FALSE. + + ImageFile Name of raw disk image file to examine. + + SectorSize Optional sector size used on disk if different from default + 512 bytes. + + Offset Optional offset in bytes to master boot record within file for + use with "non-raw" image files with headers before the actual + disk image data. + + PartitionInformation + Pointer to an array of eight PARTITION_INFORMATION structures + which will receive information from four recognized primary + partition entries followed by four recognized extended entries. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetPartitionInformation(IN LPCWSTR ImageFile, + IN DWORD SectorSize OPTIONAL, + IN PLARGE_INTEGER Offset OPTIONAL, + IN OUT PPARTITION_INFORMATION + PartitionInformation); + + + /** + Prototype for raw disk reader function used with ImDisk***Indirect + functions. + + Handle Value that was passed as first parameter to + ImDiskGetPartitionInfoIndirect. + + Buffer Buffer where read data is to be stored. + + Offset Disk offset where read operation starts. + + NumberOfBytesToRead Number of bytes to read from disk. + + NumberOfBytesRead Pointer to DWORD size variable where function stores + number of bytes actually read into Buffer. This value + can be equal to or less than NumberOfBytesToRead + parameter. + */ + typedef BOOL(WINAPI *ImDiskReadFileProc)(IN HANDLE Handle, + IN OUT LPVOID Buffer, + IN LARGE_INTEGER Offset, + IN DWORD NumberOfBytesToRead, + IN OUT LPDWORD NumberOfBytesRead); + + /** + A device read function with ImDiskReadFileProc, which means that it can be + used when calling ImDiskGetPartitionInfoIndirect function. + + Handle Operating system file handle representing a file or device + opened for reading. + + Buffer Buffer where read data is to be stored. + + Offset Disk offset where read operation starts. + + NumberOfBytesToRead + Number of bytes to read from disk. + + NumberOfBytesRead + Pointer to DWORD size variable where function stores number of + bytes actually read into Buffer. This value can be equal to or + less than NumberOfBytesToRead parameter. + */ + IMDISK_API BOOL + WINAPI + ImDiskReadFileHandle(IN HANDLE Handle, + IN OUT LPVOID Buffer, + IN LARGE_INTEGER Offset, + IN DWORD NumberOfBytesToRead, + IN OUT LPDWORD NumberOfBytesRead); + + /** + Attempts to find partition information from a partition table for a disk + image through a supplied device reader function. + + If no master boot record is found this function returns FALSE. Returns TRUE + if a master boot record with a partition table is found and values stored in + the structures pointed to by the PartitionInformation parameter. Otherwise + the function returns FALSE. + + Handle Value that is passed as first parameter to ReadFileProc. + + ReadFileProc Procedure of type ImDiskReadFileProc that is called to read raw + disk image. + + SectorSize Optional sector size used on disk if different from default + 512 bytes. + + Offset Optional offset in bytes to master boot record within file for + use with "non-raw" image files with headers before the actual + disk image data. + + PartitionInformation + Pointer to an array of eight PARTITION_INFORMATION structures + which will receive information from four recognized primary + partition entries followed by four recognized extended entries. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetPartitionInfoIndirect(IN HANDLE Handle, + IN ImDiskReadFileProc ReadFileProc, + IN DWORD SectorSize OPTIONAL, + IN PLARGE_INTEGER Offset OPTIONAL, + IN OUT PPARTITION_INFORMATION PartitionInfo); + + /** + Finds out if image file contains an ISO9660 filesystem. + + ImageFile Name of disk image file to examine. + + Offset Optional offset in bytes to where raw disk data begins, for use + with "non-raw" image files with headers before the actual disk + image data. + */ + IMDISK_API BOOL + WINAPI + ImDiskImageContainsISOFS(IN LPCWSTR ImageFile, + IN PLARGE_INTEGER Offset OPTIONAL); + + /** + Finds out if image file contains an ISO9660 filesystem, through a supplied + device reader function. + + Handle Value that is passed as first parameter to ReadFileProc. + + ReadFileProc Procedure of type ImDiskReadFileProc that is called to read raw + disk image. + + Offset Optional offset in bytes to where raw disk data begins, for use + with "non-raw" image files with headers before the actual disk + image data. + */ + IMDISK_API BOOL + WINAPI + ImDiskImageContainsISOFSIndirect(IN HANDLE Handle, + IN ImDiskReadFileProc ReadFileProc, + IN PLARGE_INTEGER Offset OPTIONAL); + + /** + Starts a Win32 service or loads a kernel module or driver. + + ServiceName Key name of the service or driver. + */ + IMDISK_API BOOL + WINAPI + ImDiskStartService(IN LPWSTR ServiceName); + + /** + An easy way to turn an empty NTFS directory to a reparse point that redirects + requests to a mounted device. Acts quite like mount points or symbolic links + in *nix. If MountPoint specifies a character followed by a colon, a drive + letter is instead created to point to Target. + + MountPoint Path to empty directory on an NTFS volume, or a drive letter + followed by a colon. + + Target Target device path on kernel object namespace form, e.g. + \Device\ImDisk2 or similar. + */ + IMDISK_API BOOL + WINAPI + ImDiskCreateMountPoint(IN LPCWSTR MountPoint, + IN LPCWSTR Target); + + /** + Restores a reparse point to be an ordinary empty directory, or removes a + drive letter mount point. When removing a drive letter mount point, this + function notifies shell components that drive letter is gone unless API + flags are set to turn off shell notifications. + + MountPoint Path to a reparse point on an NTFS volume, or a drive letter + followed by a colon to remove a drive letter mount point. + */ + IMDISK_API BOOL + WINAPI + ImDiskRemoveMountPoint(IN LPCWSTR MountPoint); + + /** + Opens a device object in the kernel object namespace. + + FileName Full kernel object namespace path to the object to open, e.g. + \Device\ImDisk2 or similar. + + AccessMode Access mode to request. + */ + IMDISK_API HANDLE + WINAPI + ImDiskOpenDeviceByName(IN PUNICODE_STRING FileName, + IN DWORD AccessMode); + + /** + Opens an ImDisk device by the device number. + + DeviceNumber Number of the ImDisk device to open. + + AccessMode Access mode to request. + */ + IMDISK_API HANDLE + WINAPI + ImDiskOpenDeviceByNumber(IN DWORD DeviceNumber, + IN DWORD AccessMode); + + /** + Opens the device a junction/mount-point type reparse point is pointing to. + + MountPoint Path to the reparse point on an NTFS volume. + + AccessMode Access mode to request to the target device. + */ + IMDISK_API HANDLE + WINAPI + ImDiskOpenDeviceByMountPoint(IN LPCWSTR MountPoint, + IN DWORD AccessMode); + + /** + Check that the user-mode library and kernel-mode driver version matches for + an open ImDisk created device object. + + DeviceHandle Handle to an open ImDisk virtual disk or control device. + */ + IMDISK_API BOOL + WINAPI + ImDiskCheckDriverVersion(IN HANDLE DeviceHandle); + + /** + Retrieves the version numbers of the user-mode API library and the kernel- + mode driver. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetVersion(IN OUT PULONG LibraryVersion OPTIONAL, + IN OUT PULONG DriverVersion OPTIONAL); + + /** + Returns the first free drive letter in the range D-Z. + */ + IMDISK_API WCHAR + WINAPI + ImDiskFindFreeDriveLetter(); + + /** + Returns a bit-field representing ImDisk devices. Bit 0 represents device 0, + bit 1 represents device 1 and so on. A bit is 1 if the device exists or 0 if + the device number is free. + + Compatibility notice: + This function is exported for compatibility with ImDisk versions before + 1.7.0. Since that version, drives can have device numbers above 63. This + function cannot return such device numbers, so in case any drive with device + number above 63 exist when this function is called, it returns a value + filled with all ones ((ULONGLONG)-1). + + Use ImDiskGetDeviceListEx function with newer versions of ImDisk. + */ + IMDISK_API ULONGLONG + WINAPI + ImDiskGetDeviceList(); + + /** + Builds a list of currently existing ImDisk virtual disks. + + ListLength Set this parameter to number of ULONG element that can be + store at the location pointed to by DeviceList parameter. + This parameter must be at least 3 for this function to work + correctly. + + DeviceList Pointer to memory location where one ULONG, containing a + device number, will be stored for each currently existing + ImDisk device. First element in list is used to store number + of devices. + + Upon return, first element in DeviceList will contain number of currently + existing ImDisk virtual disks. If DeviceList is too small to contain all + items as indicated by ListLength parameter, number of existing devices will + be stored at DeviceList location, but no further items will be stored. + + If an error occurs, this function returns FALSE and GetLastError + will return an error code. If successful, the function returns TRUE and + first element at location pointed to by DeviceList will contain number of + devices currently on the system, i.e. number of elements following the first + one in DeviceList. + + If DeviceList buffer is too small, the function returns FALSE and + GetLastError returns ERROR_MORE_DATA. In that case, only number of + existing devices will be stored at location pointed to by DeviceList + parameter. That value, plus one for the first length element, indicates how + large the buffer needs to be to successfully store all items. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetDeviceListEx(IN ULONG ListLength, + OUT PULONG DeviceList); + + /** + This function sends an IOCTL_IMDISK_QUERY_DEVICE control code to an existing + device and returns information about the device in an IMDISK_CREATE_DATA + structure. + + DeviceNumber Number of the ImDisk device to query. + + CreateData Pointer to a sufficiently large IMDISK_CREATE_DATA + structure to receive all data including the image file name + where applicable. + + CreateDataSize The size in bytes of the memory the CreateData parameter + points to. The function call will fail if the memory is not + large enough to hold the entire IMDISK_CREATE_DATA + structure. + */ + IMDISK_API BOOL + WINAPI + ImDiskQueryDevice(IN DWORD DeviceNumber, + IN OUT PIMDISK_CREATE_DATA CreateData, + IN ULONG CreateDataSize); + + /** + This function creates a new ImDisk virtual disk device. + + hWndStatusText A handle to a window that can display status message text. + The function will send WM_SETTEXT messages to this window. + If this parameter is NULL no WM_SETTEXT messages are sent + and the function acts non-interactive. + + DiskGeometry The virtual geometry of the new virtual disk. Note that the + Cylinders member does not specify the number of Cylinders + but the total size in bytes of the new virtual disk. The + actual number of cylinders are then automatically + calculated and rounded down if necessary. + + The Cylinders member can be zero if the device is backed by + an image file or a proxy device, but not if it is virtual + memory only device. + + All or some of the other members of this structure can be + zero in which case they are automatically filled in with + most reasonable values by the driver. + + Flags Bitwise or-ed combination of one of the IMDISK_TYPE_xxx + flags, one of the IMDISK_DEVICE_TYPE_xxx flags and any + number of IMDISK_OPTION_xxx flags. The flags can often be + left zero and left to the driver to automatically select. + For example, if a virtual disk size is specified to 1440 KB + and an image file name is not specified, the driver + automatically selects IMDISK_TYPE_VM|IMDISK_DEVICE_TYPE_FD + for this parameter. + + FileName Name of disk image file. In case IMDISK_TYPE_VM is + specified in the Flags parameter, this file will be loaded + into the virtual memory-backed disk when created. + + NativePath Set to TRUE if the FileName parameter specifies an NT + native path, such as \??\C:\imagefile.img or FALSE if it + specifies a Win32/DOS-style path such as C:\imagefile.img. + + MountPoint Drive letter to assign to the new virtual device. It can be + specified on the form F: or F:\. It can also specify an empty directory + on another NTFS volume. + */ + IMDISK_API BOOL + WINAPI + ImDiskCreateDevice(IN HWND hWndStatusText OPTIONAL, + IN OUT PDISK_GEOMETRY DiskGeometry OPTIONAL, + IN PLARGE_INTEGER ImageOffset OPTIONAL, + IN DWORD Flags OPTIONAL, + IN LPCWSTR FileName OPTIONAL, + IN BOOL NativePath, + IN LPWSTR MountPoint OPTIONAL); + + /** + This function creates a new ImDisk virtual disk device. + + hWndStatusText A handle to a window that can display status message text. + The function will send WM_SETTEXT messages to this window. + If this parameter is NULL no WM_SETTEXT messages are sent + and the function acts non-interactive. + + DeviceNumber In: Device number for device to create. Device number must + not be in use by an existing virtual disk. For automatic + allocation of device number, use IMDISK_AUTO_DEVICE_NUMBER + constant or specify a NULL pointer. + + Out: If DeviceNumber parameter is not NULL, device number + for created device is returned in DWORD variable pointed to. + + DiskGeometry The virtual geometry of the new virtual disk. Note that the + Cylinders member does not specify the number of Cylinders + but the total size in bytes of the new virtual disk. The + actual number of cylinders are then automatically + calculated and rounded down if necessary. + + The Cylinders member can be zero if the device is backed by + an image file or a proxy device, but not if it is virtual + memory only device. + + All or some of the other members of this structure can be + zero in which case they are automatically filled in with + most reasonable values by the driver. + + Flags Bitwise or-ed combination of one of the IMDISK_TYPE_xxx + flags, one of the IMDISK_DEVICE_TYPE_xxx flags and any + number of IMDISK_OPTION_xxx flags. The flags can often be + left zero and left to the driver to automatically select. + For example, if a virtual disk size is specified to 1440 KB + and an image file name is not specified, the driver + automatically selects IMDISK_TYPE_VM|IMDISK_DEVICE_TYPE_FD + for this parameter. + + FileName Name of disk image file. In case IMDISK_TYPE_VM is + specified in the Flags parameter, this file will be loaded + into the virtual memory-backed disk when created. + + NativePath Set to TRUE if the FileName parameter specifies an NT + native path, such as \??\C:\imagefile.img or FALSE if it + specifies a Win32/DOS-style path such as C:\imagefile.img. + + MountPoint Drive letter to assign to the new virtual device. It can + be specified on the form F: or F:\. It can also specify an empty directory + on another NTFS volume. + */ + IMDISK_API BOOL + WINAPI + ImDiskCreateDeviceEx(IN HWND hWndStatusText OPTIONAL, + IN OUT LPDWORD DeviceNumber OPTIONAL, + IN OUT PDISK_GEOMETRY DiskGeometry OPTIONAL, + IN PLARGE_INTEGER ImageOffset OPTIONAL, + IN DWORD Flags OPTIONAL, + IN LPCWSTR FileName OPTIONAL, + IN BOOL NativePath, + IN LPWSTR MountPoint OPTIONAL); + + /** + This function removes (unmounts) an existing ImDisk virtual disk device. + + hWndStatusText A handle to a window that can display status message text. + The function will send WM_SETTEXT messages to this window. + If this parameter is NULL no WM_SETTEXT messages are sent + and the function acts non-interactive. + + DeviceNumber Number of the ImDisk device to remove. This parameter is + only used if MountPoint parameter is null. + + MountPoint Drive letter of the device to remove. It can be specified + on the form F: or F:\. + */ + IMDISK_API BOOL + WINAPI + ImDiskRemoveDevice(IN HWND hWndStatusText OPTIONAL, + IN DWORD DeviceNumber OPTIONAL, + IN LPCWSTR MountPoint OPTIONAL); + + /** + This function forcefully removes (unmounts) an existing ImDisk virtual disk + device. Any unsaved data will be lost. + + Device Handle to open device. If not NULL, it is used to query + device number to find out which device to remove. If this + parameter is NULL the DeviceNumber parameter is used + instead. + + DeviceNumber Number of the ImDisk device to remove. This parameter is + only used if Device parameter is NULL. + */ + IMDISK_API BOOL + WINAPI + ImDiskForceRemoveDevice(IN HANDLE Device OPTIONAL, + IN DWORD DeviceNumber OPTIONAL); + + /** + This function changes the device characteristics of an existing ImDisk + virtual disk device. + + hWndStatusText A handle to a window that can display status message text. + The function will send WM_SETTEXT messages to this window. + If this parameter is NULL no WM_SETTEXT messages are sent + and the function acts non-interactive. + + DeviceNumber Number of the ImDisk device to change. This parameter is + only used if MountPoint parameter is null. + + MountPoint Drive letter of the device to change. It can be specified + on the form F: or F:\. + + FlagsToChange A bit-field specifying which flags to edit. The flags are + the same as the option flags in the Flags parameter used + when a new virtual disk is created. Only flags set in this + parameter are changed to the corresponding flag value in the + Flags parameter. + + Flags New values for the flags specified by the FlagsToChange + parameter. + */ + IMDISK_API BOOL + WINAPI + ImDiskChangeFlags(HWND hWndStatusText OPTIONAL, + DWORD DeviceNumber OPTIONAL, + LPCWSTR MountPoint OPTIONAL, + DWORD FlagsToChange, + DWORD Flags); + + /** + This function extends the size of an existing ImDisk virtual disk device. + + hWndStatusText A handle to a window that can display status message text. + The function will send WM_SETTEXT messages to this window. + If this parameter is NULL no WM_SETTEXT messages are sent + and the function acts non-interactive. + + DeviceNumber Number of the ImDisk device to extend. + + ExtendSize A pointer to a LARGE_INTEGER structure that specifies the + number of bytes to extend the device. + */ + IMDISK_API BOOL + WINAPI + ImDiskExtendDevice(IN HWND hWndStatusText OPTIONAL, + IN DWORD DeviceNumber, + IN CONST PLARGE_INTEGER ExtendSize); + + /** + This function saves the contents of a device to an image file. + + DeviceHandle Handle to a device for which the contents are to be saved to + an image file. + + The handle must be opened for reading, may be + opened for sequential scan and/or without intermediate + buffering but cannot be opened for overlapped operation. + Please note that a call to this function will turn on + FSCTL_ALLOW_EXTENDED_DASD_IO on for this handle. + + FileHandle Handle to an image file opened for writing. The handle + can be opened for operation without intermediate buffering + but performance is usually better if the handle is opened + with intermediate buffering. The handle cannot be opened for + overlapped operation. + + BufferSize I/O buffer size to use when reading source disk. This + parameter is optional, if it is zero the buffer size to use + will automatically chosen. + + CancelFlag Optional pointer to a BOOL value. If this BOOL value is set + to TRUE during the function call the operation is cancelled, + the function returns FALSE and GetLastError will return + ERROR_CANCELLED. If this parameter is non-null the function + will also dispatch window messages for the current thread + between each I/O operation. + */ + IMDISK_API BOOL + WINAPI + ImDiskSaveImageFile(IN HANDLE DeviceHandle, + IN HANDLE FileHandle, + IN DWORD BufferSize OPTIONAL, + IN LPBOOL CancelFlag OPTIONAL); + + /** + This function gets the size of a disk volume. + + Handle Handle to a disk volume device. + + Size Pointer to a 64 bit variable that upon successful completion + receives disk volume size as a signed integer. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetVolumeSize(IN HANDLE Handle, + IN OUT PLONGLONG Size); + + /** + Reads formatted geometry for a volume by parsing BPB, BIOS Parameter Block, + from volume boot record into a DISK_GEOMETRY structure. + + If no boot record signature is found, this function returns FALSE. + + ImageFile Path to a volume image file or a device path to a disk volume, + such as \\.\A: or \\.\C:. + + Offset Optional offset in bytes to volume boot record within file for + use with "non-raw" volume image files. This parameter can be + used to for example skip over headers for specific disk image + formats, or to skip over master boot record in a disk image + file that contains a complete raw disk image and not only a + single volume. + + DiskGeometry Pointer to DISK_GEOMETRY structure that receives information + about formatted geometry. This function zeros the Cylinders + member. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetFormattedGeometry(IN LPCWSTR ImageFile, + IN PLARGE_INTEGER Offset OPTIONAL, + IN OUT PDISK_GEOMETRY DiskGeometry); + + /** + Reads formatted geometry for a volume by parsing BPB, BIOS Parameter Block, + from volume boot record into a DISK_GEOMETRY structure. + + If no boot record signature is found, this function returns FALSE. + + Handle Value that is passed as first parameter to ReadFileProc. + + ReadFileProc Procedure of type ImDiskReadFileProc that is called to read + disk volume. + + Offset Optional offset in bytes to volume boot record within file for + use with "non-raw" volume image files. This parameter can be + used to for example skip over headers for specific disk image + formats, or to skip over master boot record in a disk image + file that contains a complete raw disk image and not only a + single volume. + + DiskGeometry Pointer to DISK_GEOMETRY structure that receives information + about formatted geometry. This function zeros the Cylinders + member. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetFormattedGeometryIndirect(IN HANDLE Handle, + IN ImDiskReadFileProc ReadFileProc, + IN PLARGE_INTEGER Offset OPTIONAL, + IN OUT PDISK_GEOMETRY DiskGeometry); + + /** + This function builds a Master Boot Record, MBR, in memory. The MBR will + contain a default Initial Program Loader, IPL, which could be used to boot + an operating system partition when the MBR is written to a disk. + + DiskGeometry Pointer to a DISK_GEOMETRY or DISK_GEOMETRY_EX structure + that contains information about logical geometry of the + disk. + + This function only uses the BytesPerSector, SectorsPerTrack + and TracksPerCylinder members. + + This parameter can be NULL if NumberOfParts parameter is + zero. + + PartitionInfo Pointer to an array of up to four PARTITION_INFORMATION + structures containing information about partitions to store + in MBR partition table. + + This function only uses the StartingOffset, PartitionLength, + BootIndicator and PartitionType members. + + This parameter can be NULL if NumberOfParts parameter is + zero. + + NumberOfParts Number of PARTITION_INFORMATION structures in array that + PartitionInfo parameter points to. + + If this parameter is zero, DiskGeometry and PartitionInfo + parameters are ignored and can be NULL. In that case MBR + will contain an empty partition table when this function + returns. + + MBR Pointer to memory buffer of at least 512 bytes where MBR + will be built. + + MBRSize Size of buffer pointed to by MBR parameter. This parameter + must be at least 512. + */ + IMDISK_API BOOL + WINAPI + ImDiskBuildMBR(IN PDISK_GEOMETRY DiskGeometry OPTIONAL, + IN PPARTITION_INFORMATION PartitionInfo OPTIONAL, + IN BYTE NumberOfParts OPTIONAL, + IN OUT LPBYTE MBR, + IN DWORD_PTR MBRSize); + + /** + This function converts a CHS disk address to LBA format. + + DiskGeometry Pointer to a DISK_GEOMETRY or DISK_GEOMETRY_EX structure + that contains information about logical geometry of the + disk. This function only uses the SectorsPerTrack and + TracksPerCylinder members. + + CHS Pointer to CHS disk address in three-byte partition table + style format. + */ + IMDISK_API DWORD + WINAPI + ImDiskConvertCHSToLBA(IN PDISK_GEOMETRY DiskGeometry, + IN LPBYTE CHS); + + /** + This function converts an LBA disk address to three-byte partition style CHS + format. The three bytes are returned in the three lower bytes of a DWORD. + + DiskGeometry Pointer to a DISK_GEOMETRY or DISK_GEOMETRY_EX structure + that contains information about logical geometry of the + disk. This function only uses the SectorsPerTrack and + TracksPerCylinder members. + + LBA LBA disk address. + */ + IMDISK_API DWORD + WINAPI + ImDiskConvertLBAToCHS(IN PDISK_GEOMETRY DiskGeometry, + IN DWORD LBA); + + /** + This function adjusts size of a saved image file. If file size is less than + requested disk size, the size will be left unchanged with return value FALSE + and GetLastError will return ERROR_DISK_OPERATION_FAILED. + + FileHandle Handle to file where disk image has been saved. + + FileSize Size of original disk which image file should be adjusted + to. + */ + IMDISK_API BOOL + WINAPI + ImDiskAdjustImageFileSize(IN HANDLE FileHandle, + IN PLARGE_INTEGER FileSize); + + /** + This function converts a native NT-style path to a Win32 DOS-style path. The + path string is converted in-place and the start address is adjusted to skip + over native directories such as \??\. Because of this, the Path parameter is + a pointer to a pointer to a string so that the pointer can be adjusted to + the new start address. + + Path Pointer to pointer to Path string in native NT-style format. + Upon return the pointed address will contain the start + address of the Win32 DOS-style path within the original + buffer. + */ + IMDISK_API VOID + WINAPI + ImDiskNativePathToWin32(IN OUT LPWSTR *Path); + + /** + This function saves the contents of a device to an image file. This is a + user-interactive function that displays dialog boxes where user can select + image file and other options. + + DeviceHandle Handle to a device for which the contents are to be saved to + an image file. + + The handle must be opened for reading, may be + opened for sequential scan and/or without intermediate + buffering but cannot be opened for overlapped operation. + Please note that a call to this function will turn on + FSCTL_ALLOW_EXTENDED_DASD_IO on for this handle. + + WindowHandle Handle to existing window that will be parent to dialog + boxes etc. + + BufferSize I/O buffer size to use when reading source disk. This + parameter is optional, if it is zero the buffer size to use + will automatically chosen. + + IsCdRomType If this parameter is TRUE and the source device type cannot + be automatically determined this function will ask user for + a .iso suffixed image file name. + */ + IMDISK_API VOID + WINAPI + ImDiskSaveImageFileInteractive(IN HANDLE DeviceHandle, + IN HWND WindowHandle OPTIONAL, + IN DWORD BufferSize OPTIONAL, + IN BOOL IsCdRomType OPTIONAL); + + /* + Opens or creates a global synchronization event. This event is shared with + ImDisk driver and will be pulsed when an ImDisk device is created, removed + or have settings changed in some other way. + + This is particularly useful for user interface components that need to be + notified when device lists and similar need to be updated. + + If successful, this function returns a handle to an event that can be used + in call to system wait functions, such as WaitForSingleObject. When the + handle is not needed, it must be closed by calling CloseHandle. + + If the function fails, it returns NULL and GetLastError will return a + system error code that further explains the error. + + InheritHandle Specifies whether or not the returned handle will be + inherited by child processes. + + */ + IMDISK_API HANDLE + WINAPI + ImDiskOpenRefreshEvent(BOOL InheritHandle); + + /* + Adds registry settings for creating a virtual disk at system startup (or + when driver is loaded). + + This function returns TRUE if successful, FALSE otherwise. If FALSE is + returned, GetLastError could be used to get actual error code. + + CreateData Pointer to IMDISK_CREATE_DATA structure that contains + device creation settings to save. + + */ + IMDISK_API BOOL + WINAPI + ImDiskSaveRegistrySettings(PIMDISK_CREATE_DATA CreateData); + + /* + Remove registry settings for creating a virtual disk at system startup (or + when driver is loaded). + + This function returns TRUE if successful, FALSE otherwise. If FALSE is + returned, GetLastError could be used to get actual error code. + + DeviceNumber Device number specified in registry settings. + */ + IMDISK_API BOOL + WINAPI + ImDiskRemoveRegistrySettings(DWORD DeviceNumber); + + /* + Retrieves number of auto-loading devices at system startup, or when driver + is loaded. This is the value of the LoadDevices registry value for + imdisk.sys driver. + + This function returns TRUE if successful, FALSE otherwise. If FALSE is + returned, GetLastError could be used to get actual error code. + + LoadDevicesValue + Pointer to variable that receives the value. + */ + IMDISK_API BOOL + WINAPI + ImDiskGetRegistryAutoLoadDevices(LPDWORD LoadDevicesValue); + + /* + Notify Explorer and other shell components that a new drive letter has + been created. Called automatically by device creation after creating a + drive letter. If no drive letter was created by a device creation routine + or if API flags was set to turn off shell notification during device + creation, this function can be called manually later. + + Note that calling this function has no effect if API flags are set to + turn off shell notifications, or if supplied drive letter path does not + specify an A-Z drive letter. + + This function returns TRUE if successful, FALSE otherwise. If FALSE is + returned, GetLastError could be used to get actual error code. + + hWnd + Window handle to use as parent handle for any message boxes. If this + parameter is NULL, no message boxes are displayed. + + DriveLetterPath + Drive letter path in one of formats A:\ or A:. + */ + IMDISK_API BOOL + WINAPI + ImDiskNotifyShellDriveLetter(HWND hWnd, + LPWSTR DriveLetterPath); + + /* + Notify Explorer and other shell components that a drive is about to be + removed. + + hWnd + Window handle to use as parent handle for any message boxes. If this + parameter is NULL, no message boxes are displayed. + + DriveLetter + Drive letter. + */ + IMDISK_API BOOL + WINAPI + ImDiskNotifyRemovePending(HWND hWnd, + WCHAR DriveLetter); + + IMDISK_API LPWSTR + CDECL + ImDiskAllocPrintF(LPCWSTR lpMessage, ...); + +#ifdef __cplusplus +} +#endif + +#endif + +#endif // _INC_IMDISK_ diff --git a/external/imdisk/imdiskver.h b/external/imdisk/imdiskver.h new file mode 100644 index 000000000..8c50dc543 --- /dev/null +++ b/external/imdisk/imdiskver.h @@ -0,0 +1,6 @@ +#define IMDISK_RC_VERSION_STR "2.0.9" +#define IMDISK_MAJOR_VERSION 2 +#define IMDISK_MINOR_VERSION 0 +#define IMDISK_MINOR_LOW_VERSION 9 + +#define IMDISK_RC_VERSION_FLD IMDISK_MAJOR_VERSION,IMDISK_MINOR_VERSION,IMDISK_MINOR_LOW_VERSION diff --git a/external/imdisk/imdproxy.h b/external/imdisk/imdproxy.h new file mode 100644 index 000000000..6d1a9291b --- /dev/null +++ b/external/imdisk/imdproxy.h @@ -0,0 +1,131 @@ +/* +ImDisk Proxy Services. + +Copyright (C) 2005-2007 Olof Lagerkvist. + +Permission is hereby granted, free of charge, to any person +obtaining a copy of this software and associated documentation +files (the "Software"), to deal in the Software without +restriction, including without limitation the rights to use, +copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following +conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. +*/ + +#ifndef _INC_IMDPROXY_ +#define _INC_IMDPROXY_ + +#if !defined(_WIN32) && !defined(_NTDDK_) +typedef int32_t LONG; +typedef uint32_t ULONG; +typedef int64_t LONGLONG; +typedef uint64_t ULONGLONG; +typedef u_short WCHAR; +#endif + +#define IMDPROXY_SVC L"ImDskSvc" +#define IMDPROXY_SVC_PIPE_DOSDEV_NAME L"\\\\.\\PIPE\\" IMDPROXY_SVC +#define IMDPROXY_SVC_PIPE_NATIVE_NAME L"\\Device\\NamedPipe\\" IMDPROXY_SVC + +#define IMDPROXY_FLAG_RO 0x01 +#define IMDPROXY_FLAG_SUPPORTS_UNMAP 0x02 +#define IMDPROXY_FLAG_SUPPORTS_ZERO 0x04 + +typedef enum _IMDPROXY_REQ +{ + IMDPROXY_REQ_NULL, + IMDPROXY_REQ_INFO, + IMDPROXY_REQ_READ, + IMDPROXY_REQ_WRITE, + IMDPROXY_REQ_CONNECT, + IMDPROXY_REQ_CLOSE, + IMDPROXY_REQ_UNMAP, + IMDPROXY_REQ_ZERO +} IMDPROXY_REQ, *PIMDPROXY_REQ; + +typedef struct _IMDPROXY_CONNECT_REQ +{ + ULONGLONG request_code; + ULONGLONG flags; + ULONGLONG length; +} IMDPROXY_CONNECT_REQ, *PIMDPROXY_CONNECT_REQ; + +typedef struct _IMDPROXY_CONNECT_RESP +{ + ULONGLONG error_code; + ULONGLONG object_ptr; +} IMDPROXY_CONNECT_RESP, *PIMDPROXY_CONNECT_RESP; + +typedef struct _IMDPROXY_INFO_RESP +{ + ULONGLONG file_size; + ULONGLONG req_alignment; + ULONGLONG flags; +} IMDPROXY_INFO_RESP, *PIMDPROXY_INFO_RESP; + +typedef struct _IMDPROXY_READ_REQ +{ + ULONGLONG request_code; + ULONGLONG offset; + ULONGLONG length; +} IMDPROXY_READ_REQ, *PIMDPROXY_READ_REQ; + +typedef struct _IMDPROXY_READ_RESP +{ + ULONGLONG errorno; + ULONGLONG length; +} IMDPROXY_READ_RESP, *PIMDPROXY_READ_RESP; + +typedef struct _IMDPROXY_WRITE_REQ +{ + ULONGLONG request_code; + ULONGLONG offset; + ULONGLONG length; +} IMDPROXY_WRITE_REQ, *PIMDPROXY_WRITE_REQ; + +typedef struct _IMDPROXY_WRITE_RESP +{ + ULONGLONG errorno; + ULONGLONG length; +} IMDPROXY_WRITE_RESP, *PIMDPROXY_WRITE_RESP; + +typedef struct _IMDPROXY_UNMAP_REQ +{ + ULONGLONG request_code; + ULONGLONG length; +} IMDPROXY_UNMAP_REQ, *PIMDPROXY_UNMAP_REQ; + +typedef struct _IMDPROXY_UNMAP_RESP +{ + ULONGLONG errorno; +} IMDPROXY_UNMAP_RESP, *PIMDPROXY_UNMAP_RESP; + +typedef struct _IMDPROXY_ZERO_REQ +{ + ULONGLONG request_code; + ULONGLONG length; +} IMDPROXY_ZERO_REQ, *PIMDPROXY_ZERO_REQ; + +typedef struct _IMDPROXY_ZERO_RESP +{ + ULONGLONG errorno; +} IMDPROXY_ZERO_RESP, *PIMDPROXY_ZERO_RESP; + +// For shared memory proxy communication only. Offset to data area in +// shared memory. +#define IMDPROXY_HEADER_SIZE 4096 + +#endif // _INC_IMDPROXY_ diff --git a/fileservplugin/fileservplugin.vcxproj b/fileservplugin/fileservplugin.vcxproj index fea7332ca..78cf187cb 100644 --- a/fileservplugin/fileservplugin.vcxproj +++ b/fileservplugin/fileservplugin.vcxproj @@ -90,6 +90,15 @@ + + true + + + x64-windows-static-md + + + x86-windows-static-md + Disabled @@ -123,13 +132,14 @@ $(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp; - cryptlib_x86.lib;ws2_32.lib;%(AdditionalDependencies) + ws2_32.lib;%(AdditionalDependencies) true Console true true MachineX86 - $(CryptoppLibDir);$(SolutionDir)/deps/libs; + + @@ -171,13 +181,14 @@ $(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp; - cryptlib_x86_64.lib;ws2_32.lib;%(AdditionalDependencies) + ws2_32.lib;%(AdditionalDependencies) true Console true true MachineX64 - $(CryptoppLibDir);$(SolutionDir)/deps/libs; + + diff --git a/fsimageplugin/ImdiskSrv.cpp b/fsimageplugin/ImdiskSrv.cpp index 8e7b85bb7..86724f818 100644 --- a/fsimageplugin/ImdiskSrv.cpp +++ b/fsimageplugin/ImdiskSrv.cpp @@ -6,8 +6,8 @@ #include "FileWrapper.h" #include #include -#include -#include +#include "../external/imdisk/imdproxy.h" +#include "../external/imdisk/imdisk.h" #include #include #include diff --git a/fsimageplugin/fsimageplugin.vcxproj b/fsimageplugin/fsimageplugin.vcxproj index bd4097767..97ba53733 100644 --- a/fsimageplugin/fsimageplugin.vcxproj +++ b/fsimageplugin/fsimageplugin.vcxproj @@ -90,6 +90,18 @@ + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + Disabled @@ -129,8 +141,9 @@ true true MachineX86 - ws2_32.lib;libzstd_x86.lib;%(AdditionalDependencies) - $(SolutionDir)/deps/libs;$(ZstdLibDir) + ws2_32.lib;%(AdditionalDependencies) + + @@ -147,13 +160,13 @@ Level3 ProgramDatabase - $(SolutionDir)/deps/include/imdisk;$(ImdiskIncludeDir);$(ZstdIncludeDir);$(SolutionDir)/deps/include/zstd + $(SolutionDir)/deps/include/imdisk;$(ImdiskIncludeDir) true Windows MachineX64 - ws2_32.lib;libzstd_x86_64.lib;%(AdditionalDependencies) + ws2_32.lib;%(AdditionalDependencies) $(SolutionDir)/deps/libs;$(ZstdLibDir) @@ -179,8 +192,9 @@ true true MachineX64 - ws2_32.lib;libzstd_x86_64.lib;%(AdditionalDependencies) - $(SolutionDir)/deps/libs;$(ZstdLibDir) + ws2_32.lib;%(AdditionalDependencies) + + diff --git a/httpserver/httpserver.vcxproj b/httpserver/httpserver.vcxproj index c7015bc9b..eb87a58b0 100644 --- a/httpserver/httpserver.vcxproj +++ b/httpserver/httpserver.vcxproj @@ -90,6 +90,18 @@ + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + Disabled diff --git a/luaplugin/luaplugin.vcxproj b/luaplugin/luaplugin.vcxproj index af4ffac05..d85aa928d 100644 --- a/luaplugin/luaplugin.vcxproj +++ b/luaplugin/luaplugin.vcxproj @@ -81,6 +81,18 @@ false + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + diff --git a/md5.h b/md5.h index 63807f9c1..202ba8537 100644 --- a/md5.h +++ b/md5.h @@ -117,12 +117,12 @@ class MD5 { #define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1 #ifdef _WIN32 -#include +#define CRYPTOPP_INCLUDE_PREFIX cryptopp #else #include "config.h" +#endif #define CRYPTOPP_INCLUDE_MD5 #include CRYPTOPP_INCLUDE_MD5 -#endif class MD5 { diff --git a/readme.md b/readme.md index 22950ab14..6d9ce5745 100644 --- a/readme.md +++ b/readme.md @@ -36,12 +36,12 @@ See the separate `readme-macos.md` for building instructions for macOS. ### Building on Windows -If git is in `PATH` you can download all dependencies by running `update_deps.bat`. +Build with Visual Studio 2019: -Afterwards opening and compiling the solution `UrBackupBackend.sln` with -Microsoft Visual Studio 2015 should work. + * Install [vcpkg](https://vcpkg.io/en/index.html) and run `vcpkg integrate install` + * Set global environment variable `VCPKG_FEATURE_FLAGS` to `manifests` + * Open `UrBackupBackend.sln` with Visual Studio 2019 and build (and run) `build_client.bat` and `build_server.bat` build the installers but you need to install a lot of dependencies like WiX, NSIS plus plugins, etc. -[![Build Status](https://travis-ci.org/uroni/urbackup_backend.svg?branch=dev)](https://travis-ci.org/uroni/urbackup_backend) diff --git a/update_deps.bat b/update_deps.bat deleted file mode 100644 index dec826fbf..000000000 --- a/update_deps.bat +++ /dev/null @@ -1,5 +0,0 @@ -if not exist deps git clone -b master http://buildserver.urbackup.org/git/urbackup_deps deps -cd deps -git reset --hard -git pull -cd .. \ No newline at end of file diff --git a/urbackupclient/sysvol_test/sysvol_test.vcxproj b/urbackupclient/sysvol_test/sysvol_test.vcxproj index aa5c23f5d..609de9399 100644 --- a/urbackupclient/sysvol_test/sysvol_test.vcxproj +++ b/urbackupclient/sysvol_test/sysvol_test.vcxproj @@ -79,6 +79,18 @@ false + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + diff --git a/urbackupclient/urbackupclient.vcxproj b/urbackupclient/urbackupclient.vcxproj index c085eeb7e..89bd68dff 100644 --- a/urbackupclient/urbackupclient.vcxproj +++ b/urbackupclient/urbackupclient.vcxproj @@ -204,6 +204,18 @@ + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + Disabled @@ -218,8 +230,8 @@ $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(SolutionDir)/deps/include/zstd;$(ZstdIncludeDir) - zlibd_x86.lib;ws2_32.lib;VssApi.Lib;%(AdditionalDependencies);libzstd_x86.lib - $(ZlibLibDir);$(SolutionDir)/deps/libs;%(AdditionalLibraryDirectories) + ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) true Windows MachineX86 @@ -242,18 +254,19 @@ $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(ZstdIncludeDir);$(SolutionDir)/deps/include/zstd - zlibd_x86_64.lib;ws2_32.lib;VssApi.Lib;%(AdditionalDependencies);libzstd_x86_64.lib + ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) true Windows MachineX64 - $(ZlibLibDir);$(SolutionDir)/deps/libs;$(ZstdLibDir) + + MaxSpeed true - ZLIB_WINAPI;WIN32;NDEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;%(PreprocessorDefinitions);CLIENT_ONLY + WIN32;NDEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;%(PreprocessorDefinitions);CLIENT_ONLY MultiThreadedDLL true @@ -263,13 +276,14 @@ $(ZstdIncludeDir);(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;$(SolutionDir)/deps/include/zstd - libzstd_x86.lib;cryptlib_x86.lib;zlib_x86.lib;ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) + ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) true Console true true MachineX86 - $(CryptoppLibDir);$(ZlibLibDir);$(SolutionDir)/deps/libs; + + @@ -289,13 +303,14 @@ $(ZstdIncludeDir);$(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;$(SolutionDir)/deps/include/zstd - cryptlib_x86_64.lib;zlib_x86_64.lib;ws2_32.lib;VssApi.Lib;%(AdditionalDependencies);libzstd_x86_64.lib + ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) true Console true true MachineX64 - $(CryptoppLibDir);$(ZlibLibDir);$(SolutionDir)/deps/libs;$(ZstdLibDir) + + diff --git a/urbackupcommon/sha2/sha2.h b/urbackupcommon/sha2/sha2.h index 59e1b3213..956009b7c 100644 --- a/urbackupcommon/sha2/sha2.h +++ b/urbackupcommon/sha2/sha2.h @@ -207,12 +207,12 @@ typedef SHA512_CTX sha512_ctx; #else //!DO_NOT_USE_CRYPTOPP_SHA #ifdef _WIN32 -#include +#define CRYPTOPP_INCLUDE_PREFIX cryptopp #else #include "../../config.h" +#endif #define CRYPTOPP_INCLUDE_SHA #include CRYPTOPP_INCLUDE_SHA -#endif typedef struct { CryptoPP::SHA256 sha; } sha256_ctx; diff --git a/urbackupserver/ImageMount.cpp b/urbackupserver/ImageMount.cpp index 32526e583..21c6863ad 100644 --- a/urbackupserver/ImageMount.cpp +++ b/urbackupserver/ImageMount.cpp @@ -12,7 +12,7 @@ #ifdef _WIN32 #include #include -#include +#include "../external/imdisk/imdisk.h" bool os_link_symbolic_junctions_raw(const std::string &target, const std::string &lname, void* transaction); #else #include diff --git a/urbackupserver/urbackupserver.vcxproj b/urbackupserver/urbackupserver.vcxproj index 55c18b0eb..790b007f5 100644 --- a/urbackupserver/urbackupserver.vcxproj +++ b/urbackupserver/urbackupserver.vcxproj @@ -9,12 +9,12 @@ Debug x64 - - Release Server + + Release Win32 - - Release Server + + Release x64 @@ -25,7 +25,7 @@ 10.0 - + DynamicLibrary Unicode true @@ -36,7 +36,7 @@ Unicode v142 - + DynamicLibrary Unicode true @@ -50,13 +50,13 @@ - + - + @@ -71,24 +71,36 @@ $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ true - $(SolutionDir)$(Configuration)\ - $(Configuration)\ - false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(Platform)\$(Configuration)\ - false + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + false AllRules.ruleset AllRules.ruleset - AllRules.ruleset - - - AllRules.ruleset - - + AllRules.ruleset + + + AllRules.ruleset + + + + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md @@ -128,18 +140,19 @@ $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(SolutionDir)/deps/include/imdisk;$(ImdiskIncludeDir);$(SolutionDir)/deps/include/zstd;$(ZstdIncludeDir) - zlibd_x86_64.lib;ws2_32.lib;VssApi.Lib;Ktmw32.lib;%(AdditionalDependencies);libzstd_x86_64.lib + ws2_32.lib;VssApi.Lib;Ktmw32.lib;%(AdditionalDependencies) true Windows MachineX64 - $(ZlibLibDir);$(SolutionDir)/deps/libs;$(ZstdLibDir) + + - + MaxSpeed true - ZLIB_WINAPI;WIN32;NDEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;SERVER_ONLY;USE_NTFS_TXF;%(PreprocessorDefinitions) + WIN32;NDEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;SERVER_ONLY;USE_NTFS_TXF;%(PreprocessorDefinitions) MultiThreadedDLL true @@ -149,16 +162,17 @@ $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(SolutionDir)/../deps/include/zlib;$(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;$(SolutionDir)/../deps/include/cryptopp;$(SolutionDir)/deps/include/imdisk;$(SolutionDir)/../deps/include/imdisk;$(ImdiskIncludeDir);$(SolutionDir)/deps/include/zstd;$(ZstdIncludeDir);$(SolutionDir)/../deps/include/zstd - libzstd_x86.lib;cryptlib_x86.lib;zlib_x86.lib;ws2_32.lib;Ktmw32.lib;%(AdditionalDependencies) + ws2_32.lib;Ktmw32.lib;%(AdditionalDependencies) true Console true true MachineX86 - $(CryptoppLibDir);$(ZlibLibDir);$(SolutionDir)/deps/libs;$(SolutionDir)/../deps/libs;$(ZstdLibDir) + + - + X64 @@ -175,13 +189,14 @@ $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(SolutionDir)/../deps/include/zlib;$(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;$(SolutionDir)/../deps/include/cryptopp;$(SolutionDir)/deps/include/imdisk;$(SolutionDir)/../deps/include/imdisk;$(ImdiskIncludeDir);$(SolutionDir)/deps/include/zstd;$(ZstdIncludeDir);$(SolutionDir)/../deps/include/zstd - libzstd_x86_64.lib;cryptlib_x86_64.lib;zlib_x86_64.lib;ws2_32.lib;Ktmw32.lib;%(AdditionalDependencies) + ws2_32.lib;Ktmw32.lib;%(AdditionalDependencies) true Console true true MachineX64 - $(CryptoppLibDir);$(ZlibLibDir);$(SolutionDir)/deps/libs;$(SolutionDir)/../deps/libs;$(ZstdLibDir) + + diff --git a/urbackupserver_installer_win/update_data.bat b/urbackupserver_installer_win/update_data.bat index 06e9d0c26..5ac29d2ab 100644 --- a/urbackupserver_installer_win/update_data.bat +++ b/urbackupserver_installer_win/update_data.bat @@ -5,7 +5,7 @@ copy /Y "..\Release Service\Server.exe" "data_service\urbackup_srv.exe" if %errorlevel% neq 0 exit /b %errorlevel% mkdir data -copy /Y "..\Release Server\urbackupserver.dll" "data\urbackupserver.dll" +copy /Y "..\release\urbackupserver.dll" "data\urbackupserver.dll" if %errorlevel% neq 0 exit /b %errorlevel% copy /Y "..\release\fsimageplugin.dll" "data\fsimageplugin.dll" @@ -66,7 +66,7 @@ copy /Y "..\x64\Release Service\Server.exe" "data_service_x64\urbackup_srv.exe" if %errorlevel% neq 0 exit /b %errorlevel% mkdir data_x64 -copy /Y "..\x64\Release Server\urbackupserver.dll" "data_x64\urbackupserver.dll" +copy /Y "..\x64\release\urbackupserver.dll" "data_x64\urbackupserver.dll" if %errorlevel% neq 0 exit /b %errorlevel% copy /Y "..\x64\release\fsimageplugin.dll" "data_x64\fsimageplugin.dll" diff --git a/urbackupserver_installer_win/urbackup_server.nsi b/urbackupserver_installer_win/urbackup_server.nsi index 6788b7633..339e53dfb 100644 --- a/urbackupserver_installer_win/urbackup_server.nsi +++ b/urbackupserver_installer_win/urbackup_server.nsi @@ -179,7 +179,6 @@ Section "install" File "data_service\urbackup_srv.exe" File "data\urlplugin.dll" File "data\luaplugin.dll" - File "data\libzstd.dll" SetOutPath "$INSTDIR" ${Else} File "data_x64\fsimageplugin.dll" @@ -190,7 +189,6 @@ Section "install" File "data_x64\cryptoplugin.dll" File "data_x64\urlplugin.dll" File "data_x64\luaplugin.dll" - File "data_x64\libzstd.dll" SetOutPath "$INSTDIR" ${EndIf} diff --git a/urlplugin/urlplugin.vcxproj b/urlplugin/urlplugin.vcxproj index 59dbcea11..a3e790c5b 100644 --- a/urlplugin/urlplugin.vcxproj +++ b/urlplugin/urlplugin.vcxproj @@ -79,6 +79,18 @@ false + + true + + + x64-windows-static-md + + + x64-windows-static-md + + + x86-windows-static-md + @@ -91,8 +103,9 @@ Console true - $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs - Crypt32.lib;zlibd_x86.lib;libcurld_x86.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + + Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -107,8 +120,9 @@ Console true - $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs - Crypt32.lib;zlibd_x86_64.lib;libcurld_x86_64.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + + Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -129,8 +143,9 @@ true - Crypt32.lib;zlib_x86.lib;libcurl_x86.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) - $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs + Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + @@ -150,7 +165,7 @@ true true $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs - Crypt32.lib;zlib_x86_64.lib;libcurl_x86_64.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..a65fac6e6 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://raw.githubusercontent.com/microsoft/vcpkg/master/scripts/vcpkg.schema.json", + "name": "urbackup", + "version": "2.5.0", + "dependencies": [ + { + "name": "cryptopp", + "default-features": true + }, + { + "name": "zstd", + "default-features": true + }, + { + "name": "zlib", + "default-features": true + }, + { + "name": "curl", + "default-features": false, + "features": [ + "non-http", + "schannel", + "winldap" + ] + } + ] + } \ No newline at end of file From aebe62f90b05f7ae82f5af798b0412ee5022c698 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 5 Dec 2021 13:11:30 +0100 Subject: [PATCH 091/469] Add panic when root device is snapshot (cherry picked from commit c8fe04f46a5cc6b6b7c6753235aeac6888f6667d) --- .../scripts_local-top_urbackup-setup-snapshot | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/linux_snapshot/scripts_local-top_urbackup-setup-snapshot b/linux_snapshot/scripts_local-top_urbackup-setup-snapshot index a30c46591..9ea3d271a 100755 --- a/linux_snapshot/scripts_local-top_urbackup-setup-snapshot +++ b/linux_snapshot/scripts_local-top_urbackup-setup-snapshot @@ -21,10 +21,21 @@ if grep "setup-snapshot=0" /proc/cmdline; then exit 0 fi +if [ "x$ROOT" = "x" ]; then + echo "urbackup-setup-snapshot: root device not defined. Not setting up snapshotting." > /dev/kmsg + exit 0 +fi + +if echo "$ROOT" | grep "root-98d1f8b1f435"; then + panic "urbackup-setup-snapshot: Root device is snapshot (clobbered: $ROOT). Please edit root= boot parameter to be the root device and add 'setup-snapshot=0' to boot parameters to fix boot. Then run 'update-grub' to fix grub config." + exit 0 +fi + RBD=$(resolve_device "$ROOT") if [ "x$RBD" = "x" ]; then echo "urbackup-setup-snapshot: root device not found. Not setting up snapshotting." > /dev/kmsg + exit 0 fi echo "urbackup-setup-snapshot: root block device $RBD" > /dev/kmsg From 6f339ae83fd76c47b884e985788333b2984a632a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 12 Dec 2021 12:48:20 -0800 Subject: [PATCH 092/469] Add winbtrfs support --- urbackupserver/snapshot_helper.cpp | 355 +++++++++++++++++++++++++++++ urbackupserver/snapshot_helper.h | 11 + 2 files changed, 366 insertions(+) diff --git a/urbackupserver/snapshot_helper.cpp b/urbackupserver/snapshot_helper.cpp index bf1229879..a69a12397 100644 --- a/urbackupserver/snapshot_helper.cpp +++ b/urbackupserver/snapshot_helper.cpp @@ -23,6 +23,9 @@ #include "server.h" #include "../urbackupcommon/os_functions.h" #ifdef _WIN32 +#include "server_settings.h" +#include "database.h" +#include #define WEXITSTATUS(x) x #else #include @@ -33,6 +36,11 @@ std::string SnapshotHelper::helper_name="urbackup_snapshot_helper"; int SnapshotHelper::isAvailable(void) { +#ifdef _WIN32 + setupWindows(); + return testWindows(); +#else + int rc=system((helper_name+" test").c_str()); rc = WEXITSTATUS(rc); @@ -42,22 +50,34 @@ int SnapshotHelper::isAvailable(void) } return -1; +#endif } bool SnapshotHelper::createEmptyFilesystem(bool image, std::string clientname, std::string name, std::string& errmsg) { +#ifdef _WIN32 + return createEmptyFilesystemWindows(clientname, name, errmsg); +#else int rc=os_popen((helper_name + " " + convert(BackupServer::getSnapshotMethod(image)) + " create \""+(clientname)+"\" \""+(name)+"\" 2>&1").c_str(), errmsg); return rc==0; +#endif } bool SnapshotHelper::snapshotFileSystem(bool image, std::string clientname, std::string old_name, std::string snapshot_name, std::string& errmsg) { +#ifdef _WIN32 + return snapshotFileSystemWindows(clientname, old_name, snapshot_name, errmsg); +#else int rc=os_popen((helper_name + " " + convert(BackupServer::getSnapshotMethod(image)) + " snapshot \""+(clientname)+"\" \""+(old_name)+"\" \""+(snapshot_name)+"\" 2>&1").c_str(), errmsg); return rc==0; +#endif } bool SnapshotHelper::removeFilesystem(bool image, std::string clientname, std::string name) { +#ifdef _WIN32 + return removeFilesystemWindows(clientname, name); +#else if (!image && BackupServer::getSnapshotMethod(image) == BackupServer::ESnapshotMethod_ZfsFile && name.find(".startup-del") != std::string::npos) @@ -65,12 +85,17 @@ bool SnapshotHelper::removeFilesystem(bool image, std::string clientname, std::s int rc=system((helper_name + " " + convert(BackupServer::getSnapshotMethod(image)) + " remove \""+clientname+"\" \""+name+"\"").c_str()); return rc==0; +#endif } bool SnapshotHelper::isSubvolume(bool image, std::string clientname, std::string name) { +#ifdef _WIN32 + return isSubvolumeWindows(clientname, name); +#else int rc=system((helper_name + " "+convert(BackupServer::getSnapshotMethod(image))+" issubvolume \""+(clientname)+"\" \""+(name)+"\"").c_str()); return rc==0; +#endif } void SnapshotHelper::setSnapshotHelperCommand(std::string helper_command) @@ -80,8 +105,12 @@ void SnapshotHelper::setSnapshotHelperCommand(std::string helper_command) bool SnapshotHelper::makeReadonly(bool image, std::string clientname, std::string name) { +#ifdef _WIN32 + return true; +#else int rc = system((helper_name + " " + convert(BackupServer::getSnapshotMethod(image)) + " makereadonly \"" + clientname + "\" \"" + name + "\"").c_str()); return rc == 0; +#endif } std::string SnapshotHelper::getMountpoint(bool image, std::string clientname, std::string name) @@ -97,3 +126,329 @@ std::string SnapshotHelper::getMountpoint(bool image, std::string clientname, st return trim(ret); } + +#ifdef _WIN32 + +namespace +{ + typedef struct { + BOOL readonly; + BOOL posix; + USHORT namelen; + WCHAR name[1]; + } btrfs_create_subvol; + + typedef struct { + HANDLE subvol; + BOOL readonly; + BOOL posix; + uint16_t namelen; + WCHAR name[1]; + } btrfs_create_snapshot; + + typedef struct { + uint64_t subvol; + uint64_t inode; + BOOL top; + } btrfs_get_file_ids; +} + +#define FSCTL_BTRFS_GET_FILE_IDS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x829, METHOD_OUT_DIRECT, FILE_ANY_ACCESS) +#define FSCTL_BTRFS_CREATE_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82a, METHOD_IN_DIRECT, FILE_ANY_ACCESS) +#define FSCTL_BTRFS_CREATE_SNAPSHOT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82b, METHOD_IN_DIRECT, FILE_ANY_ACCESS) + +typedef NTSTATUS(__stdcall* NtFsControlFilePtr)( + HANDLE FileHandle, + HANDLE Event, + PVOID ApcRoutine, + PVOID ApcContext, + PIO_STATUS_BLOCK IoStatusBlock, + ULONG FsControlCode, + PVOID InputBuffer, + ULONG InputBufferLength, + PVOID OutputBuffer, + ULONG OutputBufferLength); + +NtFsControlFilePtr NtFsControlFilePtr_fun; + +std::string SnapshotHelper::getBackupStoragePath() +{ + IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); + if (db == nullptr) + return std::string(); + + ServerSettings settings(db); + return settings.getSettings()->backupfolder; +} + +bool SnapshotHelper::createEmptyFilesystemWindows(std::string clientname, std::string name, std::string& errmsg) +{ + std::string backup_storage_path = getBackupStoragePath(); + if (backup_storage_path.empty()) + return false; + + std::string parent_path = backup_storage_path + os_file_sep() + clientname; + + std::string subvol_path = parent_path + os_file_sep() + name; + + if (os_get_file_type(subvol_path) != 0) + { + errmsg = subvol_path + " already exists"; + return false; + } + + HANDLE h = CreateFileW(Server->ConvertToWchar(parent_path).c_str(), + FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ| FILE_SHARE_WRITE|FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (h == INVALID_HANDLE_VALUE) + { + errmsg = "Failed to open " + parent_path + ". " + os_last_error_str(); + return false; + } + + std::wstring name_w = Server->ConvertToWchar(name); + + DWORD csb_len = static_cast(offsetof(btrfs_create_subvol, name[0]) + name_w.size()*sizeof(WCHAR)); + std::vector create_subvol_buf(csb_len); + btrfs_create_subvol* create_subvol = reinterpret_cast(create_subvol_buf.data()); + create_subvol->namelen = static_cast(name_w.size() * sizeof(WCHAR)); + memcpy(create_subvol->name, name_w.data(), create_subvol->namelen); + + IO_STATUS_BLOCK iosb; + NTSTATUS status = NtFsControlFilePtr_fun(h, nullptr, nullptr, nullptr, &iosb, + FSCTL_BTRFS_CREATE_SUBVOL, create_subvol, csb_len, nullptr, 0); + + CloseHandle(h); + + if (!NT_SUCCESS(status)) + { + errmsg = "FSCTL_BTRFS_CREATE_SUBVOL failed. Status="+convert(status)+" - " + os_last_error_str(); + return false; + } + + return true; +} + +bool SnapshotHelper::removeFilesystemWindows(std::string clientname, std::string name) +{ + std::string backup_storage_path = getBackupStoragePath(); + if (backup_storage_path.empty()) + return false; + + std::string parent_path = backup_storage_path + os_file_sep() + clientname; + + std::string subvol_path = parent_path + os_file_sep() + name; + + return os_remove_nonempty_dir(os_file_prefix(subvol_path)); +} + +int SnapshotHelper::testWindows() +{ + Server->Log("Testing for winbtrfs...", LL_INFO); + if (NtFsControlFilePtr_fun == nullptr) + return -1; + + std::string backup_storage_path = getBackupStoragePath(); + if (backup_storage_path.empty()) + return -1; + + std::string clientname = "testA54hj5luZtlorr494"; + std::string clientdir = backup_storage_path + os_file_sep() + + clientname; + + + bool create_dir_rc = os_create_dir(clientdir); + if (!create_dir_rc) + { + removeFilesystem(false, clientname, "A"); + removeFilesystem(false, clientname, "B"); + os_remove_dir(clientdir); + } + + create_dir_rc = create_dir_rc || os_create_dir(clientdir); + + if (!create_dir_rc) + { + Server->Log("Btrfs test failed. Could not clean and re-create client dir", LL_INFO); + return -1; + } + + std::string errmsg; + if (!createEmptyFilesystem(false, clientname, "A", errmsg)) + { + os_remove_dir(clientdir); + Server->Log("Btrfs test failed. Creating btrfs subvol failed: " + errmsg, LL_INFO); + return -1; + } + + writestring("test2", clientdir + os_file_sep() + "A" + os_file_sep() + "test2"); + + bool suc = true; + + if (!snapshotFileSystemWindows(clientname, "A", "B", errmsg)) + { + Server->Log("Btrfs test failed. Snapshotting btrfs subvol failed: " + errmsg, LL_INFO); + suc = false; + } + + if (suc) + { + writestring("test", clientdir + os_file_sep() + "A" + os_file_sep() + "test"); + + if (!os_create_hardlink(clientdir + os_file_sep() + "B" + os_file_sep() + "test", clientdir + os_file_sep() + "A" + os_file_sep() + "test", true, NULL)) + { + Server->Log("Btrfs test failed. Reflinking file failed." + os_last_error_str(), LL_INFO); + suc = false; + } + else + { + if (getFile(clientdir + os_file_sep() + "B" + os_file_sep() + "test") != "test") + { + Server->Log("Btrfs test failed. File 1 has wrong contents", LL_ERROR); + suc = false; + } + + if (getFile(clientdir + os_file_sep() + "B" + os_file_sep() + "test2") != "test2") + { + suc = false; + Server->Log("Btrfs test failed. File 2 has wrong contents", LL_ERROR); + } + } + } + + if (!removeFilesystemWindows(clientname, "A")) + { + Server->Log("Btrfs test failed. Could not remove subvolume A", LL_INFO); + suc = false; + } + + if (!removeFilesystemWindows(clientname, "B")) + { + Server->Log("Btrfs test failed. Could not remove subvolume A", LL_INFO); + suc = false; + } + + if (!os_remove_dir(clientdir)) + { + Server->Log("Btrfs test failed. Could not remove client dir", LL_INFO); + return -1; + } + + if (!suc) + { + return -1; + } + + Server->Log("Winbtrfs present and okay", LL_INFO); + + return 0; +} + +void SnapshotHelper::setupWindows() +{ + HMODULE mod = GetModuleHandleW(L"ntdll.dll"); + if (mod == nullptr) + return; + + NtFsControlFilePtr_fun = reinterpret_cast(GetProcAddress(mod, + "NtFsControlFile")); +} + +bool SnapshotHelper::snapshotFileSystemWindows(std::string clientname, std::string old_name, std::string snapshot_name, std::string& errmsg) +{ + std::string backup_storage_path = getBackupStoragePath(); + if (backup_storage_path.empty()) + return false; + + std::string parent_path = backup_storage_path + os_file_sep() + clientname; + + std::string old_subvol_path = parent_path + os_file_sep() + old_name; + + if (os_get_file_type(old_subvol_path) == 0) + { + errmsg = "Snapshot parent "+ old_subvol_path + " does not exist"; + return false; + } + + HANDLE h = CreateFileW(Server->ConvertToWchar(parent_path).c_str(), + FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (h == INVALID_HANDLE_VALUE) + { + errmsg = "Failed to open new subvol parent " + parent_path + ". " + os_last_error_str(); + return false; + } + + HANDLE h_old = CreateFileW(Server->ConvertToWchar(old_subvol_path).c_str(), + FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (h_old == INVALID_HANDLE_VALUE) + { + errmsg = "Failed to open old subvol " + old_subvol_path + ". " + os_last_error_str(); + CloseHandle(h); + return false; + } + + std::wstring name_w = Server->ConvertToWchar(snapshot_name); + + DWORD csb_len = static_cast(offsetof(btrfs_create_snapshot, name[0]) + name_w.size() * sizeof(WCHAR)); + std::vector create_snabshot_buf(csb_len); + btrfs_create_snapshot* create_snapshot = reinterpret_cast(create_snabshot_buf.data()); + create_snapshot->namelen = static_cast(name_w.size() * sizeof(WCHAR)); + memcpy(create_snapshot->name, name_w.data(), create_snapshot->namelen); + create_snapshot->subvol = h_old; + + IO_STATUS_BLOCK iosb; + NTSTATUS status = NtFsControlFilePtr_fun(h, nullptr, nullptr, nullptr, &iosb, + FSCTL_BTRFS_CREATE_SNAPSHOT, create_snapshot, csb_len, nullptr, 0); + + CloseHandle(h); + CloseHandle(h_old); + + if (!NT_SUCCESS(status)) + { + errmsg = "FSCTL_BTRFS_CREATE_SNAPSHOT failed. Status=" + convert(status) + " - " + os_last_error_str(); + return false; + } + + return true; +} + +bool SnapshotHelper::isSubvolumeWindows(std::string clientname, std::string name) +{ + std::string backup_storage_path = getBackupStoragePath(); + if (backup_storage_path.empty()) + return false; + + std::string parent_path = backup_storage_path + os_file_sep() + clientname; + + std::string subvol_path = parent_path + os_file_sep() + name; + + if (os_get_file_type(subvol_path) == 0) + return false; + + HANDLE h = CreateFileW(Server->ConvertToWchar(subvol_path).c_str(), + FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr); + + if (h == INVALID_HANDLE_VALUE) + return false; + + btrfs_get_file_ids file_ids; + + IO_STATUS_BLOCK iosb; + NTSTATUS status = NtFsControlFilePtr_fun(h, nullptr, nullptr, nullptr, &iosb, + FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &file_ids, sizeof(file_ids)); + + CloseHandle(h); + + if (!NT_SUCCESS(status)) + return false; + + return file_ids.inode == 0x100; +} + +#endif //_WIN32 diff --git a/urbackupserver/snapshot_helper.h b/urbackupserver/snapshot_helper.h index d7c0b47ef..39aaa1f75 100644 --- a/urbackupserver/snapshot_helper.h +++ b/urbackupserver/snapshot_helper.h @@ -12,5 +12,16 @@ class SnapshotHelper static bool makeReadonly(bool image, std::string clientname, std::string name); static std::string getMountpoint(bool image, std::string clientname, std::string name); private: + +#ifdef _WIN32 + static std::string getBackupStoragePath(); + static bool createEmptyFilesystemWindows(std::string clientname, std::string name, std::string& errmsg); + static bool removeFilesystemWindows(std::string clientname, std::string name); + static int testWindows(); + static void setupWindows(); + static bool snapshotFileSystemWindows(std::string clientname, std::string old_name, std::string snapshot_name, std::string& errmsg); + static bool isSubvolumeWindows(std::string clientname, std::string name); +#endif + static std::string helper_name; }; \ No newline at end of file From 259b664269d3da5bdf75b51ddf2557e0ab28de83 Mon Sep 17 00:00:00 2001 From: Moisie2000 Date: Sun, 26 Dec 2021 18:01:30 +0000 Subject: [PATCH 093/469] As we're now using launchctl to launch the backend and client processes, there's no need for the user-accessible login item routines. remove_login_item has been left, to allow cleaning up of previous installs. --- osx_installer/scripts2/postinstall | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/osx_installer/scripts2/postinstall b/osx_installer/scripts2/postinstall index 3c06c0c65..0e350def9 100755 --- a/osx_installer/scripts2/postinstall +++ b/osx_installer/scripts2/postinstall @@ -21,17 +21,4 @@ fi /bin/launchctl load "/Library/LaunchDaemons/org.urbackup.client.plist" /bin/launchctl start "org.urbackup.client.backend" -if test ! -e "$1.silent" -then - #There is user interaction required here :( - "$2/Contents/MacOS/urbackupclientgui" register_login_item -else - for console_user in $(ps aux | grep "loginwindow" | grep -v grep | awk '{print $1;}') - do - if [ "$console_user" != "root" ] - then - sudo -u "$console_user" "$2/Contents/MacOS/urbackupclientgui" daemon - fi - done -fi From a297bb7d040fe8e25f073e298dc322f9c9dadf18 Mon Sep 17 00:00:00 2001 From: Moisie2000 Date: Sat, 25 Dec 2021 15:37:37 +0000 Subject: [PATCH 094/469] Update macOS exclusions for macOS 12 Monterey (cherry picked from commit 4a370db579fb80a0b02b42875e45fc9afe203b5d) --- urbackupcommon/os_functions_lin.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/urbackupcommon/os_functions_lin.cpp b/urbackupcommon/os_functions_lin.cpp index 78a480d8e..9484ab57a 100644 --- a/urbackupcommon/os_functions_lin.cpp +++ b/urbackupcommon/os_functions_lin.cpp @@ -135,12 +135,14 @@ std::vector getFiles(const std::string &path, bool *has_error, bool ignor upath+dirp->d_name=="/private/var/db/ConfigurationProfiles/Store" || upath+dirp->d_name=="/private/var/db/CoreDuet/Knowledge" || upath+dirp->d_name=="/private/var/db/DifferentialPrivacy" || + upath+dirp->d_name=="/private/var/db/DumpPanic" || upath+dirp->d_name=="/private/var/db/fpsd/dvp" || upath+dirp->d_name=="/private/var/db/KernelExtensionManagement/Staging" || upath+dirp->d_name=="/private/var/db/lockdown" || upath+dirp->d_name=="/private/var/db/MobileIdentityService" || upath+dirp->d_name=="/private/var/db/oah" || upath+dirp->d_name=="/private/var/db/searchparty" || + upath+dirp->d_name=="/private/var/db/sysdiagnose/com.apple.sysdiagnose" || upath+dirp->d_name=="/private/var/networkd/db" || upath+dirp->d_name=="/private/var/protected/trustd/private" || upath+dirp->d_name=="/System/Library/Templates/Data/private/var/db/oah") From acf22648ab3f99b7412b8a08f3f06487e13cf94c Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 9 Jan 2022 10:24:22 +0100 Subject: [PATCH 095/469] Merge pull request #57 from ravenclaw900/patch-1 Add NSPrincipalClass key to macOS Info.plist (cherry picked from commit 5738ba4372c4f10512bed8b37623e75e32b737dc) --- osx_installer/info.plist | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osx_installer/info.plist b/osx_installer/info.plist index 1d6220499..ec7ff026a 100644 --- a/osx_installer/info.plist +++ b/osx_installer/info.plist @@ -28,5 +28,7 @@ 1 LSMultipleInstancesProhibited + NSPrincipalClass + wxNSApplication - \ No newline at end of file + From 9b433bff11ca69eb0a318f611ea2c044228e3afa Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 9 Jan 2022 11:44:45 +0100 Subject: [PATCH 096/469] Set min version for objective c code (cherry picked from commit d2d09e8fcc860e1402cc31955475a081e3f2cad2) --- create_osx_installer.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 30b055c3a..1264d86cd 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -40,9 +40,9 @@ cp osx_installer/daemon.plist osx-pkg/Library/LaunchDaemons/org.urbackup.client. mkdir -p osx-pkg/Library/LaunchAgents cp osx_installer/agent.plist osx-pkg/Library/LaunchAgents/org.urbackup.client.plist if !($development); then - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" else - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" fi make clean make -j5 From 668db14f27480ff9f7234058fb31cf5af162862a Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 9 Jan 2022 14:13:35 +0100 Subject: [PATCH 097/469] Set min version for objective c++ code --- create_osx_installer.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 1264d86cd..b8a4bc807 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -40,9 +40,9 @@ cp osx_installer/daemon.plist osx-pkg/Library/LaunchDaemons/org.urbackup.client. mkdir -p osx-pkg/Library/LaunchAgents cp osx_installer/agent.plist osx-pkg/Library/LaunchAgents/org.urbackup.client.plist if !($development); then - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" else - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" fi make clean make -j5 From e3d4f6269c5e7355adb6ff23f735137156fee396 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 12:29:36 +0100 Subject: [PATCH 098/469] Add MariaDB per detabase backup (by Alexander Zaitsev) (cherry picked from commit 23e75e858f657bd9e64e64ff2d47f2c4894b09ac) --- urbackupclient/backup_scripts/list | 14 ++++++++++++- urbackupclient/backup_scripts/mariadbdump | 21 ++++++++++++------- .../backup_scripts/mariadbdump.conf | 3 +++ 3 files changed, 30 insertions(+), 8 deletions(-) diff --git a/urbackupclient/backup_scripts/list b/urbackupclient/backup_scripts/list index 0fd730095..c4dada99f 100644 --- a/urbackupclient/backup_scripts/list +++ b/urbackupclient/backup_scripts/list @@ -7,7 +7,19 @@ CDIR=`dirname $0` . "SYSCONFDIR/postgresbase.conf" . "SYSCONFDIR/mariadbxtrabackup.conf" -if [ "x$MARIADB_DUMP_ENABLED" != "x0" ]; then echo "scriptname=mariadbdump&outputname=mariadbdump.sql"; fi +if [ "x$MARIADB_DUMP_ENABLED" != "x0" ] +then + if [ "x$MARIADB_DUMP_PER_BASE" != "x0" ] + then + baselist=$(mysql -u $MARIADB_BACKUP_USER -p=$MARIADB_BACKUP_PASSWORD -e 'show databases' -s --skip-column-names | grep -E -v 'information_schema|performance_schema') + for i in $baselist + do + echo "scriptname=mariadbdump&outputname=mariadbdump_$i.sql" + done + else + echo "scriptname=mariadbdump&outputname=mariadbdump.sql" + fi +fi if [ "x$POSTGRESQL_DUMP_ENABLED" != "x0" ]; then echo "scriptname=postgresqldump&outputname=postgresqldump.sql"; fi if [ "x$POSTGRESQL_BASE_ENABLED" != "x0" ]; then echo "scriptname=postgresbase&outputname=postgresbase&tar=1&orig_path=$POSTGRESQL_BASE_DIR"; fi if [ "x$MARIADB_XTRABACKUP_ENABLED" != "x0" ]; then echo "scriptname=mariadbxtrabackup&outputname=mariadbxtrabackup.xbstream.blockalign&orig_path=$MARIADB_TEMP_INCRDIR/mariadbxtrabackup.xbstream.blockalign"; fi diff --git a/urbackupclient/backup_scripts/mariadbdump b/urbackupclient/backup_scripts/mariadbdump index cb77dce2e..9565f50b8 100644 --- a/urbackupclient/backup_scripts/mariadbdump +++ b/urbackupclient/backup_scripts/mariadbdump @@ -27,12 +27,19 @@ set -e alias errcho='>&2 echo' -TIME=`date` -errcho "Starting backup of MariaDB at $TIME..." - -$MARIADB_DUMP --user=$MARIADB_BACKUP_USER --password=$MARIADB_BACKUP_PASSWORD --all-databases - -TIME=`date` -errcho "Backup of MariaDB finished at $TIME." +TIME="$(date)" +database=$(echo "$1" | sed 's/mariadbdump_//' | sed -e 's/\.sql$//') +if [ "$database" != "" ] && [ "x$MARIADB_DUMP_PER_BASE" != "x0" ] +then + errcho "Starting backup of MariaDB database $database at $TIME..." + $MARIADB_DUMP --user=$MARIADB_BACKUP_USER --password=$MARIADB_BACKUP_PASSWORD "$database" + TIME=`date` + errcho "Backup of MariaDB database $database finished at $TIME." +else + errcho "Starting backup of all MariaDB databases at $TIME..." + $MARIADB_DUMP --user=$MARIADB_BACKUP_USER --password=$MARIADB_BACKUP_PASSWORD --all-databases + TIME=`date` + errcho "Backup of MariaDB finished at $TIME." +fi exit 0 diff --git a/urbackupclient/backup_scripts/mariadbdump.conf b/urbackupclient/backup_scripts/mariadbdump.conf index 9faa0ce45..5a192a4f9 100644 --- a/urbackupclient/backup_scripts/mariadbdump.conf +++ b/urbackupclient/backup_scripts/mariadbdump.conf @@ -3,6 +3,9 @@ #Enable MariaDB dump backup MARIADB_DUMP_ENABLED=0 +#Enable per-base backup +MARIADB_DUMP_PER_BASE=0 + #Backup user account MARIADB_BACKUP_USER=root From 4e3801125e9af6262b35b6dde10e1693a9364781 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 12:26:28 +0100 Subject: [PATCH 099/469] Fix crypto++ compile flags detection (cherry picked from commit 5e76def21c85dcb5e7ddce02ae7ded0fdf00291c) --- configure.ac_client | 11 +++++++++-- configure.ac_server | 11 +++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 8ef2dda51..3587334aa 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -151,8 +151,15 @@ then [CryptoPP::AlignedAllocate(5);])], [AC_MSG_RESULT([yes])], [ - CRYPTOPP_CPPFLAGS="$CRYPTOPP_CPPFLAGS -DCRYPTOPP_DISABLE_ASM" - AC_MSG_RESULT([no]) + CRYPTOPP_INC="<$CRYPTOPP_INCLUDE_PREFIX/allocate.h>" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include $CRYPTOPP_INC], + [CryptoPP::AlignedAllocate(5);])], + [AC_MSG_RESULT([yes])], + [ + CRYPTOPP_CPPFLAGS="$CRYPTOPP_CPPFLAGS -DCRYPTOPP_DISABLE_ASM" + AC_MSG_RESULT([no]) + ]) ]) LDFLAGS="$SAVED_LDFLAGS" CPPFLAGS="$SAVED_CPPFLAGS" diff --git a/configure.ac_server b/configure.ac_server index aed98ac01..eaa4e2e45 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -184,8 +184,15 @@ then [CryptoPP::AlignedAllocate(5);])], [AC_MSG_RESULT([yes])], [ - CRYPTOPP_CPPFLAGS="$CRYPTOPP_CPPFLAGS -DCRYPTOPP_DISABLE_ASM" - AC_MSG_RESULT([no]) + CRYPTOPP_INC="<$CRYPTOPP_INCLUDE_PREFIX/allocate.h>" + AC_LINK_IFELSE( + [AC_LANG_PROGRAM([#include $CRYPTOPP_INC], + [CryptoPP::AlignedAllocate(5);])], + [AC_MSG_RESULT([yes])], + [ + CRYPTOPP_CPPFLAGS="$CRYPTOPP_CPPFLAGS -DCRYPTOPP_DISABLE_ASM" + AC_MSG_RESULT([no]) + ]) ]) LDFLAGS="$SAVED_LDFLAGS" CPPFLAGS="$SAVED_CPPFLAGS" From 5ec8c9003c7d63d6fde475d765af98ede326d870 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 12:27:16 +0100 Subject: [PATCH 100/469] Add new config options for only binding to localhost (cherry picked from commit f5d8bce2bbbc9aa177b019ae38c25cf4cee8eaf8) --- defaults_server | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/defaults_server b/defaults_server index 07bc9b1ec..267b46417 100755 --- a/defaults_server +++ b/defaults_server @@ -10,12 +10,20 @@ FASTCGI_PORT=55413 #Enable internal HTTP server +# Required for serving web interface without FastCGI +# and for websocket connections from client HTTP_SERVER="true" #Port for the web interface #(if internal HTTP server is enabled) HTTP_PORT=55414 +#Bind HTTP server to localhost only +HTTP_LOCALHOST_ONLY=false + +#Bind Internet port to localhost only +INTERNET_LOCALHOST_ONLY=false + #log file name LOGFILE="/var/log/urbackup.log" From 09098ac21bdfbab6a71a7bd3c3b5d2bdb6e4597d Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 12:28:36 +0100 Subject: [PATCH 101/469] Add compression buffer for synchronous compression (cherry picked from commit 77dda1c0392b33e4242a61bc8196ccb68170da20) --- fsimageplugin/CompressedFile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index e26e03ec4..0d788b284 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -68,7 +68,7 @@ CompressedFile::CompressedFile( std::string pFilename, int pMode, size_t n_threa blocksize = c_cacheBuffersize; writeHeader(); hotCache.reset(new LRUMemCache(blocksize, c_ncacheItems, n_threads)); - initCompressedBuffers(n_threads); + initCompressedBuffers(n_threads + 1); } if(hotCache.get()) @@ -93,7 +93,7 @@ CompressedFile::CompressedFile(IFile* file, bool openExisting, bool readOnly, si blocksize = c_cacheBuffersize; writeHeader(); hotCache.reset(new LRUMemCache(blocksize, c_ncacheItems, n_threads)); - initCompressedBuffers(n_threads); + initCompressedBuffers(n_threads + 1); } if(hotCache.get()!=NULL) { From b0a441e5a7ecb05a143b506b631431b305d0db8e Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 12:31:36 +0100 Subject: [PATCH 102/469] Open reparse point when reading FRN (cherry picked from commit 1756bbfd0b827879dd0e8a1a3850677c66f404b1) --- urbackupclient/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index a1d4f3022..8255f22c2 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -5648,7 +5648,7 @@ void IndexThread::commitModifyHardLinks() #ifdef _WIN32 uint128 IndexThread::getFrn(const std::string & fn) { - HANDLE hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(fn)).c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); + HANDLE hFile = CreateFileW(Server->ConvertToWchar(os_file_prefix(fn)).c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS| FILE_FLAG_OPEN_REPARSE_POINT, NULL); if (hFile == INVALID_HANDLE_VALUE) { From e17b6833e66a477202d7a59b1c922e03f9ac2b43 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 15:27:24 +0100 Subject: [PATCH 103/469] Correctly set client settings if set via command line --- clientctl/main.cpp | 10 ++++- urbackupclient/ClientService.cpp | 69 ++++++++++++++++++++++++++++++-- 2 files changed, 74 insertions(+), 5 deletions(-) diff --git a/clientctl/main.cpp b/clientctl/main.cpp index 2708c6af0..b5b9f8894 100644 --- a/clientctl/main.cpp +++ b/clientctl/main.cpp @@ -900,6 +900,9 @@ int action_set_settings(std::vector args) "New value to set the setting to", false, "setting value", cmd); + TCLAP::SwitchArg no_merge_arg("n", "no-merge", + "Don't merge server and client settings if possible", cmd); + cmd.parse(args); if (key_arg.getValue().size() != value_arg.getValue().size()) @@ -919,7 +922,12 @@ int action_set_settings(std::vector args) s_settings += key_arg.getValue()[i] + "=" + value_arg.getValue()[i] + "\n"; } - s_settings += "keep_old_settings=true\n"; + s_settings += "set_client_settings=1\n"; + + if (!no_merge_arg.getValue()) + { + s_settings += "merge_client_settings=0\n"; + } bool no_perm; bool b = Connector::updateSettings(s_settings, no_perm); diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index bae2fc500..400763acf 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -1971,9 +1971,12 @@ void ClientConnector::replaceSettings(const std::string &pData) std::auto_ptr old_settings(Server->createFileSettingsReader(settings_fn)); + bool set_client_settings = new_settings->getValue("set_client_settings", "0") == "1"; + bool merge_client_settings = new_settings->getValue("merge_client_settings", "0") == "1"; + std::vector new_keys = new_settings->getKeys(); - bool modified_settings=true; - if(old_settings.get()!=NULL) + bool modified_settings= set_client_settings; + if(old_settings.get()!=NULL && !set_client_settings) { modified_settings=false; std::vector old_keys = old_settings->getKeys(); @@ -2005,26 +2008,84 @@ void ClientConnector::replaceSettings(const std::string &pData) } } + const std::vector mergable_settings_list = getClientMergableSettingsList(); + if(modified_settings) { std::string new_data; + std::vector add_new_keys; + std::vector skip_new_keys; + for(size_t i=0;igetValue(new_keys[i], &val)) { - new_data+=new_keys[i]+"="+val+"\n"; + if (!set_client_settings) + { + new_data += new_key + "=" + val + "\n"; + } + else + { + if (new_key == "internet_mode_enabled" || + new_key == "internet_server" || + new_key == "internet_server_port" || + new_key == "internet_server_proxy" || + new_key == "internet_authkey" || + new_key == "computername") + { + new_data += new_key + "=" + val + "\n"; + } + else + { + skip_new_keys.push_back(new_key); + } + + int old_use = old_settings->getValue(new_key + ".use", 0); + int64 old_use_lm = old_settings->getValue(new_key + ".use_lm", 0); + int64 new_use_lm = Server->getTimeSeconds(); + if (old_use_lm == new_use_lm) + ++new_use_lm; + std::string old_client_val; + old_settings->getValue(new_key +".client", &old_client_val); + + int new_use = c_use_value_client; + + if (merge_client_settings && + std::find(mergable_settings_list.begin(), + mergable_settings_list.end(), new_key) != mergable_settings_list.end()) + { + new_use = old_use & c_use_value_client; + } + + if (new_use != old_use) + { + new_data += new_key + ".use=" + convert(new_use) + "\n"; + add_new_keys.push_back(new_key + ".use"); + new_data += new_key + ".use_lm=" + convert(new_use_lm) + "\n"; + add_new_keys.push_back(new_key + ".use_lm"); + } + } } } + new_keys.insert(new_keys.end(), add_new_keys.begin(), + add_new_keys.end()); + + std::sort(new_keys.begin(), new_keys.end()); + std::sort(skip_new_keys.begin(), skip_new_keys.end()); + if (old_settings.get() != NULL) { std::vector old_keys = old_settings->getKeys(); for (size_t i = 0; i < old_keys.size(); ++i) { - if (std::find(new_keys.begin(), new_keys.end(), old_keys[i]) == new_keys.end()) + if (std::binary_search(skip_new_keys.begin(), skip_new_keys.end(), old_keys[i]) || + !std::binary_search(new_keys.begin(), new_keys.end(), old_keys[i])) { std::string val; if (old_settings->getValue(old_keys[i], &val)) From db65d5cdb0aaa870a903ac6839ff884b72080af1 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 15:51:39 +0100 Subject: [PATCH 104/469] Add switches for some settings --- clientctl/main.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 71 insertions(+), 1 deletion(-) diff --git a/clientctl/main.cpp b/clientctl/main.cpp index b5b9f8894..5767c5be2 100644 --- a/clientctl/main.cpp +++ b/clientctl/main.cpp @@ -903,6 +903,22 @@ int action_set_settings(std::vector args) TCLAP::SwitchArg no_merge_arg("n", "no-merge", "Don't merge server and client settings if possible", cmd); + TCLAP::ValueArg server_url_arg("", "server-url", + "URL of server to connect to", + false, "", "url", cmd); + + TCLAP::ValueArg name_arg("", "name", + "Client name", + false, "", "string", cmd); + + TCLAP::ValueArg authkey_arg("", "authkey", + "Server authentication key for client", + false, "", "string", cmd); + + TCLAP::ValueArg proxy_arg("", "proxy", + "HTTP CONNECT proxy to use to connect to server", + false, "", "url", cmd); + cmd.parse(args); if (key_arg.getValue().size() != value_arg.getValue().size()) @@ -916,10 +932,64 @@ int action_set_settings(std::vector args) return 3; } + str_map arg_settings; + + if (server_url_arg.isSet()) + { + std::string server_url = server_url_arg.getValue(); + std::string internet_server_port = "55415"; + + if (server_url.find("urbackup://") != 0 && + server_url.find("wss://") != 0 && + server_url.find("ws://") != 0) + { + std::cerr << "Server URL must start with urbackup://, wss:// or ws://" << std::endl; + return 4; + } + + if (server_url.find("urbackup://") == 0) + { + std::string hostname = server_url.substr(11); + if (hostname.find(":") != std::string::npos) + { + internet_server_port = getafter(":", server_url); + } + } + + arg_settings["internet_server_port"] = internet_server_port; + arg_settings["internet_server"] = server_url; + arg_settings["internet_mode_enabled"] = "true"; + } + + if (authkey_arg.isSet()) + { + arg_settings["internet_authkey"] = authkey_arg.getValue(); + arg_settings["internet_mode_enabled"] = "true"; + } + + if (name_arg.isSet()) + { + arg_settings["computername"] = name_arg.getValue(); + } + + if (proxy_arg.isSet()) + { + arg_settings["internet_server_proxy"] = proxy_arg.getValue(); + arg_settings["internet_mode_enabled"] = "true"; + } + 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"; + std::string key = key_arg.getValue()[i]; + if(arg_settings.find(key)==arg_settings.end()) + s_settings += key + "=" + value_arg.getValue()[i] + "\n"; + } + + for (str_map::const_iterator it = arg_settings.begin(); + it != arg_settings.end(); ++it) + { + s_settings += it->first + "=" + it->second + "\n"; } s_settings += "set_client_settings=1\n"; From e08b6709a585a483bed282bda2d4193d3de585e0 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 22 Jan 2022 15:52:11 +0100 Subject: [PATCH 105/469] Use new switches when showing client config --- urbackupserver/serverinterface/add_client.cpp | 10 ++++++++++ urbackupserver/www/js/urbackup.js | 2 +- urbackupserver/www/templates/client_added.htm | 9 ++++----- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/urbackupserver/serverinterface/add_client.cpp b/urbackupserver/serverinterface/add_client.cpp index 02298654a..57b7a3384 100644 --- a/urbackupserver/serverinterface/add_client.cpp +++ b/urbackupserver/serverinterface/add_client.cpp @@ -48,9 +48,19 @@ ACTION_IMPL(add_client) SSettings* s = settings.getSettings(); + std::string server_url = s->internet_server; + + if (server_url.find("urbackup://") != 0 && + server_url.find("ws://") != 0 && + server_url.find("wss://") != 0) + { + server_url = "urbackup://" + s->internet_server + ":" + convert(s->internet_server_port); + } + ret.set("new_clientid", id); ret.set("new_clientname", POST["clientname"]); ret.set("new_authkey", new_authkey); + ret.set("server_url", server_url); ret.set("internet_server", s->internet_server); ret.set("internet_server_port", s->internet_server_port); if (!s->internet_server_proxy.empty()) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 65479b9f6..042f26f3f 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5996,7 +5996,7 @@ function addNewClient3(data) data.mac_url = downloadClientURL(data.new_clientid, data.new_authkey, "mac"); if(data.internet_server_proxy) { - data.internet_proxy_settings = " -k internet_server_proxy -v \""+data.internet_server_proxy+"\""; + data.internet_proxy_settings = " --proxy \""+data.internet_server_proxy+"\""; } var ndata=dustRender("client_added", data); diff --git a/urbackupserver/www/templates/client_added.htm b/urbackupserver/www/templates/client_added.htm index 32ab7fdc6..a306ab1aa 100644 --- a/urbackupserver/www/templates/client_added.htm +++ b/urbackupserver/www/templates/client_added.htm @@ -43,7 +43,7 @@ sh $TF &&\
    rm -f $TF &&\
    urbackupclientctl wait-for-backend &&\
    - urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v {internet_server} -k internet_server_port -v {internet_server_port} -k computername -v "{new_clientname}" -k internet_authkey -v {new_authkey}{internet_proxy_settings} &&\
    + urbackupclientctl set-settings --server-url "{server_url}" --name "{new_clientname}" --authkey "{new_authkey}"{internet_proxy_settings} &&\
    ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\
    ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient ) @@ -59,9 +59,8 @@
    • {tGo to the settings screen on the client}
    • {tEnable the internet mode on the client}
    • -
    • {tSet the internet server to:} {internet_server}
    • -
    • {tSet the internet server port to:} {internet_server_port}
    • -
    • {tSet the computer name to:} {new_clientname}
    • +
    • {tSet the URL to connect to:} {server_url}
    • +
    • {tSet the name to:} {new_clientname}
    • {tSet the authentication key to:} {new_authkey}
    • {tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient}
    @@ -72,7 +71,7 @@

    urbackupclientctl wait-for-backend
    - urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v {internet_server} -k internet_server_port -v {internet_server_port} -k computername -v "{new_clientname}" -k internet_authkey -v {new_authkey}{internet_proxy_settings}
    + urbackupclientctl set-settings --server-url "{server-url}" --name "{new_clientname}" --authkey "{new_authkey}"{internet_proxy_settings}
    [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
    [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient
    From c7399e6dcb382cc66dc4209f787560f0c58a204d Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 12:14:08 +0100 Subject: [PATCH 106/469] Fix connecting to list of servers --- clientctl/main.cpp | 48 ++++++++++++------- urbackupserver/server_settings.cpp | 2 +- urbackupserver/server_settings.h | 2 +- urbackupserver/serverinterface/add_client.cpp | 37 ++++++++++++-- .../serverinterface/download_client.cpp | 4 +- 5 files changed, 68 insertions(+), 25 deletions(-) diff --git a/clientctl/main.cpp b/clientctl/main.cpp index 5767c5be2..85944bde5 100644 --- a/clientctl/main.cpp +++ b/clientctl/main.cpp @@ -936,28 +936,44 @@ int action_set_settings(std::vector args) if (server_url_arg.isSet()) { - std::string server_url = server_url_arg.getValue(); - std::string internet_server_port = "55415"; - - if (server_url.find("urbackup://") != 0 && - server_url.find("wss://") != 0 && - server_url.find("ws://") != 0) + std::vector server_urls; + Tokenize(server_url_arg.getValue(), server_urls, ";"); + std::string internet_server; + std::string internet_server_port; + for (size_t i = 0; i < server_urls.size(); ++i) { - std::cerr << "Server URL must start with urbackup://, wss:// or ws://" << std::endl; - return 4; - } + std::string server_url = server_urls[i]; + std::string server_port = "55415"; - if (server_url.find("urbackup://") == 0) - { - std::string hostname = server_url.substr(11); - if (hostname.find(":") != std::string::npos) + if (server_url.find("urbackup://") != 0 && + server_url.find("wss://") != 0 && + server_url.find("ws://") != 0) + { + std::cerr << "Server URL must start with urbackup://, wss:// or ws://" << std::endl; + return 4; + } + + if (server_url.find("urbackup://") == 0) { - internet_server_port = getafter(":", server_url); - } + std::string hostname = server_url.substr(11); + if (hostname.find(":") != std::string::npos) + { + server_port = getafter(":", server_url); + } + server_url = hostname; + } + + if (!internet_server.empty()) + internet_server += ";"; + if (!internet_server_port.empty()) + internet_server_port += ";"; + + internet_server += server_url; + internet_server_port += server_port; } arg_settings["internet_server_port"] = internet_server_port; - arg_settings["internet_server"] = server_url; + arg_settings["internet_server"] = internet_server; arg_settings["internet_mode_enabled"] = "true"; } diff --git a/urbackupserver/server_settings.cpp b/urbackupserver/server_settings.cpp index 975bd5797..1eb5a003e 100644 --- a/urbackupserver/server_settings.cpp +++ b/urbackupserver/server_settings.cpp @@ -321,7 +321,7 @@ void ServerSettings::readSettingsDefault(ISettingsReader* settings_default, settings->max_sim_backups = settings_global->getValue("max_sim_backups", 100); settings->cleanup_window = settings_global->getValue("cleanup_window", "1-7/3-4"); settings->backup_database = (settings_global->getValue("backup_database", "true") == "true"); - settings->internet_server_port = (unsigned short)(atoi(settings_global->getValue("internet_server_port", "55415").c_str())); + settings->internet_server_port = settings_global->getValue("internet_server_port", "55415"); settings->internet_server_proxy = settings_global->getValue("internet_server_proxy", ""); settings->internet_server = settings_global->getValue("internet_server", ""); settings->global_internet_speed = settings_global->getValue("global_internet_speed", "-1"); diff --git a/urbackupserver/server_settings.h b/urbackupserver/server_settings.h index c173b3bf2..0b2892abd 100644 --- a/urbackupserver/server_settings.h +++ b/urbackupserver/server_settings.h @@ -85,7 +85,7 @@ struct SSettings bool backup_database; std::string internet_server; bool client_set_settings; - unsigned short internet_server_port; + std::string internet_server_port; std::string internet_server_proxy; std::string internet_authkey; bool internet_full_file_backups; diff --git a/urbackupserver/serverinterface/add_client.cpp b/urbackupserver/serverinterface/add_client.cpp index 57b7a3384..e9e576ffe 100644 --- a/urbackupserver/serverinterface/add_client.cpp +++ b/urbackupserver/serverinterface/add_client.cpp @@ -48,13 +48,40 @@ ACTION_IMPL(add_client) SSettings* s = settings.getSettings(); - std::string server_url = s->internet_server; + std::vector internet_servers; + Tokenize(s->internet_server, internet_servers, ";"); - if (server_url.find("urbackup://") != 0 && - server_url.find("ws://") != 0 && - server_url.find("wss://") != 0) + std::vector internet_server_ports; + Tokenize(s->internet_server_port, internet_server_ports, ";"); + + std::string server_url; + + for (size_t i = 0; i < internet_servers.size(); ++i) { - server_url = "urbackup://" + s->internet_server + ":" + convert(s->internet_server_port); + std::string& internet_server = internet_servers[i]; + + std::string port = "55415"; + if (i < internet_server_ports.size()) + port = internet_server_ports[i]; + else if (!internet_server_ports.empty()) + port = internet_server_ports[internet_server_ports.size()-1]; + + if (i > 0) + server_url += ";"; + + if (internet_server.find("urbackup://") != 0 && + internet_server.find("ws://") != 0 && + internet_server.find("wss://") != 0) + { + if(port!="55415") + server_url += "urbackup://" + internet_server + ":" + port; + else + server_url += "urbackup://" + internet_server; + } + else + { + server_url += internet_server; + } } ret.set("new_clientid", id); diff --git a/urbackupserver/serverinterface/download_client.cpp b/urbackupserver/serverinterface/download_client.cpp index ff1a7a999..dbe8b9a78 100644 --- a/urbackupserver/serverinterface/download_client.cpp +++ b/urbackupserver/serverinterface/download_client.cpp @@ -54,7 +54,7 @@ namespace std::string ret="\r\n"; ret+="internet_mode_enabled="+convert(settingsptr->internet_mode_enabled)+"\r\n"; ret+="internet_server="+settingsptr->internet_server+"\r\n"; - ret+="internet_server_port="+convert(settingsptr->internet_server_port)+"\r\n"; + ret+="internet_server_port="+settingsptr->internet_server_port+"\r\n"; ret += "internet_server_proxy=" + settingsptr->internet_server_proxy + "\r\n"; ret+="internet_authkey="+(authkey.empty() ? settingsptr->internet_authkey : authkey ) +"\r\n"; if(!clientname.empty()) @@ -77,7 +77,7 @@ namespace std::string ret = "RESTORE_IMAGE=1\n"; ret += "SERVER_NAME=\"" + settingsptr->internet_server + "\"\n"; - ret += "SERVER_PORT=\"" + convert(settingsptr->internet_server_port) + "\"\n"; + ret += "SERVER_PORT=\"" + settingsptr->internet_server_port + "\"\n"; ret += "SERVER_PROXY=\"" + settingsptr->internet_server_proxy + "\"\n"; ret += "RESTORE_AUTHKEY=\"" + restore_authkey+ "\"\n"; ret += "RESTORE_TOKEN=\"" + token + "\"\n"; From 78a01a8d483cf4b2deb415b32a99cbc758f4f400 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 12:41:23 +0100 Subject: [PATCH 107/469] Revert "Add compression buffer for synchronous compression" This reverts commit 09098ac21bdfbab6a71a7bd3c3b5d2bdb6e4597d. --- fsimageplugin/CompressedFile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 0d788b284..e26e03ec4 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -68,7 +68,7 @@ CompressedFile::CompressedFile( std::string pFilename, int pMode, size_t n_threa blocksize = c_cacheBuffersize; writeHeader(); hotCache.reset(new LRUMemCache(blocksize, c_ncacheItems, n_threads)); - initCompressedBuffers(n_threads + 1); + initCompressedBuffers(n_threads); } if(hotCache.get()) @@ -93,7 +93,7 @@ CompressedFile::CompressedFile(IFile* file, bool openExisting, bool readOnly, si blocksize = c_cacheBuffersize; writeHeader(); hotCache.reset(new LRUMemCache(blocksize, c_ncacheItems, n_threads)); - initCompressedBuffers(n_threads + 1); + initCompressedBuffers(n_threads); } if(hotCache.get()!=NULL) { From 742b882f2f1bd369c37ce6f0f8c721aed439de86 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 12:44:09 +0100 Subject: [PATCH 108/469] Fix handling of default number of compression threads --- fsimageplugin/vhdfile.cpp | 43 ++++++++++++++++++-------------------- fsimageplugin/vhdfile.h | 2 ++ fsimageplugin/vhdxfile.cpp | 7 +++++-- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/fsimageplugin/vhdfile.cpp b/fsimageplugin/vhdfile.cpp index 36856c67f..7f20184b8 100644 --- a/fsimageplugin/vhdfile.cpp +++ b/fsimageplugin/vhdfile.cpp @@ -43,34 +43,31 @@ const int64 unixtime_offset=946684800; const unsigned int sector_size=512; -namespace +size_t VHDFile::getNumCompThreads(bool read_only) { - size_t getNumCompThreads(bool read_only) - { - if (read_only) - return 1; + if (read_only) + return 1; - const size_t maxCpus = 5; + const size_t maxCpus = 5; #ifdef _WIN32 - SYSTEM_INFO system_info; - GetSystemInfo(&system_info); - DWORD numCpus = system_info.dwNumberOfProcessors; - if (numCpus == 0) - return 1; - return (std::min)(static_cast(numCpus), maxCpus); + SYSTEM_INFO system_info; + GetSystemInfo(&system_info); + DWORD numCpus = system_info.dwNumberOfProcessors; + if (numCpus == 0) + return 1; + return (std::min)(static_cast(numCpus), maxCpus); #else - long numCpus = sysconf(_SC_NPROCESSORS_ONLN); - if (numCpus < 0) - { - numCpus = 2; - } - else if (numCpus == 0) - { - numCpus = 1; - } - return (std::min)(static_cast(numCpus), maxCpus); -#endif + long numCpus = sysconf(_SC_NPROCESSORS_ONLN); + if (numCpus < 0) + { + numCpus = 2; } + else if (numCpus == 0) + { + numCpus = 1; + } + return (std::min)(static_cast(numCpus), maxCpus); +#endif } VHDFile::VHDFile(const std::string &fn, bool pRead_only, uint64 pDstsize, unsigned int pBlocksize, bool fast_mode, bool compress, size_t compress_n_threads) diff --git a/fsimageplugin/vhdfile.h b/fsimageplugin/vhdfile.h index edba3c5cc..c08f85d4c 100644 --- a/fsimageplugin/vhdfile.h +++ b/fsimageplugin/vhdfile.h @@ -122,6 +122,8 @@ class VHDFile : public IVHDFile, public IFile virtual bool setUnused(_i64 unused_start, _i64 unused_end); + static size_t getNumCompThreads(bool read_only); + private: bool check_if_compressed(); diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index 03d50e221..22717b88f 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -26,6 +26,7 @@ #include "ClientBitmap.h" #include "IFilesystem.h" #include "fs/ntfs.h" +#include "vhdfile.h" #define PAYLOAD_BLOCK_NOT_PRESENT 0 #define PAYLOAD_BLOCK_UNDEFINED 1 @@ -2519,7 +2520,8 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre if (compress) { compressed_file.reset(new CompressedFile(backing_file.get(), - false, read_only, compress_n_threads)); + false, read_only, + compress_n_threads == 0 ? VHDFile::getNumCompThreads(read_only) : compress_n_threads)); if (compressed_file->hasError()) { @@ -2541,7 +2543,8 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre if (check_if_compressed()) { compressed_file.reset(new CompressedFile(backing_file.get(), - true, read_only, compress_n_threads)); + true, read_only, + compress_n_threads == 0 ? VHDFile::getNumCompThreads(read_only) : compress_n_threads)); if (compressed_file->hasError()) { From 92459eb33aa6457b6a8ab7221a3f554619270171 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 12:54:10 +0100 Subject: [PATCH 109/469] Fix backing file destruction --- fsimageplugin/vhdxfile.cpp | 26 ++++++++++++++++---------- fsimageplugin/vhdxfile.h | 3 ++- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index 22717b88f..f364db3db 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1204,7 +1204,7 @@ bool VHDXFile::setUnused(_i64 unused_start, _i64 unused_end) bool VHDXFile::setBackingFileSize(_i64 fsize) { - if (file != backing_file.get()) + if (file != backing_file) { return false; } @@ -1893,7 +1893,7 @@ bool VHDXFile::createNew() return false; } - if (file == backing_file.get() && + if (file == backing_file && !backing_file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) { Server->Log("Error writing new bat region. " + os_last_error_str(), LL_WARNING); @@ -2007,7 +2007,7 @@ bool VHDXFile::replayLog() int64 new_fsize = -1; if (file->Size() < head_entry.new_fsize && - file == backing_file.get()) + file == backing_file) { if (backing_file->Resize(head_entry.new_fsize, false)) new_fsize = head_entry.new_fsize; @@ -2451,7 +2451,7 @@ bool VHDXFile::allocateBatBlockFull(int64 block) { allocated_size = new_pos + block_size + allocate_size_add_size; - if (file == backing_file.get() && + if (file == backing_file && !backing_file->Resize(allocated_size, false)) { Server->Log("Error resizing backing file to new allocated size " @@ -2500,15 +2500,17 @@ void VHDXFile::calcNextPayloadPos() bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_threads) { - backing_file.reset(Server->openFile(fn, read_only ? MODE_READ : MODE_RW_CREATE)); + backing_file_holder.reset(Server->openFile(fn, read_only ? MODE_READ : MODE_RW_CREATE)); - if (backing_file.get() == NULL) + if (backing_file_holder.get() == NULL) { Server->Log("Error opening VHDX backing file at \"" + fn + "\". " + os_last_error_str(), LL_WARNING); return false; } + backing_file = backing_file_holder.get(); + if (backing_file->Size() == 0) { if (read_only) @@ -2519,10 +2521,12 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre if (compress) { - compressed_file.reset(new CompressedFile(backing_file.get(), + compressed_file.reset(new CompressedFile(backing_file, false, read_only, compress_n_threads == 0 ? VHDFile::getNumCompThreads(read_only) : compress_n_threads)); + backing_file_holder.release(); + if (compressed_file->hasError()) { Server->Log("Error opening VHDX compressed file -1", LL_WARNING); @@ -2533,7 +2537,7 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre } else { - file = backing_file.get(); + file = backing_file; } return createNew(); @@ -2542,10 +2546,12 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre { if (check_if_compressed()) { - compressed_file.reset(new CompressedFile(backing_file.get(), + compressed_file.reset(new CompressedFile(backing_file, true, read_only, compress_n_threads == 0 ? VHDFile::getNumCompThreads(read_only) : compress_n_threads)); + backing_file_holder.release(); + if (compressed_file->hasError()) { Server->Log("Error opening VHDX compressed file -2", LL_WARNING); @@ -2556,7 +2562,7 @@ bool VHDXFile::open(const std::string& fn, bool compress, size_t compress_n_thre } else { - file = backing_file.get(); + file = backing_file; } if (!readHeader()) diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h index 337082e0a..663ba7092 100644 --- a/fsimageplugin/vhdxfile.h +++ b/fsimageplugin/vhdxfile.h @@ -153,7 +153,8 @@ class VHDXFile : public IVHDFile, public IFile std::vector bat_buf; std::set pending_bat_entries; - std::auto_ptr backing_file; + IFsFile* backing_file; + std::auto_ptr backing_file_holder; IFile* file; std::auto_ptr compressed_file; int64 allocated_size; From 5d066b02cc8bebabc327973fe8f9b2219b1ba8fd Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 13:11:44 +0100 Subject: [PATCH 110/469] Log more information --- fsimageplugin/vhdxfile.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index f364db3db..9fe34174f 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -2331,7 +2331,7 @@ bool VHDXFile::readMeta() || parent_locator_entry->KeyOffset>10*1024*1024) { Server->Log("Parent locator entry key offset not plausible: "+convert(parent_locator_entry->KeyOffset)+ - " length: "+convert(parent_locator_entry->KeyLength), + " length: "+convert(parent_locator_entry->KeyLength)+" entry_buf.size()="+convert(entry_buf.size()), LL_WARNING); return false; } @@ -2339,8 +2339,8 @@ bool VHDXFile::readMeta() if (parent_locator_entry->ValueOffset + parent_locator_entry->ValueLength >= entry_buf.size() || parent_locator_entry->ValueOffset > 10 * 1024 * 1024) { - Server->Log("Parent locator entry key offset not plausible: " + convert(parent_locator_entry->ValueOffset)+ - " length: " + convert(parent_locator_entry->ValueLength), + Server->Log("Parent locator entry value offset not plausible: " + convert(parent_locator_entry->ValueOffset)+ + " length: " + convert(parent_locator_entry->ValueLength) + " entry_buf.size()=" + convert(entry_buf.size()), LL_WARNING); return false; } From 3d0204539afb2c7dd754706aef12d6f138cef3d6 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 13:13:13 +0100 Subject: [PATCH 111/469] Remove masking key logging --- urbackupcommon/WebSocketPipe.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/urbackupcommon/WebSocketPipe.cpp b/urbackupcommon/WebSocketPipe.cpp index 336409820..1c5924e03 100644 --- a/urbackupcommon/WebSocketPipe.cpp +++ b/urbackupcommon/WebSocketPipe.cpp @@ -153,7 +153,6 @@ bool WebSocketPipe::Write(const char* buffer, size_t bsize, int timeoutms, bool memcpy(new_buf.data(), header, header_pos); - Server->Log("Masking key: " + convert(*((unsigned int*)masking_key))); for (size_t i = 0; i < bsize; ++i) { size_t j = i % 4; From fd0abdecc9426482a8bab750bc31a324ea572fcb Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 13:22:15 +0100 Subject: [PATCH 112/469] Fix entry key/value check --- fsimageplugin/vhdxfile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index 9fe34174f..39f43057c 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -2327,7 +2327,7 @@ bool VHDXFile::readMeta() { VhdxParentLocatorEntry* parent_locator_entry = reinterpret_cast(entry_buf.data() + 20 + i * 12); - if (parent_locator_entry->KeyOffset + parent_locator_entry->KeyLength >= entry_buf.size() + if (parent_locator_entry->KeyOffset + parent_locator_entry->KeyLength > entry_buf.size() || parent_locator_entry->KeyOffset>10*1024*1024) { Server->Log("Parent locator entry key offset not plausible: "+convert(parent_locator_entry->KeyOffset)+ @@ -2336,7 +2336,7 @@ bool VHDXFile::readMeta() return false; } - if (parent_locator_entry->ValueOffset + parent_locator_entry->ValueLength >= entry_buf.size() + if (parent_locator_entry->ValueOffset + parent_locator_entry->ValueLength > entry_buf.size() || parent_locator_entry->ValueOffset > 10 * 1024 * 1024) { Server->Log("Parent locator entry value offset not plausible: " + convert(parent_locator_entry->ValueOffset)+ From 4ed31d6dcdd86cb09a815fb36d9102d199f11acb Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 13:38:15 +0100 Subject: [PATCH 113/469] Show passive settings/correct name --- urbackupserver/www/templates/settings_user.htm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/www/templates/settings_user.htm b/urbackupserver/www/templates/settings_user.htm index b40472abf..6622382fc 100644 --- a/urbackupserver/www/templates/settings_user.htm +++ b/urbackupserver/www/templates/settings_user.htm @@ -26,8 +26,9 @@

  • {tClient}
  • {tArchive}
  • {tAlerts}
  • +
  • {tLocal/passive client}
  • {internet_settings_start|s} -
  • {tInternet}
  • +
  • {tInternet/Active client}
  • {internet_settings_end|s}
  • {tAdvanced}
  • From 594f291823ef3189ea4d18bac3a523ad34bbfc47 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 13:39:11 +0100 Subject: [PATCH 114/469] Update templates --- urbackupserver/www/js/templates.js | 152 ++++++++++++++--------------- 1 file changed, 76 insertions(+), 76 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 7766c45c3..1cd4b749c 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,76 +1,76 @@ -(function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tEdit alert scripts"]),ctx,"h").write("
     

    ").reference(ctx._get(false, ["tAlert script parameters"]),ctx,"h").write("

    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("



    ").reference(ctx._get(false, ["tAlert script"]),ctx,"h").write("

    \t\t

    ").exists(ctx._get(false, ["saved_ok"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("
    Saved script successfully.
    ");}return body_0;})(); -(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAdd client"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDownload the client from:"]),ctx,"h").write(" www.urbackup.org

    ").reference(ctx._get(false, ["tIf you want a client to use multiple backup servers this server's identity is:"]),ctx,"h").write(" ").reference(ctx._get(false, ["server_identity"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tFor security reasons check/add following line in the file server_idents.txt on your client:"]),ctx,"h").write("

    ").reference(ctx._get(false, ["server_pubkey"]),ctx,"h",["s"]).write("



    ");}return body_0;})(); -(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tName:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLabel:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDefault value:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tType:"]),ctx,"h").write("
     
    ");}return body_0;})(); -(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write(" > ").reference(ctx._get(false, ["cpath"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_2},null).write("").section(ctx._get(false, ["items"]),ctx,{"block":body_3},null).write("
     ").reference(ctx._get(false, ["tFile"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tCreated"]),ctx,"h").write("").reference(ctx._get(false, ["tLast modified"]),ctx,"h").write("").reference(ctx._get(false, ["tLast accessed"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tVersion"]),ctx,"h").write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["size"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["creat"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["mod"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["access"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["backuptime"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_version"]),ctx,{"block":body_4},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_5},null).write("");}function body_4(chk,ctx){return chk.write("").reference(ctx._get(false, ["version"]),ctx,"h").write("");}function body_5(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").exists(ctx._get(false, ["backups"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["backup_images"]),ctx,{"block":body_11},null).notexists(ctx._get(false, ["backups"]),ctx,{"block":body_20},null).write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tFile backups"]),ctx,"h").write("

    ").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_3},null).write("").section(ctx._get(false, ["backups"]),ctx,{"block":body_4},null).write("
     ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tArchived"]),ctx,"h").write("?
    ");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("");}function body_4(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["incr"]),ctx,"h").write("").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_5},null).write("");}function body_5(chk,ctx){return chk.write("").notexists(ctx._get(false, ["is_archived"]),ctx,{"block":body_6},null).write("");}function body_6(chk,ctx){return chk.notexists(ctx._get(false, ["disable_delete"]),ctx,{"block":body_7},null);}function body_7(chk,ctx){return chk.exists(ctx._get(false, ["can_delete"]),ctx,{"block":body_8},null);}function body_8(chk,ctx){return chk.exists(ctx._get(false, ["delete_pending"]),ctx,{"else":body_9,"block":body_10},null);}function body_9(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["tDelete"]),ctx,"h").write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tBackup is marked for deletion. Do not delete"]),ctx,"h").write(" ").reference(ctx._get(false, ["tDelete now"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tImage backups"]),ctx,"h").write("

    \t\t\t\t").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_12},null).write("").section(ctx._get(false, ["backup_images"]),ctx,{"block":body_13},null).write("
     ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume"]),ctx,"h").write("").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tArchived"]),ctx,"h").write("?
    ");}function body_12(chk,ctx){return chk.write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("");}function body_13(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["letter"]),ctx,"h").write("").reference(ctx._get(false, ["incr"]),ctx,"h").write("").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["has_actions"]),ctx,{"block":body_14},null).write("");}function body_14(chk,ctx){return chk.write("").notexists(ctx._get(false, ["is_archived"]),ctx,{"block":body_15},null).write("");}function body_15(chk,ctx){return chk.notexists(ctx._get(false, ["disable_delete"]),ctx,{"block":body_16},null);}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_delete"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.exists(ctx._get(false, ["delete_pending"]),ctx,{"else":body_18,"block":body_19},null);}function body_18(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["tDelete"]),ctx,"h").write("");}function body_19(chk,ctx){return chk.write("").reference(ctx._get(false, ["tBackup is marked for deletion. Do not delete"]),ctx,"h").write(" ").reference(ctx._get(false, ["tDelete now"]),ctx,"h").write("");}function body_20(chk,ctx){return chk.notexists(ctx._get(false, ["backup_images"]),ctx,{"block":body_21},null);}function body_21(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tNo backups"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tNo backups of this client yet"]),ctx,"h");}return body_0;})(); -(function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tPreparing restore. Please be patient..."]),ctx,"h").write("
     
    ");}return body_0;})(); -(function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h",["s"]).write("");}return body_0;})(); -(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["show_client_breadcrumb"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["clientname"]),ctx,"h").write(" > ").reference(ctx._get(false, ["cpath"]),ctx,"h",["s"]).write("
    ").section(ctx._get(false, ["image_backup_info"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["can_mount"]),ctx,{"else":body_4,"block":body_11},null).exists(ctx._get(false, ["download_zip"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_14},null).write("
    ");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tClients"]),ctx,"h").write(" >");}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tImage backup information"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tId"]),ctx,"h").write(": ").reference(ctx._get(false, ["id"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write(": ").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tIncremental"]),ctx,"h").write(": ").reference(ctx._get(false, ["incr"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSize"]),ctx,"h").write(": ").reference(ctx._get(false, ["size_bytes"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tVolume"]),ctx,"h").write(": ").reference(ctx._get(false, ["letter"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tArchived"]),ctx,"h").write(": ").reference(ctx._get(false, ["archived"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tVolume size"]),ctx,"h").write(": ").reference(ctx._get(false, ["volume_size"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPartition style"]),ctx,"h").write(": ").reference(ctx._get(false, ["part_table"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDisk number"]),ctx,"h").write(": ").reference(ctx._get(false, ["disk_number"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPartition number"]),ctx,"h").write(": ").reference(ctx._get(false, ["partition_number"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tFile system type"]),ctx,"h").write(": ").reference(ctx._get(false, ["fs_type"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tVolume name"]),ctx,"h").write(": ").reference(ctx._get(false, ["volume_name"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSerial number"]),ctx,"h").write(": ").reference(ctx._get(false, ["serial_number"]),ctx,"h").write("
    ").exists(ctx._get(false, ["linux_image_restore"]),ctx,{"block":body_3},null).write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tRestore Linux image"]),ctx,"h").write("");}function body_4(chk,ctx){return chk.notexists(ctx._get(false, ["no_files"]),ctx,{"block":body_5},null);}function body_5(chk,ctx){return chk.exists(ctx._get(false, ["mount_failed"]),ctx,{"else":body_6,"block":body_10},null);}function body_6(chk,ctx){return chk.write("").section(ctx._get(false, ["files"]),ctx,{"block":body_7},null).write("
     ").reference(ctx._get(false, ["tFile"]),ctx,"h").write("").reference(ctx._get(false, ["tSize"]),ctx,"h").write("").reference(ctx._get(false, ["tCreated"]),ctx,"h").write("").reference(ctx._get(false, ["tLast modified"]),ctx,"h").write("").reference(ctx._get(false, ["tLast accessed"]),ctx,"h").write(" 
    ");}function body_7(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["size"]),ctx,"h").write("").reference(ctx._get(false, ["creat"]),ctx,"h").write("").reference(ctx._get(false, ["mod"]),ctx,"h").write("").reference(ctx._get(false, ["access"]),ctx,"h").write("").exists(ctx._get(false, ["list_items"]),ctx,{"block":body_8},null).exists(ctx._get(false, ["can_restore"]),ctx,{"block":body_9},null).write("");}function body_8(chk,ctx){return chk.write("").reference(ctx._get(false, ["tList"]),ctx,"h").write("");}function body_9(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore"]),ctx,"h").write("");}function body_10(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMounting image failed. Please see server log file for details."]),ctx,"h").write("
    ").reference(ctx._get(false, ["mount_errmsg"]),ctx,"h").write("
    ");}function body_11(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tMount image"]),ctx,"h").write("").exists(ctx._get(false, ["os_mount"]),ctx,{"block":body_12},null).write("
    ");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."]),ctx,"h").write("");}function body_13(chk,ctx){return chk.write("").reference(ctx._get(false, ["tDownload folder as ZIP"]),ctx,"h").write("");}function body_14(chk,ctx){return chk.write("").reference(ctx._get(false, ["tRestore folder to client"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange password"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAccess denied"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong or you do not have the required rights to access this file or folder."]),ctx,"h").exists(ctx._get(false, ["errcode"]),ctx,{"block":body_1},null).write("

    ").reference(ctx._get(false, ["tLogin with username and password"]),ctx,"h").write("

    ");}function body_1(chk,ctx){return chk.write("(").reference(ctx._get(false, ["errcode"]),ctx,"h").write(")");}return body_0;})(); -(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.write("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}return body_0;})(); -(function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanging password failed:"]),ctx,"h").write("
    ").reference(ctx._get(false, ["fail_reason"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChanged password successfully"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClients"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["generic_text"]),ctx,{"block":body_1},null).reference(ctx._get(false, ["ext_text"]),ctx,"h",["s"]).exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["dir_error_text"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient added successfully"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tAdded new client with name:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tDefault authentication key:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("

    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Windows"]),ctx,"h").write("
    • ").reference(ctx._get(false, ["tDownload preconfigured client installer for Linux"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tInstall it directly in the terminal via:"]),ctx,"h").write("

      TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

      ").reference(ctx._get(false, ["tWith Docker (web interface accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"").reference(ctx._get(false, ["linux_url"]),ctx,"h").write("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").reference(ctx._get(false, ["tWith Docker (web interface not accessible from client):"]),ctx,"h").write("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").reference(ctx._get(false, ["tAlternatively after you installed the client from:"]),ctx,"h").write(" https://www.urbackup.org/download.html

      • ").reference(ctx._get(false, ["tGo to the settings screen on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tEnable the internet mode on the client"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the internet server port to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the computer name to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tSet the authentication key to:"]),ctx,"h").write(" ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").write("
      • ").reference(ctx._get(false, ["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"]),ctx,"h").write("

      ").reference(ctx._get(false, ["tWith the command line:"]),ctx,"h").write("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings -k internet_mode_enabled -v true -k internet_server -v ").reference(ctx._get(false, ["internet_server"]),ctx,"h").write(" -k internet_server_port -v ").reference(ctx._get(false, ["internet_server_port"]),ctx,"h").write(" -k computername -v \"").reference(ctx._get(false, ["new_clientname"]),ctx,"h").write("\" -k internet_authkey -v ").reference(ctx._get(false, ["new_authkey"]),ctx,"h").reference(ctx._get(false, ["internet_proxy_settings"]),ctx,"h").write("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}return body_0;})(); -(function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tError while accessing backups"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSorry, something went wrong:"]),ctx,"h").write(" ").reference(ctx._get(false, ["err"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["database_error_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["creating_filesindex_text"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tNumber of file entries processed"]),ctx,"h").write(": ").reference(ctx._get(false, ["processed_file_entries"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tPercent finished"]),ctx,"h").write(": ").reference(ctx._get(false, ["percent_finished"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThis server has discovered clients which are currently not configured to use this server."]),ctx,"h").write(" ").reference(ctx._get(false, ["tSee here for details on how this can happen."]),ctx,"h").write("

    ").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null);}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["tOk. Dismiss this hint."]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["id"]),ctx,"h").write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["is_image"]),ctx,{"else":body_1,"block":body_4},null).write("").reference(ctx._get(false, ["backuptime"]),ctx,"h").write("").reference(ctx._get(false, ["duration"]),ctx,"h").write("").reference(ctx._get(false, ["size"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("-");}function body_3(chk,ctx){return chk.write("Path: ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_4(chk,ctx){return chk.write("Volume: ").reference(ctx._get(false, ["details"]),ctx,"h");}return body_0;})(); -(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tUrBackup live log"]),ctx,"h").write(": ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
     
    ");}function body_1(chk,ctx){return chk.write("g.logid=").reference(ctx._get(false, ["logid"]),ctx,"h").write(";");}return body_0;})(); -(function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["level"]),ctx,"h").write("
    ").reference(ctx._get(false, ["time"]),ctx,"h").write("
    ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); -(function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLast activities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tID"]),ctx,"h").write("").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tStarting time"]),ctx,"h").write("").reference(ctx._get(false, ["tRequired time"]),ctx,"h").write("").reference(ctx._get(false, ["tUsed Storage"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["time"]),ctx,"h").write("  ").reference(ctx._get(false, ["loglevel"]),ctx,"h").write("  ").reference(ctx._get(false, ["message"]),ctx,"h",["s"]).write("");}return body_0;})(); -(function(){dust.register("login",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("logs_report_mail",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["report_single_mail"]),ctx,"h").write(" -");}return body_0;})(); -(function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLog"]),ctx,"h").write(": (").reference(ctx._get(false, ["name"]),ctx,"h").write(")
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tLevel"]),ctx,"h").write("").reference(ctx._get(false, ["tTime"]),ctx,"h").write("").reference(ctx._get(false, ["tMessage"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tBack"]),ctx,"h").write("

    ");}return body_0;})(); -(function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo entries for this filter"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); -(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo entries for this filter"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.write(" ").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["time"]),ctx,"h").write("").reference(ctx._get(false, ["errors"]),ctx,"h").write("
    ").reference(ctx._get(false, ["warnings"]),ctx,"h").write("
    ").reference(ctx._get(false, ["action"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.reference(ctx._get(false, ["tThere is a new version of UrBackup server available"]),ctx,"h").write(" (").reference(ctx._get(false, ["new_version_number"]),ctx,"h").write("). Download it here.
    ").reference(ctx._get(false, ["tOk. Stop showing this."]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["name"]),ctx,"h").write("
  • ");}return body_0;})(); -(function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); -(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["nospc_stalled_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ").reference(ctx._get(false, ["tNo activities"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tActivities"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("").reference(ctx._get(false, ["tDetails"]),ctx,"h").write("").reference(ctx._get(false, ["tProgress"]),ctx,"h").write("").reference(ctx._get(false, ["tETA"]),ctx,"h").write("").reference(ctx._get(false, ["tSpeed"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles in queue"]),ctx,"h").write(" 
    ");}return body_0;})(); -(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tRestore Linux image"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tTo restore your Linux disk please enter following in a terminal:"]),ctx,"h").write("

    TF=`mktemp` && wget \"").reference(ctx._get(false, ["linux_restore_url"]),ctx,"h").write("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}return body_0;})(); -(function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["nospc_fatal_text"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tOk. Reset this error"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["action"]),ctx,"h").write("").exists(ctx._get(false, ["image"]),ctx,{"else":body_1,"block":body_6},null).exists(ctx._get(false, ["show_details"]),ctx,{"block":body_7},null).exists(ctx._get(false, ["backups_interrupted"]),ctx,{"block":body_8},null).write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_10},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_11},null).write("
    ").exists(ctx._get(false, ["f_total_bytes"]),ctx,{"block":body_12},null).write("").reference(ctx._get(false, ["eta"]),ctx,"h").write("").exists(ctx._get(false, ["paused"]),ctx,{"else":body_13,"block":body_14},null).write("").reference(ctx._get(false, ["queue"]),ctx,"h").write("").exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["can_stop_backup"]),ctx,{"block":body_16},null).exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_18},null).write("");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["client_update"]),ctx,{"else":body_2,"block":body_5},null);}function body_2(chk,ctx){return chk.exists(ctx._get(false, ["file_restore"]),ctx,{"else":body_3,"block":body_4},null);}function body_3(chk,ctx){return chk.write("-");}function body_4(chk,ctx){return chk.reference(ctx._get(false, ["tPath:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h",["s"]);}function body_5(chk,ctx){return chk.reference(ctx._get(false, ["tTo version:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["tVolume:"]),ctx,"h").write(" ").reference(ctx._get(false, ["details"]),ctx,"h");}function body_7(chk,ctx){return chk.reference(ctx._get(false, ["details"]),ctx,"h");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackups interrupted"]),ctx,"h");}function body_9(chk,ctx){return chk.write("min-width: 2em;");}function body_10(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_11(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}function body_12(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["f_done_bytes"]),ctx,"h").write(" / ").reference(ctx._get(false, ["f_total_bytes"]),ctx,"h").write("
    ");}function body_13(chk,ctx){return chk.reference(ctx._get(false, ["speed"]),ctx,"h");}function body_14(chk,ctx){return chk.reference(ctx._get(false, ["tPaused"]),ctx,"h");}function body_15(chk,ctx){return chk.write("");}function body_16(chk,ctx){return chk.exists(ctx._get(false, ["can_show_backup_log"]),ctx,{"block":body_17},null);}function body_17(chk,ctx){return chk.write(" ");}function body_18(chk,ctx){return chk.write("");}return body_0;})(); -(function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["ONLY_WIN32_BEGIN"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["ONLY_WIN32_END"]),ctx,"h",["s"]).write("
    MBit/s
     
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}return body_0;})(); -(function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tReport script"]),ctx,"h").write("

    \t\t

    ").exists(ctx._get(false, ["saved_ok"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("
    Saved script successfully.
    ");}return body_0;})(); -(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_every"]),ctx,"h").write("").reference(ctx._get(false, ["archive_for"]),ctx,"h").write("").reference(ctx._get(false, ["archive_window"]),ctx,"h").write("").reference(ctx._get(false, ["archive_backup_type_str"]),ctx,"h").write("").reference(ctx._get(false, ["archive_letters_str"]),ctx,"h").write("").exists(ctx._get(false, ["show_archive_timeleft"]),ctx,{"block":body_1},null).write("").exists(ctx._get(false, ["source_group"]),ctx,{"block":body_2},null).exists(ctx._get(false, ["source_here"]),ctx,{"block":body_3},null).write("");}function body_1(chk,ctx){return chk.write("").reference(ctx._get(false, ["archive_timeleft"]),ctx,"h").write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("disabled");}return body_0;})(); -(function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest Mail sent successfully"]),ctx,"h").write(".
    ");}return body_0;})(); -(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tClient"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("

    ").exists(ctx._get(false, ["groupmod"]),ctx,{"block":body_1},null).write("
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("

     
    ");}function body_1(chk,ctx){return chk.write("
    Member of group
    ");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("
  • ").reference(ctx._get(false, ["tPermissions"]),ctx,"h").write("
  • ");}return body_0;})(); -(function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["msg"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSending test mail failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["mail_err"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.write("");}return body_0;})(); -(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSaved settings successfully"]),ctx,"h").write(".
    ");}return body_0;})(); -(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange password for user"]),ctx,"h").write(": ").reference(ctx._get(false, ["username"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tChange rights for user"]),ctx,"h").write(": ").reference(ctx._get(false, ["username"]),ctx,"h").write("
    ").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tDomain"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tTranslation"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("

    ").reference(ctx._get(false, ["tNew domain"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_1},null).write("
    ").reference(ctx._get(false, ["tBackup Statistics"]),ctx,"h").write("
    ").notexists(ctx._get(false, ["maximized"]),ctx,{"block":body_2},null).write("\t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tImages"]),ctx,"h").write("").reference(ctx._get(false, ["tFiles"]),ctx,"h").write("").reference(ctx._get(false, ["tAll"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tSum"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tImages"]),ctx,"h").write("").reference(ctx._get(false, ["images_total"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tFiles"]),ctx,"h").write("").reference(ctx._get(false, ["files_total"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tAll"]),ctx,"h").write("").reference(ctx._get(false, ["used_total"]),ctx,"h").write("
    ").notexists(ctx._get(false, ["maximized"]),ctx,{"block":body_3},null).write("
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_4},null).write("
    ").reference(ctx._get(false, ["tStorage allocation"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...
    ").exists(ctx._get(false, ["maximized"]),ctx,{"block":body_5},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("
    ");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ");}return body_0;})(); -(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["rights"]),ctx,"h").write("").exists(ctx._get(false, ["can_change"]),ctx,{"block":body_1},null).write("");}function body_1(chk,ctx){return chk.write(" ");}return body_0;})(); -(function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["tNo Users"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.write("
    \t\t\t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tUsername"]),ctx,"h").write("").reference(ctx._get(false, ["tRights"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); -(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["rights"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); -(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tBackup status"]),ctx,"h").write("
    ").reference(ctx._get(false, ["nospc_fatal"]),ctx,"h",["s"]).reference(ctx._get(false, ["nospc_stalled"]),ctx,"h",["s"]).reference(ctx._get(false, ["database_error"]),ctx,"h",["s"]).reference(ctx._get(false, ["endian_info"]),ctx,"h",["s"]).write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tGroup name"]),ctx,"h").write("").reference(ctx._get(false, ["tOnline"]),ctx,"h").write("").reference(ctx._get(false, ["tStatus"]),ctx,"h").write("").reference(ctx._get(false, ["tLast seen"]),ctx,"h").write("").reference(ctx._get(false, ["tLast file backup"]),ctx,"h").write("").reference(ctx._get(false, ["tLast image backup"]),ctx,"h").write("").reference(ctx._get(false, ["tFile backup status"]),ctx,"h").write("").reference(ctx._get(false, ["tImage backup status"]),ctx,"h").write("").reference(ctx._get(false, ["tIP"]),ctx,"h").write("").reference(ctx._get(false, ["tClient version"]),ctx,"h").write("").reference(ctx._get(false, ["tOperating System"]),ctx,"h").write("
    ").exists(ctx._get(false, ["status_can_show_all"]),ctx,{"block":body_2},null).reference(ctx._get(false, ["modify_clients"]),ctx,"h",["s"]).exists(ctx._get(false, ["has_client_download"]),ctx,{"block":body_3},null).exists(ctx._get(false, ["allow_add_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["removed_clients_table"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["status_extra_clients"]),ctx,{"block":body_8},null).write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["status_client_download_windows"]),ctx,"h",["s"]).reference(ctx._get(false, ["status_client_download_linux"]),ctx,"h",["s"]).write("
    ");}function body_4(chk,ctx){return chk.write("");}function body_5(chk,ctx){return chk.write("
    ").section(ctx._get(false, ["removed_clients"]),ctx,{"block":body_6},null).write("
    ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write(" 
    ");}function body_6(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["tThis client is going to be removed. "]),ctx,"h").write(" ").exists(ctx._get(false, ["remove_client"]),ctx,{"block":body_7},null).reference(ctx._get(false, ["tClients are removed during the cleanup in the cleanup time window. "]),ctx,"h").write("");}function body_7(chk,ctx){return chk.write("").reference(ctx._get(false, ["tStop removing client"]),ctx,"h").write(". ");}function body_8(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tClient discovery hints"]),ctx,"h").write("
    \t\t\t").reference(ctx._get(false, ["extra_clients_rows"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tHostname/IP"]),ctx,"h").write("").reference(ctx._get(false, ["tOnline"]),ctx,"h").write("").reference(ctx._get(false, ["tActions"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tGroup"]),ctx,"h").write(" ").reference(ctx._get(false, ["groupname"]),ctx,"h").write("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").reference(ctx._get(false, ["settings_inv"]),ctx,"h",["s"]).write("
    ");}function body_1(chk,ctx){return chk.write("");}return body_0;})(); -(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tStorage usage of"]),ctx,"h").write(" ").reference(ctx._get(false, ["clientname"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLoading"]),ctx,"h").write("...

     
    ");}return body_0;})(); -(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["images"]),ctx,"h").write("").reference(ctx._get(false, ["files"]),ctx,"h").write("").reference(ctx._get(false, ["used"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.write("").reference(ctx._get(false, ["hostname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.write("");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Windows"]),ctx,"h");}function body_2(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Mac OS X"]),ctx,"h");}function body_3(chk,ctx){return chk.reference(ctx._get(false, ["tDownload client for Linux"]),ctx,"h");}return body_0;})(); -(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tSelect all"]),ctx,"h").write("").reference(ctx._get(false, ["tSelect none"]),ctx,"h").write("").reference(ctx._get(false, ["rem_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["tRemove selected"]),ctx,"h").write("").reference(ctx._get(false, ["rem_stop"]),ctx,"h",["s"]).write("
    ");}return body_0;})(); -(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.write("").exists(ctx._get(false, ["show_select_box"]),ctx,{"block":body_1},null).write("").reference(ctx._get(false, ["name"]),ctx,"h").write("").reference(ctx._get(false, ["groupname"]),ctx,"h").write("").reference(ctx._get(false, ["online"]),ctx,"h").write(" ").exists(ctx._get(false, ["online_add_status"]),ctx,{"block":body_2},null).write(" ").exists(ctx._get(false, ["reset_client_uid"]),ctx,{"block":body_3},null).write("").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastseen"]),ctx,"h").write("").reference(ctx._get(false, ["lastbackup"]),ctx,"h").reference(ctx._get(false, ["start_file_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["lastbackup_image"]),ctx,"h").reference(ctx._get(false, ["start_image_backup"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["file_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["image_ok_t"]),ctx,"h").write("").reference(ctx._get(false, ["ip"]),ctx,"h").write("").reference(ctx._get(false, ["client_version_string"]),ctx,"h").write("").reference(ctx._get(false, ["os_version_string"]),ctx,"h").write("");}function body_1(chk,ctx){return chk.write("");}function body_2(chk,ctx){return chk.write("(").reference(ctx._get(false, ["status"]),ctx,"h",["s"]).write(")");}function body_3(chk,ctx){return chk.write("").reference(ctx._get(false, ["tAllow new client"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.write("
    ").exists(ctx._get(false, ["percent"]),ctx,{"block":body_2},null).write("
    ").exists(ctx._get(false, ["indexing"]),ctx,{"block":body_3},null).write("
    ");}function body_1(chk,ctx){return chk.write("min-width: 2em;");}function body_2(chk,ctx){return chk.reference(ctx._get(false, ["pcdone"]),ctx,"h").write("%");}function body_3(chk,ctx){return chk.reference(ctx._get(false, ["tIndexing..."]),ctx,"h");}return body_0;})(); -(function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tmpdir_error_text"]),ctx,"h").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.write("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").reference(ctx._get(false, ["virus_error_path"]),ctx,"h").write(" ).").exists(ctx._get(false, ["stop_show_key"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tOk. Stop showing this error"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tLogs"]),ctx,"h").write("
    \t").reference(ctx._get(false, ["rows"]),ctx,"h",["s"]).write("
     ").reference(ctx._get(false, ["tComputer name"]),ctx,"h").write("").reference(ctx._get(false, ["tBackup time"]),ctx,"h").write("").reference(ctx._get(false, ["tErrors"]),ctx,"h").write("").reference(ctx._get(false, ["tWarnings"]),ctx,"h").write("").reference(ctx._get(false, ["tAction"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tLive Log"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tReports"]),ctx,"h").write("
    ").exists(ctx._get(false, ["has_user"]),ctx,{"else":body_1,"block":body_2},null).write("
    ");}function body_1(chk,ctx){return chk.reference(ctx._get(false, ["tYou need to create a user to be able to send reports"]),ctx,"h");}function body_2(chk,ctx){return chk.write("

     
    +
    ").exists(ctx._get(false, ["can_report_script_edit"]),ctx,{"block":body_3},null).write("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}function body_3(chk,ctx){return chk.write("

    ").reference(ctx._get(false, ["tEdit report script"]),ctx,"h").write("");}return body_0;})(); -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tAbout UrBackup"]),ctx,"h").write("
    UrBackup Server ").reference(ctx._get(false, ["version"]),ctx,"h").write("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}return body_0;})(); -(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.write("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").exists(ctx._get(false, ["test_login"]),ctx,{"block":body_1},null).write("
    ");}function body_1(chk,ctx){return chk.exists(ctx._get(false, ["test_login_ok"]),ctx,{"else":body_2,"block":body_3},null);}function body_2(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login failed. Error:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_err"]),ctx,"h").write("
    ");}function body_3(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tTest login succeeded. Rights of user:"]),ctx,"h").write(" ").reference(ctx._get(false, ["ldap_rights"]),ctx,"h").write("
    ");}return body_0;})(); -(function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.write("\t\t\t
    ").reference(ctx._get(false, ["upgrade_error_text"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tCurrent version"]),ctx,"h").write(": ").reference(ctx._get(false, ["curr_db_version"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tTarget version"]),ctx,"h").write(": ").reference(ctx._get(false, ["target_db_version"]),ctx,"h").write("


    ");}return body_0;})(); -(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.exists(ctx._get(false, ["client_settings"]),ctx,{"else":body_1,"block":body_2},null).write("
    ").reference(ctx._get(false, ["thours"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    \t\t\t\t
    ").reference(ctx._get(false, ["tdays"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tDays"]),ctx,"h").write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_4},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_5},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_6},null).write("\t\t\t").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_7},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_8},null).write("
    ").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tArchive every"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive for"]),ctx,"h").write("").reference(ctx._get(false, ["tArchive window"]),ctx,"h").write(" ?").reference(ctx._get(false, ["tBackup type"]),ctx,"h").write("").reference(ctx._get(false, ["tVolume letters"]),ctx,"h").write("").reference(ctx._get(false, ["tNext archival"]),ctx,"h").write("  
     ").exists(ctx._get(false, ["archive_global"]),ctx,{"block":body_9},null).reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]).write("\t\t
    ").exists(ctx._get(false, ["can_edit_scripts"]),ctx,{"block":body_10},null).write("
    \t\t\t
    ").reference(ctx._get(false, ["mod_alert_params"]),ctx,"h",["s"]).write("
    MBit/s
    ").reference(ctx._get(false, ["internet_settings_start"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_11},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_12},null).write("
    KBit/s
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_15},null).exists(ctx._get(false, ["main_client"]),ctx,{"block":body_16},null).write("
    ").exists(ctx._get(false, ["main_client"]),ctx,{"block":body_17},null).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").exists(ctx._get(false, ["global_settings"]),ctx,{"block":body_18},null).write("
    ").reference(ctx._get(false, ["internet_settings_end"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    \t\t\t
    ").reference(ctx._get(false, ["global_settings_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["tMB"]),ctx,"h").write("
    ").reference(ctx._get(false, ["global_settings_end"]),ctx,"h",["s"]).write("
    ").exists(ctx._get(false, ["client_settings"]),ctx,{"block":body_19},null);}function body_1(chk,ctx){return chk.write("
    ");}function body_2(chk,ctx){return chk.write("
    ");}function body_3(chk,ctx){return chk.write("");}function body_4(chk,ctx){return chk.write("
    ");}function body_5(chk,ctx){return chk.write("
    ").reference(ctx._get(false, ["tMin"]),ctx,"h").write("
    ");}function body_6(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_7(chk,ctx){return chk.write("
    ");}function body_8(chk,ctx){return chk.reference(ctx._get(false, ["no_compname_start"]),ctx,"h",["s"]).write("
    ").reference(ctx._get(false, ["no_compname_end"]),ctx,"h",["s"]);}function body_9(chk,ctx){return chk.write("");}function body_10(chk,ctx){return chk.write("").reference(ctx._get(false, ["tEdit scripts"]),ctx,"h").write("");}function body_11(chk,ctx){return chk.write("
    ");}function body_12(chk,ctx){return chk.notexists(ctx._get(false, ["global_settings"]),ctx,{"block":body_13},null).exists(ctx._get(false, ["with_authkey"]),ctx,{"block":body_14},null);}function body_13(chk,ctx){return chk.write("
    ");}function body_14(chk,ctx){return chk.write("
    ");}function body_15(chk,ctx){return chk.write("
    KBit/s
    ");}function body_16(chk,ctx){return chk.write("
    ");}function body_17(chk,ctx){return chk.write("
    ");}function body_18(chk,ctx){return chk.write("
    ");}function body_19(chk,ctx){return chk.write("
    ");}return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
     

    ").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



    ").f(ctx.get(["tAlert script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); +(function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tPreparing restore. Please be patient..."], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAccess denied"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong or you do not have the required rights to access this file or folder."], false),ctx,"h").x(ctx.get(["errcode"], false),ctx,{"block":body_1},{}).w("

    ").f(ctx.get(["tLogin with username and password"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("(").f(ctx.get(["errcode"], false),ctx,"h").w(")");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClients"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tError while accessing backups"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong:"], false),ctx,"h").w(" ").f(ctx.get(["err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); +(function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.w("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanging password failed:"], false),ctx,"h").w("
    ").f(ctx.get(["fail_reason"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h").w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h").w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the URL to connect to:"], false),ctx,"h").w(" ").f(ctx.get(["server_url"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server-url"], false),ctx,"h").w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h").w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["database_error_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["generic_text"], false),ctx,{"block":body_1},{}).f(ctx.get(["ext_text"], false),ctx,"h",["s"]).x(ctx.get(["stop_show_key"], false),ctx,{"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["dir_error_text"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_2.__dustBody=!0;return body_0;})(); +(function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["creating_filesindex_text"], false),ctx,"h").w("
    ").f(ctx.get(["tNumber of file entries processed"], false),ctx,"h").w(": ").f(ctx.get(["processed_file_entries"], false),ctx,"h").w("
    ").f(ctx.get(["tPercent finished"], false),ctx,"h").w(": ").f(ctx.get(["percent_finished"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

    ").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["id"], false),ctx,"h").w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["is_image"], false),ctx,{"else":body_1,"block":body_4},{}).w("").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["duration"], false),ctx,"h").w("").f(ctx.get(["size"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("-");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("Path: ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("Volume: ").f(ctx.get(["details"], false),ctx,"h");}body_4.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLast activities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tID"], false),ctx,"h").w("").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tStarting time"], false),ctx,"h").w("").f(ctx.get(["tRequired time"], false),ctx,"h").w("").f(ctx.get(["tUsed Storage"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLog"], false),ctx,"h").w(": (").f(ctx.get(["name"], false),ctx,"h").w(")
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tLevel"], false),ctx,"h").w("").f(ctx.get(["tTime"], false),ctx,"h").w("").f(ctx.get(["tMessage"], false),ctx,"h").w("

    ").f(ctx.get(["tBack"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["level"], false),ctx,"h").w("
    ").f(ctx.get(["time"], false),ctx,"h").w("
    ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("login",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("logs_report_mail",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["report_single_mail"], false),ctx,"h").w(" -");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("logs_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["time"], false),ctx,"h").w("").f(ctx.get(["errors"], false),ctx,"h").w("
    ").f(ctx.get(["warnings"], false),ctx,"h").w("
    ").f(ctx.get(["action"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("logs_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLogs"], false),ctx,"h").w("
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tErrors"], false),ctx,"h").w("").f(ctx.get(["tWarnings"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("
    ").f(ctx.get(["tLive Log"], false),ctx,"h").w("
    ").f(ctx.get(["tReports"], false),ctx,"h").w("
    ").x(ctx.get(["has_user"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tYou need to create a user to be able to send reports"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

     
    +
    ").x(ctx.get(["can_report_script_edit"], false),ctx,{"block":body_3},{}).w("\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("

    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThere is a new version of UrBackup server available"], false),ctx,"h").w(" (").f(ctx.get(["new_version_number"], false),ctx,"h").w("). Download it here.
    ").f(ctx.get(["tOk. Stop showing this."], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["nospc_fatal_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tETA"], false),ctx,"h").w("").f(ctx.get(["tSpeed"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("

    ").f(ctx.get(["tReport script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["ONLY_WIN32_BEGIN"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["ONLY_WIN32_END"], false),ctx,"h",["s"]).w("
    MBit/s
     
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest Mail sent successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSaved settings successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["msg"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["rights"], false),ctx,"h").w("").x(ctx.get(["can_change"], false),ctx,{"block":body_1},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w(" ");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo Users"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["tBackup Statistics"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_2},{}).w("\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["tAll"], false),ctx,"h").w("
    ").f(ctx.get(["tSum"], false),ctx,"h").w("
    ").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["images_total"], false),ctx,"h").w("
    ").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["files_total"], false),ctx,"h").w("
    ").f(ctx.get(["tAll"], false),ctx,"h").w("").f(ctx.get(["used_total"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_3},{}).w("
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_4},{}).w("
    ").f(ctx.get(["tStorage allocation"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_5},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ");}body_5.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tDownload client for Windows"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["tDownload client for Mac OS X"], false),ctx,"h");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tDownload client for Linux"], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["hostname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["groupname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w(" ").x(ctx.get(["online_add_status"], false),ctx,{"block":body_2},{}).w(" ").x(ctx.get(["reset_client_uid"], false),ctx,{"block":body_3},{}).w("").f(ctx.get(["status"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastseen"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h").f(ctx.get(["start_file_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastbackup_image"], false),ctx,"h").f(ctx.get(["start_image_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["file_ok_t"], false),ctx,"h").w("").f(ctx.get(["image_ok_t"], false),ctx,"h").w("").f(ctx.get(["ip"], false),ctx,"h").w("").f(ctx.get(["client_version_string"], false),ctx,"h").w("").f(ctx.get(["os_version_string"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("(").f(ctx.get(["status"], false),ctx,"h",["s"]).w(")");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tAllow new client"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSelect all"], false),ctx,"h").w("").f(ctx.get(["tSelect none"], false),ctx,"h").w("").f(ctx.get(["rem_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["tRemove selected"], false),ctx,"h").w("").f(ctx.get(["rem_stop"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_2},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_3},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("min-width: 2em;");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tmpdir_error_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.w("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").f(ctx.get(["virus_error_path"], false),ctx,"h").w(" ).").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["upgrade_error_text"], false),ctx,"h").w("
    ").f(ctx.get(["tCurrent version"], false),ctx,"h").w(": ").f(ctx.get(["curr_db_version"], false),ctx,"h").w("
    ").f(ctx.get(["tTarget version"], false),ctx,"h").w(": ").f(ctx.get(["target_db_version"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); From 9f74013207eb1d3a56fe7673000ab7e0f0561b17 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 16:14:50 +0100 Subject: [PATCH 115/469] Fix encoding issues --- urbackupserver/www/js/templates.js | 42 +++++++++---------- urbackupserver/www/js/urbackup.js | 2 +- urbackupserver/www/templates/client_added.htm | 22 +++++----- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 1cd4b749c..a5a8400c9 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,32 +1,32 @@ -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
     

    ").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



    ").f(ctx.get(["tAlert script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); (function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tPreparing restore. Please be patient..."], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAccess denied"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong or you do not have the required rights to access this file or folder."], false),ctx,"h").x(ctx.get(["errcode"], false),ctx,{"block":body_1},{}).w("

    ").f(ctx.get(["tLogin with username and password"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("(").f(ctx.get(["errcode"], false),ctx,"h").w(")");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClients"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tError while accessing backups"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong:"], false),ctx,"h").w(" ").f(ctx.get(["err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_files",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").s(ctx.get(["image_backup_info"], false),ctx,{"block":body_2},{}).x(ctx.get(["can_mount"], false),ctx,{"else":body_4,"block":body_11},{}).x(ctx.get(["download_zip"], false),ctx,{"block":body_13},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_14},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tImage backup information"], false),ctx,"h").w("
    ").f(ctx.get(["tId"], false),ctx,"h").w(": ").f(ctx.get(["id"], false),ctx,"h").w("
    ").f(ctx.get(["tBackup time"], false),ctx,"h").w(": ").f(ctx.get(["backuptime"], false),ctx,"h").w("
    ").f(ctx.get(["tIncremental"], false),ctx,"h").w(": ").f(ctx.get(["incr"], false),ctx,"h").w("
    ").f(ctx.get(["tSize"], false),ctx,"h").w(": ").f(ctx.get(["size_bytes"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume"], false),ctx,"h").w(": ").f(ctx.get(["letter"], false),ctx,"h").w("
    ").f(ctx.get(["tArchived"], false),ctx,"h").w(": ").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tVolume size"], false),ctx,"h").w(": ").f(ctx.get(["volume_size"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition style"], false),ctx,"h").w(": ").f(ctx.get(["part_table"], false),ctx,"h").w("
    ").f(ctx.get(["tDisk number"], false),ctx,"h").w(": ").f(ctx.get(["disk_number"], false),ctx,"h").w("
    ").f(ctx.get(["tPartition number"], false),ctx,"h").w(": ").f(ctx.get(["partition_number"], false),ctx,"h").w("
    ").f(ctx.get(["tFile system type"], false),ctx,"h").w(": ").f(ctx.get(["fs_type"], false),ctx,"h").w("
    ").f(ctx.get(["tVolume name"], false),ctx,"h").w(": ").f(ctx.get(["volume_name"], false),ctx,"h").w("
    ").f(ctx.get(["tSerial number"], false),ctx,"h").w(": ").f(ctx.get(["serial_number"], false),ctx,"h").w("
    ").x(ctx.get(["linux_image_restore"], false),ctx,{"block":body_3},{}).w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.nx(ctx.get(["no_files"], false),ctx,{"block":body_5},{});}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.x(ctx.get(["mount_failed"], false),ctx,{"else":body_6,"block":body_10},{});}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").s(ctx.get(["files"], false),ctx,{"block":body_7},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w(" 
    ");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h").w("").f(ctx.get(["creat"], false),ctx,"h").w("").f(ctx.get(["mod"], false),ctx,"h").w("").f(ctx.get(["access"], false),ctx,"h").w("").x(ctx.get(["list_items"], false),ctx,{"block":body_8},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_9},{}).w("");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("").f(ctx.get(["tList"], false),ctx,"h").w("");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("
    ").f(ctx.get(["tMounting image failed. Please see server log file for details."], false),ctx,"h").w("
    ").f(ctx.get(["mount_errmsg"], false),ctx,"h").w("
    ");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tMount image"], false),ctx,"h").w("").x(ctx.get(["os_mount"], false),ctx,{"block":body_12},{}).w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["tUrBackup will use non-sandboxed server operating system functionality to mount the image. Only mount the image if you trust its source."], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("").f(ctx.get(["tDownload folder as ZIP"], false),ctx,"h").w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").f(ctx.get(["tRestore folder to client"], false),ctx,"h").w("");}body_14.__dustBody=!0;return body_0;})(); (function(){dust.register("big_endian_info",body_0);function body_0(chk,ctx){return chk.w("
    UrBackup is currently only partially tested on big endian systems. In particular image backups and restores have not been tested.
    UrBackup has been put into testing mode. This means UrBackup will always log debug messages.
    If you want to help make UrBackup available on big endian systems please report all problems to the forums or to our issue tracker. Thank you for your help!
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanging password failed:"], false),ctx,"h").w("
    ").f(ctx.get(["fail_reason"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h").w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h").w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h").w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the URL to connect to:"], false),ctx,"h").w(" ").f(ctx.get(["server_url"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h").w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server-url"], false),ctx,"h").w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h").w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h").w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h",["s"]).w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h",["s"]).w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the URL to connect to:"], false),ctx,"h").w(" ").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tSet the name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["database_error_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

    ").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["generic_text"], false),ctx,{"block":body_1},{}).f(ctx.get(["ext_text"], false),ctx,"h",["s"]).x(ctx.get(["stop_show_key"], false),ctx,{"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["dir_error_text"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_2.__dustBody=!0;return body_0;})(); (function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["creating_filesindex_text"], false),ctx,"h").w("
    ").f(ctx.get(["tNumber of file entries processed"], false),ctx,"h").w(": ").f(ctx.get(["processed_file_entries"], false),ctx,"h").w("
    ").f(ctx.get(["tPercent finished"], false),ctx,"h").w(": ").f(ctx.get(["percent_finished"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("has_ident_error_clients",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThis server has discovered clients which are currently not configured to use this server."], false),ctx,"h").w(" ").f(ctx.get(["tSee here for details on how this can happen."], false),ctx,"h").w("

    ").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tOk. Dismiss this hint."], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("lastacts_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["id"], false),ctx,"h").w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["is_image"], false),ctx,{"else":body_1,"block":body_4},{}).w("").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["duration"], false),ctx,"h").w("").f(ctx.get(["size"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("-");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("Path: ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("Volume: ").f(ctx.get(["details"], false),ctx,"h");}body_4.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("lastacts_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLast activities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tID"], false),ctx,"h").w("").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tStarting time"], false),ctx,"h").w("").f(ctx.get(["tRequired time"], false),ctx,"h").w("").f(ctx.get(["tUsed Storage"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tLog"], false),ctx,"h").w(": (").f(ctx.get(["name"], false),ctx,"h").w(")
    \t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tLevel"], false),ctx,"h").w("").f(ctx.get(["tTime"], false),ctx,"h").w("").f(ctx.get(["tMessage"], false),ctx,"h").w("

    ").f(ctx.get(["tBack"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["time"], false),ctx,"h").w("  ").f(ctx.get(["loglevel"], false),ctx,"h").w("  ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("live_log",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tUrBackup live log"], false),ctx,"h").w(": ").f(ctx.get(["clientname"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("g.logid=").f(ctx.get(["logid"], false),ctx,"h").w(";");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("log_single_row",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["level"], false),ctx,"h").w("
    ").f(ctx.get(["time"], false),ctx,"h").w("
    ").f(ctx.get(["message"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("log_single_filter",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("login",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("logs_filter",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("logs_none",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo entries for this filter"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); @@ -37,40 +37,40 @@ (function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThere is a new version of UrBackup server available"], false),ctx,"h").w(" (").f(ctx.get(["new_version_number"], false),ctx,"h").w("). Download it here.
    ").f(ctx.get(["tOk. Stop showing this."], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["nospc_fatal_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); -(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("progress_table",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tETA"], false),ctx,"h").w("").f(ctx.get(["tSpeed"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("progress_table_none",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tActivities"], false),ctx,"h").w("
    \t\t\t
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tAction"], false),ctx,"h").w("").f(ctx.get(["tDetails"], false),ctx,"h").w("").f(ctx.get(["tProgress"], false),ctx,"h").w("").f(ctx.get(["tFiles in queue"], false),ctx,"h").w(" 
    ").f(ctx.get(["tNo activities"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("report_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit report script"], false),ctx,"h").w("

    ").f(ctx.get(["tReport script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["ONLY_WIN32_BEGIN"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["ONLY_WIN32_END"], false),ctx,"h",["s"]).w("
    MBit/s
     
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("restore_linux_img",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tRestore Linux image"], false),ctx,"h").w("

    ").f(ctx.get(["tTo restore your Linux disk please enter following in a terminal:"], false),ctx,"h").w("

    TF=`mktemp` && wget \"").f(ctx.get(["linux_restore_url"], false),ctx,"h").w("\" -O $TF && sudo sh $TF; rm -f $TF

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail_test_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest Mail sent successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_save_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSaved settings successfully"], false),ctx,"h").w(".
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tClient"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("

    ").x(ctx.get(["groupmod"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Member of group
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
  • ").f(ctx.get(["tPermissions"], false),ctx,"h").w("
  • ");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_add_done",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["msg"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_user_create",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_create_admin",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["rights"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_pw_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_rights_change",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange rights for user"], false),ctx,"h").w(": ").f(ctx.get(["username"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tDomain"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tTranslation"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("

    ").f(ctx.get(["tNew domain"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_user_rights_change_row",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["rights"], false),ctx,"h").w("").x(ctx.get(["can_change"], false),ctx,{"block":body_1},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w(" ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_users_start",body_0);function body_0(chk,ctx){return chk.w("
    \t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tUsername"], false),ctx,"h").w("").f(ctx.get(["tRights"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_users_start_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["rights"], false),ctx,"h").w("").x(ctx.get(["can_change"], false),ctx,{"block":body_1},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w(" ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_users_start_row_empty",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["tNo Users"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("stat_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_1},{}).w("
    ").f(ctx.get(["tBackup Statistics"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_2},{}).w("\t\t\t").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["tAll"], false),ctx,"h").w("
    ").f(ctx.get(["tSum"], false),ctx,"h").w("
    ").f(ctx.get(["tImages"], false),ctx,"h").w("").f(ctx.get(["images_total"], false),ctx,"h").w("
    ").f(ctx.get(["tFiles"], false),ctx,"h").w("").f(ctx.get(["files_total"], false),ctx,"h").w("
    ").f(ctx.get(["tAll"], false),ctx,"h").w("").f(ctx.get(["used_total"], false),ctx,"h").w("
    ").nx(ctx.get(["maximized"], false),ctx,{"block":body_3},{}).w("
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_4},{}).w("
    ").f(ctx.get(["tStorage allocation"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...
    ").x(ctx.get(["maximized"], false),ctx,{"block":body_5},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ");}body_5.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_client_download",body_0);function body_0(chk,ctx){return chk.w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["tDownload client for Windows"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["tDownload client for Mac OS X"], false),ctx,"h");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tDownload client for Linux"], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["hostname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_user",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tStorage usage of"], false),ctx,"h").w(" ").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").f(ctx.get(["tLoading"], false),ctx,"h").w("...

     
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("stat_general_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["images"], false),ctx,"h").w("").f(ctx.get(["files"], false),ctx,"h").w("").f(ctx.get(["used"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_detail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackup status"], false),ctx,"h").w("
    ").f(ctx.get(["nospc_fatal"], false),ctx,"h",["s"]).f(ctx.get(["nospc_stalled"], false),ctx,"h",["s"]).f(ctx.get(["database_error"], false),ctx,"h",["s"]).f(ctx.get(["endian_info"], false),ctx,"h",["s"]).w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tGroup name"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tStatus"], false),ctx,"h").w("").f(ctx.get(["tLast seen"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("").f(ctx.get(["tLast image backup"], false),ctx,"h").w("").f(ctx.get(["tFile backup status"], false),ctx,"h").w("").f(ctx.get(["tImage backup status"], false),ctx,"h").w("").f(ctx.get(["tIP"], false),ctx,"h").w("").f(ctx.get(["tClient version"], false),ctx,"h").w("").f(ctx.get(["tOperating System"], false),ctx,"h").w("
    ").x(ctx.get(["status_can_show_all"], false),ctx,{"block":body_2},{}).f(ctx.get(["modify_clients"], false),ctx,"h",["s"]).x(ctx.get(["has_client_download"], false),ctx,{"block":body_3},{}).x(ctx.get(["allow_add_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["removed_clients_table"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["status_extra_clients"], false),ctx,{"block":body_8},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["status_client_download_windows"], false),ctx,"h",["s"]).f(ctx.get(["status_client_download_linux"], false),ctx,"h",["s"]).w("
    ");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").s(ctx.get(["removed_clients"], false),ctx,{"block":body_6},{}).w("
    ").f(ctx.get(["tComputer name"], false),ctx,"h").w(" 
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["tThis client is going to be removed. "], false),ctx,"h").w(" ").x(ctx.get(["remove_client"], false),ctx,{"block":body_7},{}).f(ctx.get(["tClients are removed during the cleanup in the cleanup time window. "], false),ctx,"h").w("");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("").f(ctx.get(["tStop removing client"], false),ctx,"h").w(". ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient discovery hints"], false),ctx,"h").w("
    \t\t\t").f(ctx.get(["extra_clients_rows"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tHostname/IP"], false),ctx,"h").w("").f(ctx.get(["tOnline"], false),ctx,"h").w("").f(ctx.get(["tActions"], false),ctx,"h").w("
    ");}body_8.__dustBody=!0;return body_0;})(); +(function(){dust.register("status_detail_extra_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["hostname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_detail_row",body_0);function body_0(chk,ctx){return chk.w("").x(ctx.get(["show_select_box"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["groupname"], false),ctx,"h").w("").f(ctx.get(["online"], false),ctx,"h").w(" ").x(ctx.get(["online_add_status"], false),ctx,{"block":body_2},{}).w(" ").x(ctx.get(["reset_client_uid"], false),ctx,{"block":body_3},{}).w("").f(ctx.get(["status"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastseen"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h").f(ctx.get(["start_file_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["lastbackup_image"], false),ctx,"h").f(ctx.get(["start_image_backup"], false),ctx,"h",["s"]).w("").f(ctx.get(["file_ok_t"], false),ctx,"h").w("").f(ctx.get(["image_ok_t"], false),ctx,"h").w("").f(ctx.get(["ip"], false),ctx,"h").w("").f(ctx.get(["client_version_string"], false),ctx,"h").w("").f(ctx.get(["os_version_string"], false),ctx,"h").w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("(").f(ctx.get(["status"], false),ctx,"h",["s"]).w(")");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tAllow new client"], false),ctx,"h").w("");}body_3.__dustBody=!0;return body_0;})(); +(function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tmpdir_error_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("status_modify_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSelect all"], false),ctx,"h").w("").f(ctx.get(["tSelect none"], false),ctx,"h").w("").f(ctx.get(["rem_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["tRemove selected"], false),ctx,"h").w("").f(ctx.get(["rem_stop"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("status_percent_done",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_2},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_3},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("min-width: 2em;");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_3.__dustBody=!0;return body_0;})(); -(function(){dust.register("tmpdir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tmpdir_error_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("virus_error",body_0);function body_0(chk,ctx){return chk.w("
    On-access virus scanner active in temporary file path. This will cause backups to fail once your virus scanner detects a backed up file as a virus. Your virus scanner will also probably scan each backed up file multiple times causing performance problems. You should consider completely disabling the on-access virus scanner on the server or at the very least exclude UrBackup server's temporary path ( ").f(ctx.get(["virus_error_path"], false),ctx,"h").w(" ).").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("upgrade_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["upgrade_error_text"], false),ctx,"h").w("
    ").f(ctx.get(["tCurrent version"], false),ctx,"h").w(": ").f(ctx.get(["curr_db_version"], false),ctx,"h").w("
    ").f(ctx.get(["tTarget version"], false),ctx,"h").w(": ").f(ctx.get(["target_db_version"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 042f26f3f..ed9a86b1a 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5996,7 +5996,7 @@ function addNewClient3(data) data.mac_url = downloadClientURL(data.new_clientid, data.new_authkey, "mac"); if(data.internet_server_proxy) { - data.internet_proxy_settings = " --proxy \""+data.internet_server_proxy+"\""; + data.internet_proxy_settings = " --proxy \""+unescapeHTML(data.internet_server_proxy)+"\""; } var ndata=dustRender("client_added", data); diff --git a/urbackupserver/www/templates/client_added.htm b/urbackupserver/www/templates/client_added.htm index a306ab1aa..0e3cf51b2 100644 --- a/urbackupserver/www/templates/client_added.htm +++ b/urbackupserver/www/templates/client_added.htm @@ -2,22 +2,22 @@
    {tClient added successfully}

    - {tAdded new client with name:} {new_clientname} + {tAdded new client with name:} {new_clientname|s}

    - {tDefault authentication key:} {new_authkey} + {tDefault authentication key:} {new_authkey|s}

      -
    • {tDownload preconfigured client installer for Windows}
    • +
    • {tDownload preconfigured client installer for Windows}
    • - {tDownload preconfigured client installer for Linux} + {tDownload preconfigured client installer for Linux}

      {tInstall it directly in the terminal via:}

      - TF=`mktemp` && wget "{linux_url}" -O $TF && sudo sh $TF; rm -f $TF + TF=`mktemp` && wget "{linux_url|s}" -O $TF && sudo sh $TF; rm -f $TF

      @@ -26,7 +26,7 @@

      RUN TF=`mktemp` &&\
      - wget "{linux_url}" -O $TF &&\
      + wget "{linux_url|s}" -O $TF &&\
      sh $TF &&\
      rm -f $TF &&\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\
      @@ -43,7 +43,7 @@ sh $TF &&\
      rm -f $TF &&\
      urbackupclientctl wait-for-backend &&\
      - urbackupclientctl set-settings --server-url "{server_url}" --name "{new_clientname}" --authkey "{new_authkey}"{internet_proxy_settings} &&\
      + urbackupclientctl set-settings --server-url "{server_url|s}" --name "{new_clientname|s}" --authkey "{new_authkey|s}"{internet_proxy_settings} &&\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )
      @@ -59,9 +59,9 @@

      • {tGo to the settings screen on the client}
      • {tEnable the internet mode on the client}
      • -
      • {tSet the URL to connect to:} {server_url}
      • -
      • {tSet the name to:} {new_clientname}
      • -
      • {tSet the authentication key to:} {new_authkey}
      • +
      • {tSet the URL to connect to:} {server_url|s}
      • +
      • {tSet the name to:} {new_clientname|s}
      • +
      • {tSet the authentication key to:} {new_authkey|s}
      • {tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient}

      @@ -71,7 +71,7 @@

      urbackupclientctl wait-for-backend
      - urbackupclientctl set-settings --server-url "{server-url}" --name "{new_clientname}" --authkey "{new_authkey}"{internet_proxy_settings}
      + urbackupclientctl set-settings --server-url "{server_url|s}" --name "{new_clientname|s}" --authkey "{new_authkey|s}"{internet_proxy_settings}
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient
      From 0f95cfd160f87a8b18e205397c4b6d6ec5a75e11 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 23 Jan 2022 19:30:01 +0100 Subject: [PATCH 116/469] Fix getting archival prefix + index --- urbackupserver/server_archive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/server_archive.cpp b/urbackupserver/server_archive.cpp index de61d7424..ec29e8896 100644 --- a/urbackupserver/server_archive.cpp +++ b/urbackupserver/server_archive.cpp @@ -331,7 +331,7 @@ namespace i = 0; - if (params.find("every_" + prefix + convert(i)) == params.end()) + if (params.find("every_" + prefix + convert(i)) != params.end()) { idx = prefix + convert(i); return true; From 1d70bfefff1ff7e64900bbc196b6be6b0b356440 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 29 Jan 2022 13:18:17 +0100 Subject: [PATCH 117/469] Uninstall initramfs script --- uninstall_urbackupclient | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/uninstall_urbackupclient b/uninstall_urbackupclient index 86071aa6a..12e3054c6 100644 --- a/uninstall_urbackupclient +++ b/uninstall_urbackupclient @@ -29,6 +29,13 @@ then /etc/init.d/urbackupclientbackend stop || true fi +if [ -e "/usr/share/initramfs-tools/hooks/urbackup-setup-snapshot" ] +then + echo "Removing device mapper boot volume snapshot..." + rm /usr/share/initramfs-tools/scripts/local-top/urbackup-setup-snapshot + update-initramfs -u +fi + if [ ! -e "$PREFIX/var/urbackup/backup_server.db" ] then From 0d3585cd00621d950c5f5e935b964b89ac0dad6d Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 29 Jan 2022 13:36:33 +0100 Subject: [PATCH 118/469] Ignore case when looking at websocket headers --- urbackupclient/InternetClient.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urbackupclient/InternetClient.cpp b/urbackupclient/InternetClient.cpp index 095d55cd1..ae0fd56a5 100644 --- a/urbackupclient/InternetClient.cpp +++ b/urbackupclient/InternetClient.cpp @@ -1333,14 +1333,14 @@ IPipe * InternetClient::connect(const SServerConnectionSettings & selected_serve header_map[key] = trim(getafter(":", headers[i])); } - if (header_map["sec-websocket-protocol"] != "urbackup") + if (strlower(header_map["sec-websocket-protocol"]) != "urbackup") { Server->Log("Unknown web socket protocol \"" + header_map["sec-websocket-protocol"] + "\"", LL_ERROR); Server->destroy(cs); return NULL; } - if (header_map["upgrade"] != "websocket") + if (strlower(header_map["upgrade"]) != "websocket") { Server->Log("Unknown web socket upgrade value \"" + header_map["upgrade"] + "\"", LL_ERROR); Server->destroy(cs); From f9e7652db4c50dc7a614bb88beb2675728047753 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 29 Jan 2022 13:41:06 +0100 Subject: [PATCH 119/469] Ignore case when looking at websocket headers --- httpserver/HTTPClient.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/httpserver/HTTPClient.cpp b/httpserver/HTTPClient.cpp index 6093da25a..2d45f45b1 100644 --- a/httpserver/HTTPClient.cpp +++ b/httpserver/HTTPClient.cpp @@ -528,9 +528,10 @@ bool CHTTPClient::processRequest(void) size_t pstart; str_map::iterator upgrade_param = http_params.find("UPGRADE"); if (upgrade_param != http_params.end() - && upgrade_param->second == "websocket") + && strlower(upgrade_param->second) == "websocket") { std::string name = getuntil("?", *pl); + if (name.empty()) name = *pl; std::string gparams = getafter("?", *pl); CHTTPSocket* socket_handler = new CHTTPSocket(name, gparams, http_params, pipe, endpoint); request_ticket = Server->getThreadPool()->execute(socket_handler, "http websocket"); From 6d4950376115ccb3a31689640962fe03a68cb11a Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 29 Jan 2022 13:56:36 +0100 Subject: [PATCH 120/469] Ignore case when looking at websocket headers --- urbackupserver/WebSocketConnector.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urbackupserver/WebSocketConnector.cpp b/urbackupserver/WebSocketConnector.cpp index 2230174f2..12403f2b9 100644 --- a/urbackupserver/WebSocketConnector.cpp +++ b/urbackupserver/WebSocketConnector.cpp @@ -9,7 +9,7 @@ extern ICryptoFactory* crypto_fak; void WebSocketConnector::Execute(str_map& GET, THREAD_ID tid, str_map& PARAMS, IPipe* pipe, const std::string& endpoint_name) { - if (PARAMS["CONNECTION"] != "Upgrade") + if (strlower(PARAMS["CONNECTION"]) != "upgrade") { pipe->Write("HTTP/1.1 500 Expecting Connection: Upgrade\r\nConnection: Close\r\n\r\n"); delete pipe; @@ -21,7 +21,7 @@ void WebSocketConnector::Execute(str_map& GET, THREAD_ID tid, str_map& PARAMS, I Tokenize(protocol_list, protocols, ","); for (size_t i = 0; i < protocols.size(); ++i) - protocols[i] = trim(protocols[i]); + protocols[i] = strlower(trim(protocols[i])); if (std::find(protocols.begin(), protocols.end(), "urbackup") == protocols.end()) { From 16faa57a456e7576f59fe5acc6d1c0801b21cc2a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 29 Jan 2022 13:59:43 +0100 Subject: [PATCH 121/469] Fix uninitialized value (cherry picked from commit c5379a241109bca7b953dd2a69fb50f836699d7a) # Conflicts: # urbackupcommon/fileclient/FileClientChunked.cpp --- urbackupcommon/fileclient/FileClientChunked.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/urbackupcommon/fileclient/FileClientChunked.cpp b/urbackupcommon/fileclient/FileClientChunked.cpp index e35c758dc..8c2a0979a 100644 --- a/urbackupcommon/fileclient/FileClientChunked.cpp +++ b/urbackupcommon/fileclient/FileClientChunked.cpp @@ -2309,7 +2309,7 @@ void FileClientChunked::adjustOutputFilesizeOnFailure( _i64& filesize_out ) _u32 read; do { - bool has_error; + bool has_error = false; read = m_chunkhashes->Read(&buffer[0], 4096, &has_error); if(has_error) @@ -2454,4 +2454,5 @@ _i64 FileClientChunked::getRealTransferredBytes() tbytes+=ofbPipe()->getRealTransferredBytes(); } return tbytes; -} +} + From 6e204cb4d64d54947b6247ce8072c88bf7971f31 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 5 Feb 2022 12:12:06 +0100 Subject: [PATCH 122/469] Use sync functions when generating templates --- .../www/templates/compile_templates.js | 58 +++++++------------ 1 file changed, 22 insertions(+), 36 deletions(-) diff --git a/urbackupserver/www/templates/compile_templates.js b/urbackupserver/www/templates/compile_templates.js index 8de16d507..d0711bfea 100644 --- a/urbackupserver/www/templates/compile_templates.js +++ b/urbackupserver/www/templates/compile_templates.js @@ -11,50 +11,36 @@ function getExtension(filename) { } var templates_file="../js/templates.js"; -var generating = false; function generate_templates() { - if(generating==true) - { - setTimeout(generate_templates, 100); - return; - } - generating = true; console.log("Generating templates..."); fs.writeFileSync(templates_file+".new", ''); - - var open_files = 0; - fs.readdir('.', function(err,files){ - if(err) throw err; - files.sort(function(a, b) { - return a===b ? 0 : ( a < b ? -1 : 1); - }).forEach(function(file){ - if(getExtension(file)=="htm") - { - ++open_files; - fs.readFile(file, 'utf8', function (err,data) { - if (err) throw err; - if (data.charCodeAt(0) == 65279) { - data = data.substring(1); - } - console.log("Compiling template "+file+" ..."); - fs.appendFileSync(templates_file+".new", dust.compile(data, file.substring(0, file.length-4))+"\n"); - --open_files; - - if(open_files==0) - { - console.log("Done compiling templates."); - generating = false; - - fs.createReadStream(templates_file+".new").pipe(fs.createWriteStream(templates_file)); - } - }); + files = fs.readdirSync('.'); + + files = files.sort(function(a, b) { + return a===b ? 0 : ( a < b ? -1 : 1); + }); + + files.forEach(function(file) + { + if(getExtension(file)=="htm") + { + data = fs.readFileSync(file, 'utf8'); + + if (data.charCodeAt(0) == 65279) { + data = data.substring(1); } - }); - }); + console.log("Compiling template "+file+" ..."); + fs.appendFileSync(templates_file+".new", dust.compile(data, file.substring(0, file.length-4))+"\n"); + } + }); + + console.log("Done compiling templates."); + + fs.createReadStream(templates_file+".new").pipe(fs.createWriteStream(templates_file)); } generate_templates(); From 9bea026cff0ce0ed787e5539ba683129547b3ae1 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 5 Feb 2022 12:43:06 +0100 Subject: [PATCH 123/469] Increment version --- configure.ac_client | 2 +- configure.ac_server | 2 +- urbackupserver/www/js/urbackup.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 3587334aa..40a463198 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.16.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.17.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/configure.ac_server b/configure.ac_server index eaa4e2e45..73da298c7 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.22.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.23.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index ed9a86b1a..c45de6ac5 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5,7 +5,7 @@ g.startup=true; g.no_tab_mouse_click=false; g.tabberidx=-1; g.progress_stop_id=-1; -g.current_version=2005002200; +g.current_version=2005002300; g.status_show_all=false; g.ldap_login=false; g.datatable_default_config={}; From 7861eb2045a4694b350dbc9d0c5ae74072023a91 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 5 Feb 2022 12:46:06 +0100 Subject: [PATCH 124/469] Fix FreeBSD build issue --- fileservplugin/FileMetadataPipe.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fileservplugin/FileMetadataPipe.h b/fileservplugin/FileMetadataPipe.h index 7024c46f3..58739c337 100644 --- a/fileservplugin/FileMetadataPipe.h +++ b/fileservplugin/FileMetadataPipe.h @@ -134,7 +134,7 @@ class FileMetadataPipe : public PipeFileBase #include #include "../common/data.h" -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(__FreeBSD__) void serialize_stat_buf(const struct stat& buf, const std::string& symlink_target, CWData& data); #else void serialize_stat_buf(const struct stat64& buf, const std::string& symlink_target, CWData& data); From 7026f45a8cff7741e2f14b3c06ac3ded904ba982 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 5 Feb 2022 22:15:40 +0100 Subject: [PATCH 125/469] Fix vhdx restore --- urbackupserver/server_channel.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/urbackupserver/server_channel.cpp b/urbackupserver/server_channel.cpp index f38d360d7..ff8eef27c 100644 --- a/urbackupserver/server_channel.cpp +++ b/urbackupserver/server_channel.cpp @@ -1172,6 +1172,10 @@ void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) { vhdfile = image_fak->createVHDFile(res[0]["path"], true, 0, 2 * 1024 * 1024, false, IFSImageFactory::ImageFormat_RawCowFile); } + else if (file_extension == "vhdx" || file_extension == "vhdxz") + { + vhdfile = image_fak->createVHDFile(res[0]["path"], true, 0, 2 * 1024 * 1024, false, IFSImageFactory::ImageFormat_VHDX); + } else { vhdfile = image_fak->createVHDFile(res[0]["path"], true, 0); From e8a1f5eb5c1b5e81a2943b4dee15d889e46dac12 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 6 Feb 2022 12:59:18 +0100 Subject: [PATCH 126/469] Fix SEGV if mount device cannot be found --- urbackupclient/client.cpp | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 8255f22c2..67b810a53 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -9176,21 +9176,18 @@ void IndexThread::addScRefs(VSS_ID ssetid, std::vector& out) void IndexThread::openCbtHdatFile(SCRef* ref, const std::string& sharename, const std::string & volume) { - if (ref!=NULL - && ref->cbt) - { #ifdef _WIN32 - std::string vol = volume; - normalizeVolume(vol); - vol = strlower(vol); + std::string vol = volume; + normalizeVolume(vol); + vol = strlower(vol); #else - std::string vol = getMountDevice(volume); - if(vol.empty()) - { - return; - } + std::string vol = getMountDevice(volume); #endif + if (ref!=NULL + && ref->cbt + && !vol.empty()) + { index_hdat_file.reset(Server->openFile("urbackup/hdat_file_" + conv_filename(vol) + ".dat", MODE_RW_CREATE_DELETE)); index_hdat_fs_block_size = -1; From bfd6145d5d47c11e975b40045c6260b6c2deb4d5 Mon Sep 17 00:00:00 2001 From: Shura Date: Tue, 15 Feb 2022 11:11:50 +0300 Subject: [PATCH 127/469] password param fix There was an wrong equal sign. --- urbackupclient/backup_scripts/list | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/backup_scripts/list b/urbackupclient/backup_scripts/list index c4dada99f..a2d07a89e 100644 --- a/urbackupclient/backup_scripts/list +++ b/urbackupclient/backup_scripts/list @@ -11,7 +11,7 @@ if [ "x$MARIADB_DUMP_ENABLED" != "x0" ] then if [ "x$MARIADB_DUMP_PER_BASE" != "x0" ] then - baselist=$(mysql -u $MARIADB_BACKUP_USER -p=$MARIADB_BACKUP_PASSWORD -e 'show databases' -s --skip-column-names | grep -E -v 'information_schema|performance_schema') + baselist=$(mysql -u $MARIADB_BACKUP_USER -p$MARIADB_BACKUP_PASSWORD -e 'show databases' -s --skip-column-names | grep -E -v 'information_schema|performance_schema') for i in $baselist do echo "scriptname=mariadbdump&outputname=mariadbdump_$i.sql" From d19b2aab0b28236266a8d8f99c395d441de52185 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 20 Feb 2022 13:17:50 +0100 Subject: [PATCH 128/469] Fix quoting --- linux_snapshot/dattobd_create_snapshot | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/linux_snapshot/dattobd_create_snapshot b/linux_snapshot/dattobd_create_snapshot index 986ca5b70..e3f81b19a 100755 --- a/linux_snapshot/dattobd_create_snapshot +++ b/linux_snapshot/dattobd_create_snapshot @@ -15,12 +15,12 @@ SNAP_COWFILE_PATH="/mnt/urbackup_snaps/cbt_info/$SNAP_MOUNTPOINT_SAN-cowfile" exists() { - [ -e $1 ] + [ -e "$1" ] } has_num () { - exists "/mnt/urbackup_snaps/cbt_info/*-snapdev" && grep "$1" "/mnt/urbackup_snaps/cbt_info/*-snapdev" > /dev/null + exists /mnt/urbackup_snaps/cbt_info/*-snapdev && grep "$1" /mnt/urbackup_snaps/cbt_info/*-snapdev > /dev/null } From a7952567a6f2d3c2c0891741ac7bbbb88fcbd6d2 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 13 Mar 2022 09:19:10 +0100 Subject: [PATCH 129/469] Properly free local encryption/compression --- urbackupclient/ClientService.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 400763acf..6f71153b2 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -312,6 +312,11 @@ void ClientConnector::Init(THREAD_ID pTID, IPipe *pPipe, const std::string& pEnd ClientConnector::~ClientConnector(void) { + if (state != CCSTATE_FILESERV && pipe != orig_pipe) + { + Server->destroy(pipe); + } + if (curr_result_id != 0) { IndexThread::removeResult(curr_result_id); @@ -3598,7 +3603,14 @@ bool ClientConnector::closeSocket( void ) { if(state!=CCSTATE_FILESERV) { - return true; + if (pipe != orig_pipe) + { + return false; + } + else + { + return true; + } } else { From 1f3de070abb98530c954d913f4e25b032c66e91b Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 13 Mar 2022 09:19:53 +0100 Subject: [PATCH 130/469] Don't try to connect if there are no servers to connect to --- urbackupclient/InternetClient.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupclient/InternetClient.cpp b/urbackupclient/InternetClient.cpp index ae0fd56a5..54b3f7861 100644 --- a/urbackupclient/InternetClient.cpp +++ b/urbackupclient/InternetClient.cpp @@ -241,7 +241,8 @@ void InternetClient::operator()(void) } else { - if(n_connectionsgetThreadPool()->execute(new InternetClientThread(NULL, server_settings, NULL), "internet client"); newConnection(); From a5897e2a58273d8caf25df1d0fdb6c4ae01a7b96 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 13 Mar 2022 09:20:23 +0100 Subject: [PATCH 131/469] Add timestamp to server keyadd --- urbackupserver/ClientMain.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index f89289f51..0c62c377d 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -2696,8 +2696,10 @@ IPipe *ClientMain::getClientCommandConnection(ServerSettings* server_settings, i std::string server_keyadd; if (!secret_session_key.empty()) { - server_keyadd.resize(16); - Server->randomFill(&server_keyadd[0], server_keyadd.size()); + server_keyadd.resize(16 + sizeof(int64)); + Server->randomFill(&server_keyadd[0], server_keyadd.size()- sizeof(int64)); + int64 ctime = Server->getTimeSeconds(); + memcpy(&server_keyadd[16], &ctime, sizeof(int64)); } std::string tosend = identity + "ENC?keyadd="+ base64_encode_dash(server_keyadd)+"&compress="+EscapeParamString(compression)+"&compress_level="+convert(compression_level); size_t rc = tcpstack.Send(ret, tosend); From ebb76d4c75204e0d0c4f52375a97373ec78cc05a Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 13 Mar 2022 09:20:43 +0100 Subject: [PATCH 132/469] Remove unused setting --- urbackupcommon/settingslist.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/urbackupcommon/settingslist.cpp b/urbackupcommon/settingslist.cpp index 070b00b22..8f318312c 100644 --- a/urbackupcommon/settingslist.cpp +++ b/urbackupcommon/settingslist.cpp @@ -61,7 +61,6 @@ std::vector getSettingsList(void) ret.push_back("internet_authkey"); ret.push_back("internet_speed"); ret.push_back("local_speed"); - ret.push_back("internet_client_enabled"); ret.push_back("internet_image_backups"); ret.push_back("internet_full_file_backups"); ret.push_back("internet_encrypt"); From 49a79831196e13e0f69d026bf1142fefee9504b3 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 14 Mar 2022 22:00:40 +0100 Subject: [PATCH 133/469] Fix escape issue --- urbackupserver/www/templates/client_added.htm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/www/templates/client_added.htm b/urbackupserver/www/templates/client_added.htm index 0e3cf51b2..5f19a151a 100644 --- a/urbackupserver/www/templates/client_added.htm +++ b/urbackupserver/www/templates/client_added.htm @@ -9,7 +9,7 @@

        -
      • {tDownload preconfigured client installer for Windows}
      • +
      • {tDownload preconfigured client installer for Windows}
      • {tDownload preconfigured client installer for Linux}

        From 2a5e17c3b5a89785d2f0accea7f52fa7d34ac536 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 2 Apr 2022 20:51:57 +0200 Subject: [PATCH 134/469] Don't write EICAR file if it is configured to not show the error (cherry picked from commit 9fc2d934a0c6223e421e9d4d9474f2501fdd3c8c) --- urbackupserver/serverinterface/status_check.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/serverinterface/status_check.cpp b/urbackupserver/serverinterface/status_check.cpp index cabe41769..9f6af2cd3 100644 --- a/urbackupserver/serverinterface/status_check.cpp +++ b/urbackupserver/serverinterface/status_check.cpp @@ -317,7 +317,8 @@ namespace os_remove_dir(os_file_prefix(test1_path)); } - if (!server_settings->no_file_backups) + if (!server_settings->no_file_backups && + !is_stop_show(db, "virus_error")) { bool use_tmpfiles = server_settings->use_tmpfiles; std::string tmpfile_path; From 8023d518c87f07e9460300d2586308c1c8884aa5 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 10 Apr 2022 12:26:29 +0200 Subject: [PATCH 135/469] Add reset all settings use switch --- urbackupserver/www/js/urbackup.js | 123 ++++++++++++++++-- .../www/templates/settings_user.htm | 3 + 2 files changed, 115 insertions(+), 11 deletions(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index c45de6ac5..d71ca36bc 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -2769,6 +2769,66 @@ function unescapeCurrentSettings(settings) setting.value_group = unescapeHTML(setting.value_group); } } +function settingSwitchReset(key) +{ + var use = g.curr_settings[key].use; + if(use==1) + use=2; + else if(use==2) + use=4; + else if(use==4) + use=3; + else if(use==3) + use=1; + + g.curr_settings[key].use=use; + + for(var i=0;i2) + use=1; + if($.inArray(key, g.mergable_settings_list)==-1 + && (use==3 || use>4)) + use=1; + + return use; +} function settingSwitch() { var key = $(this).attr("id"); @@ -2781,6 +2841,12 @@ function settingSwitch() if(key=="backup_window") key="backup_window_incr_file"; + if(key=="reset") + { + settingSwitchReset(key); + return; + } + var use = g.curr_settings[key].use; if(use==1) @@ -2798,15 +2864,7 @@ function settingSwitch() use=1; } - if($.inArray(key, g.client_settings_list)==-1 - && use==4 && $.inArray(key, g.mergable_settings_list)!=-1) - use=3; - else if($.inArray(key, g.client_settings_list)==-1 - && use>2) - use=1; - if($.inArray(key, g.mergable_settings_list)==-1 - && (use==3 || use>4)) - use=1; + use = fixupUse(key, use); if(use==2 && typeof g.curr_settings[key].value == "undefined") @@ -2933,10 +2991,48 @@ function settingChange(p_key) settingChangeKey(key); } + +function getResetVal(settings) +{ + var use = 0; + for (var key in settings) { + if (!settings.hasOwnProperty(key)) { + continue; + } + + var setting = settings[key]; + + if(typeof setting!="object") + continue; + + use |= setting.use; + } + + if(use!=1 && use!=2 && use!=4) + use = 1; + + return {"use": use, + value: "", + value_client: "", + value_group: ""}; +} + function renderSettingSwitch(key) { var val; - if(key=="backup_window") + if(key=="reset") + { + if(typeof g.curr_settings["reset"] === "undefined") + { + val = getResetVal(g.curr_settings); + g.curr_settings["reset"] = val; + } + else + { + val = g.curr_settings[key]; + } + } + else if(key=="backup_window") { val = g.curr_settings["backup_window_incr_file"]; } @@ -3047,7 +3143,7 @@ function mergeSettingUpdateUse(key) } function renderMergeSetting(key) { - if(key=="archive") + if(key=="archive" || key=="reset") return; var val = g.curr_settings[key]; @@ -3153,6 +3249,11 @@ function renderSettingSwitchAll() { renderSettingSwitch("backup_window"); } + + if(I("reset_sw")) + { + renderSettingSwitch("reset"); + } } function show_settings2(data) { diff --git a/urbackupserver/www/templates/settings_user.htm b/urbackupserver/www/templates/settings_user.htm index 6622382fc..2e921f239 100644 --- a/urbackupserver/www/templates/settings_user.htm +++ b/urbackupserver/www/templates/settings_user.htm @@ -16,6 +16,9 @@

    {/groupmod} +
    + Reset:
    +
    ** -** Note the last bullet in particular. The destructor X in +** Note the last bullet in particular. The destructor X in ** sqlite3_set_auxdata(C,N,P,X) might be called immediately, before the ** sqlite3_set_auxdata() interface even returns. Hence sqlite3_set_auxdata() ** should be called near the end of the function implementation and the @@ -5543,8 +5985,9 @@ typedef void (*sqlite3_destructor_type)(void*); ** 2nd parameter of sqlite3_result_error() or sqlite3_result_error16() ** as the text of an error message. ^SQLite interprets the error ** message string from sqlite3_result_error() as UTF-8. ^SQLite -** interprets the string from sqlite3_result_error16() as UTF-16 in native -** byte order. ^If the third parameter to sqlite3_result_error() +** interprets the string from sqlite3_result_error16() as UTF-16 using +** the same [byte-order determination rules] as [sqlite3_bind_text16()]. +** ^If the third parameter to sqlite3_result_error() ** or sqlite3_result_error16() is negative then SQLite takes as the error ** message all text up through the first zero character. ** ^If the third parameter to sqlite3_result_error() or @@ -5586,9 +6029,10 @@ typedef void (*sqlite3_destructor_type)(void*); ** of [SQLITE_UTF8], [SQLITE_UTF16], [SQLITE_UTF16BE], or [SQLITE_UTF16LE]. ** ^SQLite takes the text result from the application from ** the 2nd parameter of the sqlite3_result_text* interfaces. -** ^If the 3rd parameter to the sqlite3_result_text* interfaces -** is negative, then SQLite takes result text from the 2nd parameter -** through the first zero character. +** ^If the 3rd parameter to any of the sqlite3_result_text* interfaces +** other than sqlite3_result_text64() is negative, then SQLite computes +** the string length itself by searching the 2nd parameter for the first +** zero character. ** ^If the 3rd parameter to the sqlite3_result_text* interfaces ** is non-negative, then as many bytes (not characters) of the text ** pointed to by the 2nd parameter are taken as the application-defined @@ -5612,6 +6056,25 @@ typedef void (*sqlite3_destructor_type)(void*); ** then SQLite makes a copy of the result into space obtained ** from [sqlite3_malloc()] before it returns. ** +** ^For the sqlite3_result_text16(), sqlite3_result_text16le(), and +** sqlite3_result_text16be() routines, and for sqlite3_result_text64() +** when the encoding is not UTF8, if the input UTF16 begins with a +** byte-order mark (BOM, U+FEFF) then the BOM is removed from the +** string and the rest of the string is interpreted according to the +** byte-order specified by the BOM. ^The byte-order specified by +** the BOM at the beginning of the text overrides the byte-order +** specified by the interface procedure. ^So, for example, if +** sqlite3_result_text16le() is invoked with text that begins +** with bytes 0xfe, 0xff (a big-endian byte-order mark) then the +** first two bytes of input are skipped and the remaining input +** is interpreted as UTF16BE text. +** +** ^For UTF16 input text to the sqlite3_result_text16(), +** sqlite3_result_text16be(), sqlite3_result_text16le(), and +** sqlite3_result_text64() routines, if the text contains invalid +** UTF16 characters, the invalid characters might be converted +** into the unicode replacement character, U+FFFD. +** ** ^The sqlite3_result_value() interface sets the result of ** the application-defined function to be a copy of the ** [unprotected sqlite3_value] object specified by the 2nd parameter. ^The @@ -5624,7 +6087,7 @@ typedef void (*sqlite3_destructor_type)(void*); ** ** ^The sqlite3_result_pointer(C,P,T,D) interface sets the result to an ** SQL NULL value, just like [sqlite3_result_null(C)], except that it -** also associates the host-language pointer P or type T with that +** also associates the host-language pointer P or type T with that ** NULL value such that the pointer can be retrieved within an ** [application-defined SQL function] using [sqlite3_value_pointer()]. ** ^If the D parameter is not NULL, then it is a pointer to a destructor @@ -5666,8 +6129,8 @@ SQLITE_API int sqlite3_result_zeroblob64(sqlite3_context*, sqlite3_uint64 n); ** METHOD: sqlite3_context ** ** The sqlite3_result_subtype(C,T) function causes the subtype of -** the result from the [application-defined SQL function] with -** [sqlite3_context] C to be the value T. Only the lower 8 bits +** the result from the [application-defined SQL function] with +** [sqlite3_context] C to be the value T. Only the lower 8 bits ** of the subtype T are preserved in current versions of SQLite; ** higher order bits are discarded. ** The number of subtype bytes preserved by SQLite might increase @@ -5714,7 +6177,7 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** deleted. ^When all collating functions having the same name are deleted, ** that collation is no longer usable. ** -** ^The collating function callback is invoked with a copy of the pArg +** ^The collating function callback is invoked with a copy of the pArg ** application data pointer and with two strings in the encoding specified ** by the eTextRep argument. The two integer parameters to the collating ** function callback are the length of the two strings, in bytes. The collating @@ -5745,36 +6208,36 @@ SQLITE_API void sqlite3_result_subtype(sqlite3_context*,unsigned int); ** calls to the collation creation functions or when the ** [database connection] is closed using [sqlite3_close()]. ** -** ^The xDestroy callback is not called if the +** ^The xDestroy callback is not called if the ** sqlite3_create_collation_v2() function fails. Applications that invoke -** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should +** sqlite3_create_collation_v2() with a non-NULL xDestroy argument should ** check the return code and dispose of the application data pointer ** themselves rather than expecting SQLite to deal with it for them. -** This is different from every other SQLite interface. The inconsistency -** is unfortunate but cannot be changed without breaking backwards +** This is different from every other SQLite interface. The inconsistency +** is unfortunate but cannot be changed without breaking backwards ** compatibility. ** ** See also: [sqlite3_collation_needed()] and [sqlite3_collation_needed16()]. */ SQLITE_API int sqlite3_create_collation( - sqlite3*, - const char *zName, - int eTextRep, + sqlite3*, + const char *zName, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); SQLITE_API int sqlite3_create_collation_v2( - sqlite3*, - const char *zName, - int eTextRep, + sqlite3*, + const char *zName, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*), void(*xDestroy)(void*) ); SQLITE_API int sqlite3_create_collation16( - sqlite3*, + sqlite3*, const void *zName, - int eTextRep, + int eTextRep, void *pArg, int(*xCompare)(void*,int,const void*,int,const void*) ); @@ -5807,64 +6270,19 @@ SQLITE_API int sqlite3_create_collation16( ** [sqlite3_create_collation_v2()]. */ SQLITE_API int sqlite3_collation_needed( - sqlite3*, - void*, + sqlite3*, + void*, void(*)(void*,sqlite3*,int eTextRep,const char*) ); SQLITE_API int sqlite3_collation_needed16( - sqlite3*, + sqlite3*, void*, void(*)(void*,sqlite3*,int eTextRep,const void*) ); -#ifdef SQLITE_HAS_CODEC -/* -** Specify the key for an encrypted database. This routine should be -** called right after sqlite3_open(). -** -** The code to implement this API is not available in the public release -** of SQLite. -*/ -SQLITE_API int sqlite3_key( - sqlite3 *db, /* Database to be rekeyed */ - const void *pKey, int nKey /* The key */ -); -SQLITE_API int sqlite3_key_v2( - sqlite3 *db, /* Database to be rekeyed */ - const char *zDbName, /* Name of the database */ - const void *pKey, int nKey /* The key */ -); - -/* -** Change the key on an open database. If the current database is not -** encrypted, this routine will encrypt it. If pNew==0 or nNew==0, the -** database is decrypted. -** -** The code to implement this API is not available in the public release -** of SQLite. -*/ -SQLITE_API int sqlite3_rekey( - sqlite3 *db, /* Database to be rekeyed */ - const void *pKey, int nKey /* The new key */ -); -SQLITE_API int sqlite3_rekey_v2( - sqlite3 *db, /* Database to be rekeyed */ - const char *zDbName, /* Name of the database */ - const void *pKey, int nKey /* The new key */ -); - -/* -** Specify the activation key for a SEE database. Unless -** activated, none of the SEE routines will work. -*/ -SQLITE_API void sqlite3_activate_see( - const char *zPassPhrase /* Activation phrase */ -); -#endif - #ifdef SQLITE_ENABLE_CEROD /* -** Specify the activation key for a CEROD database. Unless +** Specify the activation key for a CEROD database. Unless ** activated, none of the CEROD routines will work. */ SQLITE_API void sqlite3_activate_cerod( @@ -5888,6 +6306,13 @@ SQLITE_API void sqlite3_activate_cerod( ** of the default VFS is not implemented correctly, or not implemented at ** all, then the behavior of sqlite3_sleep() may deviate from the description ** in the previous paragraphs. +** +** If a negative argument is passed to sqlite3_sleep() the results vary by +** VFS and operating system. Some system treat a negative argument as an +** instruction to sleep forever. Others understand it to mean do not sleep +** at all. ^In SQLite version 3.42.0 and later, a negative +** argument passed into sqlite3_sleep() is changed to zero before it is relayed +** down into the xSleep method of the VFS. */ SQLITE_API int sqlite3_sleep(int); @@ -5920,7 +6345,7 @@ SQLITE_API int sqlite3_sleep(int); ** ^The [temp_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [temp_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -5977,7 +6402,7 @@ SQLITE_API SQLITE_EXTERN char *sqlite3_temp_directory; ** ^The [data_store_directory pragma] may modify this variable and cause ** it to point to memory obtained from [sqlite3_malloc]. ^Furthermore, ** the [data_store_directory pragma] always assumes that any string -** that this variable points to is held in memory obtained from +** that this variable points to is held in memory obtained from ** [sqlite3_malloc] and the pragma may attempt to free that memory ** using [sqlite3_free]. ** Hence, if this variable is modified directly, either it should be @@ -6058,6 +6483,28 @@ SQLITE_API int sqlite3_get_autocommit(sqlite3*); */ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); +/* +** CAPI3REF: Return The Schema Name For A Database Connection +** METHOD: sqlite3 +** +** ^The sqlite3_db_name(D,N) interface returns a pointer to the schema name +** for the N-th database on database connection D, or a NULL pointer of N is +** out of range. An N value of 0 means the main database file. An N of 1 is +** the "temp" schema. Larger values of N correspond to various ATTACH-ed +** databases. +** +** Space to hold the string that is returned by sqlite3_db_name() is managed +** by SQLite itself. The string might be deallocated by any operation that +** changes the schema, including [ATTACH] or [DETACH] or calls to +** [sqlite3_serialize()] or [sqlite3_deserialize()], even operations that +** occur on a different thread. Applications that need to +** remember the string long-term should make their own copy. Applications that +** are accessing the same database connection simultaneously on multiple +** threads should mutex-protect calls to this API and should make their own +** private copy of the result prior to releasing the mutex. +*/ +SQLITE_API const char *sqlite3_db_name(sqlite3 *db, int N); + /* ** CAPI3REF: Return The Filename For A Database Connection ** METHOD: sqlite3 @@ -6088,7 +6535,7 @@ SQLITE_API sqlite3 *sqlite3_db_handle(sqlite3_stmt*); **
  • [sqlite3_filename_wal()] ** */ -SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); +SQLITE_API sqlite3_filename sqlite3_db_filename(sqlite3 *db, const char *zDbName); /* ** CAPI3REF: Determine if a database is read-only @@ -6100,6 +6547,57 @@ SQLITE_API const char *sqlite3_db_filename(sqlite3 *db, const char *zDbName); */ SQLITE_API int sqlite3_db_readonly(sqlite3 *db, const char *zDbName); +/* +** CAPI3REF: Determine the transaction state of a database +** METHOD: sqlite3 +** +** ^The sqlite3_txn_state(D,S) interface returns the current +** [transaction state] of schema S in database connection D. ^If S is NULL, +** then the highest transaction state of any schema on database connection D +** is returned. Transaction states are (in order of lowest to highest): +**
      +**
    1. SQLITE_TXN_NONE +**
    2. SQLITE_TXN_READ +**
    3. SQLITE_TXN_WRITE +**
    +** ^If the S argument to sqlite3_txn_state(D,S) is not the name of +** a valid schema, then -1 is returned. +*/ +SQLITE_API int sqlite3_txn_state(sqlite3*,const char *zSchema); + +/* +** CAPI3REF: Allowed return values from [sqlite3_txn_state()] +** KEYWORDS: {transaction state} +** +** These constants define the current transaction state of a database file. +** ^The [sqlite3_txn_state(D,S)] interface returns one of these +** constants in order to describe the transaction state of schema S +** in [database connection] D. +** +**
    +** [[SQLITE_TXN_NONE]]
    SQLITE_TXN_NONE
    +**
    The SQLITE_TXN_NONE state means that no transaction is currently +** pending.
    +** +** [[SQLITE_TXN_READ]]
    SQLITE_TXN_READ
    +**
    The SQLITE_TXN_READ state means that the database is currently +** in a read transaction. Content has been read from the database file +** but nothing in the database file has changed. The transaction state +** will advanced to SQLITE_TXN_WRITE if any changes occur and there are +** no other conflicting concurrent write transactions. The transaction +** state will revert to SQLITE_TXN_NONE following a [ROLLBACK] or +** [COMMIT].
    +** +** [[SQLITE_TXN_WRITE]]
    SQLITE_TXN_WRITE
    +**
    The SQLITE_TXN_WRITE state means that the database is currently +** in a write transaction. Content has been written to the database file +** but has not yet committed. The transaction state will change to +** to SQLITE_TXN_NONE at the next [ROLLBACK] or [COMMIT].
    +*/ +#define SQLITE_TXN_NONE 0 +#define SQLITE_TXN_READ 1 +#define SQLITE_TXN_WRITE 2 + /* ** CAPI3REF: Find the next prepared statement ** METHOD: sqlite3 @@ -6166,6 +6664,72 @@ SQLITE_API sqlite3_stmt *sqlite3_next_stmt(sqlite3 *pDb, sqlite3_stmt *pStmt); SQLITE_API void *sqlite3_commit_hook(sqlite3*, int(*)(void*), void*); SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); +/* +** CAPI3REF: Autovacuum Compaction Amount Callback +** METHOD: sqlite3 +** +** ^The sqlite3_autovacuum_pages(D,C,P,X) interface registers a callback +** function C that is invoked prior to each autovacuum of the database +** file. ^The callback is passed a copy of the generic data pointer (P), +** the schema-name of the attached database that is being autovacuumed, +** the size of the database file in pages, the number of free pages, +** and the number of bytes per page, respectively. The callback should +** return the number of free pages that should be removed by the +** autovacuum. ^If the callback returns zero, then no autovacuum happens. +** ^If the value returned is greater than or equal to the number of +** free pages, then a complete autovacuum happens. +** +**

    ^If there are multiple ATTACH-ed database files that are being +** modified as part of a transaction commit, then the autovacuum pages +** callback is invoked separately for each file. +** +**

    The callback is not reentrant. The callback function should +** not attempt to invoke any other SQLite interface. If it does, bad +** things may happen, including segmentation faults and corrupt database +** files. The callback function should be a simple function that +** does some arithmetic on its input parameters and returns a result. +** +** ^The X parameter to sqlite3_autovacuum_pages(D,C,P,X) is an optional +** destructor for the P parameter. ^If X is not NULL, then X(P) is +** invoked whenever the database connection closes or when the callback +** is overwritten by another invocation of sqlite3_autovacuum_pages(). +** +**

    ^There is only one autovacuum pages callback per database connection. +** ^Each call to the sqlite3_autovacuum_pages() interface overrides all +** previous invocations for that database connection. ^If the callback +** argument (C) to sqlite3_autovacuum_pages(D,C,P,X) is a NULL pointer, +** then the autovacuum steps callback is cancelled. The return value +** from sqlite3_autovacuum_pages() is normally SQLITE_OK, but might +** be some other error code if something goes wrong. The current +** implementation will only return SQLITE_OK or SQLITE_MISUSE, but other +** return codes might be added in future releases. +** +**

    If no autovacuum pages callback is specified (the usual case) or +** a NULL pointer is provided for the callback, +** then the default behavior is to vacuum all free pages. So, in other +** words, the default behavior is the same as if the callback function +** were something like this: +** +**

    +**     unsigned int demonstration_autovac_pages_callback(
    +**       void *pClientData,
    +**       const char *zSchema,
    +**       unsigned int nDbPage,
    +**       unsigned int nFreePage,
    +**       unsigned int nBytePerPage
    +**     ){
    +**       return nFreePage;
    +**     }
    +** 
    +*/ +SQLITE_API int sqlite3_autovacuum_pages( + sqlite3 *db, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, + void(*)(void*) +); + + /* ** CAPI3REF: Data Change Notification Callbacks ** METHOD: sqlite3 @@ -6190,7 +6754,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** ^In the case of an update, this is the [rowid] after the update takes place. ** ** ^(The update hook is not invoked when internal system tables are -** modified (i.e. sqlite_master and sqlite_sequence).)^ +** modified (i.e. sqlite_sequence).)^ ** ^The update hook is not invoked when [WITHOUT ROWID] tables are modified. ** ** ^In the current implementation, the update hook @@ -6216,7 +6780,7 @@ SQLITE_API void *sqlite3_rollback_hook(sqlite3*, void(*)(void *), void*); ** and [sqlite3_preupdate_hook()] interfaces. */ SQLITE_API void *sqlite3_update_hook( - sqlite3*, + sqlite3*, void(*)(void *,int ,char const *,char const *,sqlite3_int64), void* ); @@ -6229,8 +6793,13 @@ SQLITE_API void *sqlite3_update_hook( ** to the same database. Sharing is enabled if the argument is true ** and disabled if the argument is false.)^ ** +** This interface is omitted if SQLite is compiled with +** [-DSQLITE_OMIT_SHARED_CACHE]. The [-DSQLITE_OMIT_SHARED_CACHE] +** compile-time option is recommended because the +** [use of shared cache mode is discouraged]. +** ** ^Cache sharing is enabled and disabled for an entire process. -** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). +** This is a change as of SQLite [version 3.5.0] ([dateof:3.5.0]). ** In prior versions of SQLite, ** sharing was enabled or disabled for each thread separately. ** @@ -6251,8 +6820,8 @@ SQLITE_API void *sqlite3_update_hook( ** with the [SQLITE_OPEN_SHAREDCACHE] flag. ** ** Note: This method is disabled on MacOS X 10.7 and iOS version 5.0 -** and will always return SQLITE_MISUSE. On those systems, -** shared cache mode should be enabled per-database connection via +** and will always return SQLITE_MISUSE. On those systems, +** shared cache mode should be enabled per-database connection via ** [sqlite3_open_v2()] with [SQLITE_OPEN_SHAREDCACHE]. ** ** This interface is threadsafe on processors where writing a @@ -6305,7 +6874,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** as heap memory usages approaches the limit. ** ^The soft heap limit is "soft" because even though SQLite strives to stay ** below the limit, it will exceed the limit rather than generate -** an [SQLITE_NOMEM] error. In other words, the soft heap limit +** an [SQLITE_NOMEM] error. In other words, the soft heap limit ** is advisory only. ** ** ^The sqlite3_hard_heap_limit64(N) interface sets a hard upper bound of @@ -6327,7 +6896,7 @@ SQLITE_API int sqlite3_db_release_memory(sqlite3*); ** ^The soft heap limit may not be greater than the hard heap limit. ** ^If the hard heap limit is enabled and if sqlite3_soft_heap_limit(N) ** is invoked with a value of N that is greater than the hard heap limit, -** the the soft heap limit is set to the value of the hard heap limit. +** the soft heap limit is set to the value of the hard heap limit. ** ^The soft heap limit is automatically enabled whenever the hard heap ** limit is enabled. ^When sqlite3_hard_heap_limit64(N) is invoked and ** the soft heap limit is outside the range of 1..N, then the soft heap @@ -6421,7 +6990,7 @@ SQLITE_API SQLITE_DEPRECATED void sqlite3_soft_heap_limit(int N); ** ** ^If the specified table is actually a view, an [error code] is returned. ** -** ^If the specified column is "rowid", "oid" or "_rowid_" and the table +** ^If the specified column is "rowid", "oid" or "_rowid_" and the table ** is not a [WITHOUT ROWID] table and an ** [INTEGER PRIMARY KEY] column has been explicitly declared, then the output ** parameters are set for the explicitly declared column. ^(If there is no @@ -6487,7 +7056,7 @@ SQLITE_API int sqlite3_table_column_metadata( ** prior to calling this API, ** otherwise an error will be returned. ** -** Security warning: It is recommended that the +** Security warning: It is recommended that the ** [SQLITE_DBCONFIG_ENABLE_LOAD_EXTENSION] method be used to enable only this ** interface. The use of the [sqlite3_enable_load_extension()] interface ** should be avoided. This will keep the SQL function [load_extension()] @@ -6574,7 +7143,7 @@ SQLITE_API int sqlite3_auto_extension(void(*xEntryPoint)(void)); ** ^The [sqlite3_cancel_auto_extension(X)] interface unregisters the ** initialization routine X that was registered using a prior call to ** [sqlite3_auto_extension(X)]. ^The [sqlite3_cancel_auto_extension(X)] -** routine returns 1 if initialization routine X was successfully +** routine returns 1 if initialization routine X was successfully ** unregistered and it returns 0 if X was not on the list of initialization ** routines. */ @@ -6588,15 +7157,6 @@ SQLITE_API int sqlite3_cancel_auto_extension(void(*xEntryPoint)(void)); */ SQLITE_API void sqlite3_reset_auto_extension(void); -/* -** The interface to the virtual-table mechanism is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - /* ** Structures used by the virtual table interface */ @@ -6609,8 +7169,8 @@ typedef struct sqlite3_module sqlite3_module; ** CAPI3REF: Virtual Table Object ** KEYWORDS: sqlite3_module {virtual table module} ** -** This structure, sometimes called a "virtual table module", -** defines the implementation of a [virtual table]. +** This structure, sometimes called a "virtual table module", +** defines the implementation of a [virtual table]. ** This structure consists mostly of methods for the module. ** ** ^A virtual table module is created by filling in a persistent @@ -6649,7 +7209,7 @@ struct sqlite3_module { void (**pxFunc)(sqlite3_context*,int,sqlite3_value**), void **ppArg); int (*xRename)(sqlite3_vtab *pVtab, const char *zNew); - /* The methods above are in version 1 of the sqlite_module object. Those + /* The methods above are in version 1 of the sqlite_module object. Those ** below are for version 2 and greater. */ int (*xSavepoint)(sqlite3_vtab *pVTab, int); int (*xRelease)(sqlite3_vtab *pVTab, int); @@ -6699,7 +7259,7 @@ struct sqlite3_module { ** required by SQLite. If the table has at least 64 columns and any column ** to the right of the first 63 is required, then bit 63 of colUsed is also ** set. In other words, column iCol may be required if the expression -** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to +** (colUsed & ((sqlite3_uint64)1 << (iCol>=63 ? 63 : iCol))) evaluates to ** non-zero. ** ** The [xBestIndex] method must fill aConstraintUsage[] with information @@ -6715,10 +7275,10 @@ struct sqlite3_module { ** when the omit flag is true there is no guarantee that the constraint will ** not be checked again using byte code.)^ ** -** ^The idxNum and idxPtr values are recorded and passed into the +** ^The idxNum and idxStr values are recorded and passed into the ** [xFilter] method. -** ^[sqlite3_free()] is used to free idxPtr if and only if -** needToFreeIdxPtr is true. +** ^[sqlite3_free()] is used to free idxStr if and only if +** needToFreeIdxStr is true. ** ** ^The orderByConsumed means that output from [xFilter]/[xNext] will occur in ** the correct order to satisfy the ORDER BY clause so that no separate @@ -6726,17 +7286,17 @@ struct sqlite3_module { ** ** ^The estimatedCost value is an estimate of the cost of a particular ** strategy. A cost of N indicates that the cost of the strategy is similar -** to a linear scan of an SQLite table with N rows. A cost of log(N) +** to a linear scan of an SQLite table with N rows. A cost of log(N) ** indicates that the expense of the operation is similar to that of a ** binary search on a unique indexed field of an SQLite table with N rows. ** ** ^The estimatedRows value is an estimate of the number of rows that ** will be returned by the strategy. ** -** The xBestIndex method may optionally populate the idxFlags field with a +** The xBestIndex method may optionally populate the idxFlags field with a ** mask of SQLITE_INDEX_SCAN_* flags. Currently there is only one such flag - ** SQLITE_INDEX_SCAN_UNIQUE. If the xBestIndex method sets this flag, SQLite -** assumes that the strategy may visit at most one row. +** assumes that the strategy may visit at most one row. ** ** Additionally, if xBestIndex sets the SQLITE_INDEX_SCAN_UNIQUE flag, then ** SQLite also assumes that if a call to the xUpdate() method is made as @@ -6749,14 +7309,14 @@ struct sqlite3_module { ** the xUpdate method are automatically rolled back by SQLite. ** ** IMPORTANT: The estimatedRows field was added to the sqlite3_index_info -** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). +** structure for SQLite [version 3.8.2] ([dateof:3.8.2]). ** If a virtual table extension is -** used with an SQLite version earlier than 3.8.2, the results of attempting -** to read or write the estimatedRows field are undefined (but are likely +** used with an SQLite version earlier than 3.8.2, the results of attempting +** to read or write the estimatedRows field are undefined (but are likely ** to include crashing the application). The estimatedRows field should ** therefore only be used if [sqlite3_libversion_number()] returns a ** value greater than or equal to 3008002. Similarly, the idxFlags field -** was added for [version 3.9.0] ([dateof:3.9.0]). +** was added for [version 3.9.0] ([dateof:3.9.0]). ** It may therefore only be used if ** sqlite3_libversion_number() returns a value greater than or equal to ** 3009000. @@ -6796,7 +7356,7 @@ struct sqlite3_index_info { /* ** CAPI3REF: Virtual Table Scan Flags ** -** Virtual table implementations are allowed to set the +** Virtual table implementations are allowed to set the ** [sqlite3_index_info].idxFlags field to some combination of ** these bits. */ @@ -6807,24 +7367,56 @@ struct sqlite3_index_info { ** ** These macros define the allowed values for the ** [sqlite3_index_info].aConstraint[].op field. Each value represents -** an operator that is part of a constraint term in the wHERE clause of +** an operator that is part of a constraint term in the WHERE clause of ** a query that uses a [virtual table]. -*/ -#define SQLITE_INDEX_CONSTRAINT_EQ 2 -#define SQLITE_INDEX_CONSTRAINT_GT 4 -#define SQLITE_INDEX_CONSTRAINT_LE 8 -#define SQLITE_INDEX_CONSTRAINT_LT 16 -#define SQLITE_INDEX_CONSTRAINT_GE 32 -#define SQLITE_INDEX_CONSTRAINT_MATCH 64 -#define SQLITE_INDEX_CONSTRAINT_LIKE 65 -#define SQLITE_INDEX_CONSTRAINT_GLOB 66 -#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 -#define SQLITE_INDEX_CONSTRAINT_NE 68 -#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 -#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 -#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 -#define SQLITE_INDEX_CONSTRAINT_IS 72 -#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 +** +** ^The left-hand operand of the operator is given by the corresponding +** aConstraint[].iColumn field. ^An iColumn of -1 indicates the left-hand +** operand is the rowid. +** The SQLITE_INDEX_CONSTRAINT_LIMIT and SQLITE_INDEX_CONSTRAINT_OFFSET +** operators have no left-hand operand, and so for those operators the +** corresponding aConstraint[].iColumn is meaningless and should not be +** used. +** +** All operator values from SQLITE_INDEX_CONSTRAINT_FUNCTION through +** value 255 are reserved to represent functions that are overloaded +** by the [xFindFunction|xFindFunction method] of the virtual table +** implementation. +** +** The right-hand operands for each constraint might be accessible using +** the [sqlite3_vtab_rhs_value()] interface. Usually the right-hand +** operand is only available if it appears as a single constant literal +** in the input SQL. If the right-hand operand is another column or an +** expression (even a constant expression) or a parameter, then the +** sqlite3_vtab_rhs_value() probably will not be able to extract it. +** ^The SQLITE_INDEX_CONSTRAINT_ISNULL and +** SQLITE_INDEX_CONSTRAINT_ISNOTNULL operators have no right-hand operand +** and hence calls to sqlite3_vtab_rhs_value() for those operators will +** always return SQLITE_NOTFOUND. +** +** The collating sequence to be used for comparison can be found using +** the [sqlite3_vtab_collation()] interface. For most real-world virtual +** tables, the collating sequence of constraints does not matter (for example +** because the constraints are numeric) and so the sqlite3_vtab_collation() +** interface is not commonly needed. +*/ +#define SQLITE_INDEX_CONSTRAINT_EQ 2 +#define SQLITE_INDEX_CONSTRAINT_GT 4 +#define SQLITE_INDEX_CONSTRAINT_LE 8 +#define SQLITE_INDEX_CONSTRAINT_LT 16 +#define SQLITE_INDEX_CONSTRAINT_GE 32 +#define SQLITE_INDEX_CONSTRAINT_MATCH 64 +#define SQLITE_INDEX_CONSTRAINT_LIKE 65 +#define SQLITE_INDEX_CONSTRAINT_GLOB 66 +#define SQLITE_INDEX_CONSTRAINT_REGEXP 67 +#define SQLITE_INDEX_CONSTRAINT_NE 68 +#define SQLITE_INDEX_CONSTRAINT_ISNOT 69 +#define SQLITE_INDEX_CONSTRAINT_ISNOTNULL 70 +#define SQLITE_INDEX_CONSTRAINT_ISNULL 71 +#define SQLITE_INDEX_CONSTRAINT_IS 72 +#define SQLITE_INDEX_CONSTRAINT_LIMIT 73 +#define SQLITE_INDEX_CONSTRAINT_OFFSET 74 +#define SQLITE_INDEX_CONSTRAINT_FUNCTION 150 /* ** CAPI3REF: Register A Virtual Table Implementation @@ -6836,7 +7428,7 @@ struct sqlite3_index_info { ** preexisting [virtual table] for the module. ** ** ^The module name is registered on the [database connection] specified -** by the first parameter. ^The name of the module is given by the +** by the first parameter. ^The name of the module is given by the ** second parameter. ^The third parameter is a pointer to ** the implementation of the [virtual table module]. ^The fourth ** parameter is an arbitrary client data pointer that is passed through @@ -6853,7 +7445,7 @@ struct sqlite3_index_info { ** destructor. ** ** ^If the third parameter (the pointer to the sqlite3_module object) is -** NULL then no new module is create and any existing modules with the +** NULL then no new module is created and any existing modules with the ** same name are dropped. ** ** See also: [sqlite3_drop_modules()] @@ -6951,7 +7543,7 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); ** METHOD: sqlite3 ** ** ^(Virtual tables can provide alternative implementations of functions -** using the [xFindFunction] method of the [virtual table module]. +** using the [xFindFunction] method of the [virtual table module]. ** But global versions of those functions ** must exist in order to be overloaded.)^ ** @@ -6965,16 +7557,6 @@ SQLITE_API int sqlite3_declare_vtab(sqlite3*, const char *zSQL); */ SQLITE_API int sqlite3_overload_function(sqlite3*, const char *zFuncName, int nArg); -/* -** The interface to the virtual-table mechanism defined above (back up -** to a comment remarkably similar to this one) is currently considered -** to be experimental. The interface might change in incompatible ways. -** If this is a problem for you, do not use the interface at this time. -** -** When the virtual-table mechanism stabilizes, we will declare the -** interface fixed, support it indefinitely, and remove this comment. -*/ - /* ** CAPI3REF: A Handle To An Open BLOB ** KEYWORDS: {BLOB handle} {BLOB handles} @@ -7002,7 +7584,7 @@ typedef struct sqlite3_blob sqlite3_blob; ** SELECT zColumn FROM zDb.zTable WHERE [rowid] = iRow; ** )^ ** -** ^(Parameter zDb is not the filename that contains the database, but +** ^(Parameter zDb is not the filename that contains the database, but ** rather the symbolic name of the database. For attached databases, this is ** the name that appears after the AS keyword in the [ATTACH] statement. ** For the main database file, the database name is "main". For TEMP @@ -7015,28 +7597,28 @@ typedef struct sqlite3_blob sqlite3_blob; ** ^(On success, [SQLITE_OK] is returned and the new [BLOB handle] is stored ** in *ppBlob. Otherwise an [error code] is returned and, unless the error ** code is SQLITE_MISUSE, *ppBlob is set to NULL.)^ ^This means that, provided -** the API is not misused, it is always safe to call [sqlite3_blob_close()] +** the API is not misused, it is always safe to call [sqlite3_blob_close()] ** on *ppBlob after this function it returns. ** ** This function fails with SQLITE_ERROR if any of the following are true: **
      -**
    • ^(Database zDb does not exist)^, -**
    • ^(Table zTable does not exist within database zDb)^, -**
    • ^(Table zTable is a WITHOUT ROWID table)^, +**
    • ^(Database zDb does not exist)^, +**
    • ^(Table zTable does not exist within database zDb)^, +**
    • ^(Table zTable is a WITHOUT ROWID table)^, **
    • ^(Column zColumn does not exist)^, **
    • ^(Row iRow is not present in the table)^, **
    • ^(The specified column of row iRow contains a value that is not ** a TEXT or BLOB value)^, -**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE +**
    • ^(Column zColumn is part of an index, PRIMARY KEY or UNIQUE ** constraint and the blob is being opened for read/write access)^, -**
    • ^([foreign key constraints | Foreign key constraints] are enabled, +**
    • ^([foreign key constraints | Foreign key constraints] are enabled, ** column zColumn is part of a [child key] definition and the blob is ** being opened for read/write access)^. **
    ** -** ^Unless it returns SQLITE_MISUSE, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless it returns SQLITE_MISUSE, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** A BLOB referenced by sqlite3_blob_open() may be read using the ** [sqlite3_blob_read()] interface and modified by using @@ -7062,7 +7644,7 @@ typedef struct sqlite3_blob sqlite3_blob; ** blob. ** ** ^The [sqlite3_bind_zeroblob()] and [sqlite3_result_zeroblob()] interfaces -** and the built-in [zeroblob] SQL function may be used to create a +** and the built-in [zeroblob] SQL function may be used to create a ** zero-filled blob to read or write using the incremental-blob interface. ** ** To avoid a resource leak, every open [BLOB handle] should eventually @@ -7112,7 +7694,7 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** DESTRUCTOR: sqlite3_blob ** ** ^This function closes an open [BLOB handle]. ^(The BLOB handle is closed -** unconditionally. Even if this routine returns an error code, the +** unconditionally. Even if this routine returns an error code, the ** handle is still closed.)^ ** ** ^If the blob handle being closed was opened for read-write access, and if @@ -7122,10 +7704,10 @@ SQLITE_API int sqlite3_blob_reopen(sqlite3_blob *, sqlite3_int64); ** code is returned and the transaction rolled back. ** ** Calling this function with an argument that is not a NULL pointer or an -** open blob handle results in undefined behaviour. ^Calling this routine -** with a null pointer (such as would be returned by a failed call to +** open blob handle results in undefined behaviour. ^Calling this routine +** with a null pointer (such as would be returned by a failed call to ** [sqlite3_blob_open()]) is a harmless no-op. ^Otherwise, if this function -** is passed a valid open blob handle, the values returned by the +** is passed a valid open blob handle, the values returned by the ** sqlite3_errcode() and sqlite3_errmsg() functions are set before returning. */ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); @@ -7134,7 +7716,7 @@ SQLITE_API int sqlite3_blob_close(sqlite3_blob *); ** CAPI3REF: Return The Size Of An Open BLOB ** METHOD: sqlite3_blob ** -** ^Returns the size in bytes of the BLOB accessible via the +** ^Returns the size in bytes of the BLOB accessible via the ** successfully opened [BLOB handle] in its only argument. ^The ** incremental blob I/O routines can only read or overwriting existing ** blob content; they cannot change the size of a blob. @@ -7185,9 +7767,9 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** ** ^(On success, sqlite3_blob_write() returns SQLITE_OK. ** Otherwise, an [error code] or an [extended error code] is returned.)^ -** ^Unless SQLITE_MISUSE is returned, this function sets the -** [database connection] error code and message accessible via -** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. +** ^Unless SQLITE_MISUSE is returned, this function sets the +** [database connection] error code and message accessible via +** [sqlite3_errcode()] and [sqlite3_errmsg()] and related functions. ** ** ^If the [BLOB handle] passed as the first argument was not opened for ** writing (the flags parameter to [sqlite3_blob_open()] was zero), @@ -7196,9 +7778,9 @@ SQLITE_API int sqlite3_blob_read(sqlite3_blob *, void *Z, int N, int iOffset); ** This function may only modify the contents of the BLOB; it is ** not possible to increase the size of a BLOB using this API. ** ^If offset iOffset is less than N bytes from the end of the BLOB, -** [SQLITE_ERROR] is returned and no data is written. The size of the -** BLOB (and hence the maximum value of N+iOffset) can be determined -** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less +** [SQLITE_ERROR] is returned and no data is written. The size of the +** BLOB (and hence the maximum value of N+iOffset) can be determined +** using the [sqlite3_blob_bytes()] interface. ^If N or iOffset are less ** than zero [SQLITE_ERROR] is returned and no data is written. ** ** ^An attempt to write to an expired [BLOB handle] fails with an @@ -7292,7 +7874,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); **
      **
    • SQLITE_MUTEX_FAST **
    • SQLITE_MUTEX_RECURSIVE -**
    • SQLITE_MUTEX_STATIC_MASTER +**
    • SQLITE_MUTEX_STATIC_MAIN **
    • SQLITE_MUTEX_STATIC_MEM **
    • SQLITE_MUTEX_STATIC_OPEN **
    • SQLITE_MUTEX_STATIC_PRNG @@ -7350,7 +7932,7 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** ^(Some systems (for example, Windows 95) do not support the operation ** implemented by sqlite3_mutex_try(). On those systems, sqlite3_mutex_try() ** will always return SQLITE_BUSY. The SQLite core only ever uses -** sqlite3_mutex_try() as an optimization so this is acceptable +** sqlite3_mutex_try() as an optimization so this is acceptable ** behavior.)^ ** ** ^The sqlite3_mutex_leave() routine exits a mutex that was @@ -7358,9 +7940,9 @@ SQLITE_API int sqlite3_vfs_unregister(sqlite3_vfs*); ** is undefined if the mutex is not currently entered by the ** calling thread or is not currently allocated. ** -** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), or -** sqlite3_mutex_leave() is a NULL pointer, then all three routines -** behave as no-ops. +** ^If the argument to sqlite3_mutex_enter(), sqlite3_mutex_try(), +** sqlite3_mutex_leave(), or sqlite3_mutex_free() is a NULL pointer, +** then any of the four routines behaves as a no-op. ** ** See also: [sqlite3_mutex_held()] and [sqlite3_mutex_notheld()]. */ @@ -7494,7 +8076,7 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); */ #define SQLITE_MUTEX_FAST 0 #define SQLITE_MUTEX_RECURSIVE 1 -#define SQLITE_MUTEX_STATIC_MASTER 2 +#define SQLITE_MUTEX_STATIC_MAIN 2 #define SQLITE_MUTEX_STATIC_MEM 3 /* sqlite3_malloc() */ #define SQLITE_MUTEX_STATIC_MEM2 4 /* NOT USED */ #define SQLITE_MUTEX_STATIC_OPEN 4 /* sqlite3BtreeOpen() */ @@ -7509,11 +8091,15 @@ SQLITE_API int sqlite3_mutex_notheld(sqlite3_mutex*); #define SQLITE_MUTEX_STATIC_VFS2 12 /* For use by extension VFS */ #define SQLITE_MUTEX_STATIC_VFS3 13 /* For use by application VFS */ +/* Legacy compatibility: */ +#define SQLITE_MUTEX_STATIC_MASTER 2 + + /* ** CAPI3REF: Retrieve the mutex for a database connection ** METHOD: sqlite3 ** -** ^This interface returns a pointer the [sqlite3_mutex] object that +** ^This interface returns a pointer the [sqlite3_mutex] object that ** serializes access to the [database connection] given in the argument ** when the [threading mode] is Serialized. ** ^If the [threading mode] is Single-thread or Multi-thread then this @@ -7540,7 +8126,7 @@ SQLITE_API sqlite3_mutex *sqlite3_db_mutex(sqlite3*); ** method becomes the return value of this routine. ** ** A few opcodes for [sqlite3_file_control()] are handled directly -** by the SQLite core and never invoke the +** by the SQLite core and never invoke the ** sqlite3_io_methods.xFileControl method. ** ^The [SQLITE_FCNTL_FILE_POINTER] value for the op parameter causes ** a pointer to the underlying [sqlite3_file] object to be written into @@ -7604,7 +8190,7 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_PENDING_BYTE 11 #define SQLITE_TESTCTRL_ASSERT 12 #define SQLITE_TESTCTRL_ALWAYS 13 -#define SQLITE_TESTCTRL_RESERVE 14 +#define SQLITE_TESTCTRL_RESERVE 14 /* NOT USED */ #define SQLITE_TESTCTRL_OPTIMIZATIONS 15 #define SQLITE_TESTCTRL_ISKEYWORD 16 /* NOT USED */ #define SQLITE_TESTCTRL_SCRATCHMALLOC 17 /* NOT USED */ @@ -7622,12 +8208,17 @@ SQLITE_API int sqlite3_test_control(int op, ...); #define SQLITE_TESTCTRL_RESULT_INTREAL 27 #define SQLITE_TESTCTRL_PRNG_SEED 28 #define SQLITE_TESTCTRL_EXTRA_SCHEMA_CHECKS 29 -#define SQLITE_TESTCTRL_LAST 29 /* Largest TESTCTRL */ +#define SQLITE_TESTCTRL_SEEK_COUNT 30 +#define SQLITE_TESTCTRL_TRACEFLAGS 31 +#define SQLITE_TESTCTRL_TUNE 32 +#define SQLITE_TESTCTRL_LOGEST 33 +#define SQLITE_TESTCTRL_USELONGDOUBLE 34 +#define SQLITE_TESTCTRL_LAST 34 /* Largest TESTCTRL */ /* ** CAPI3REF: SQL Keyword Checking ** -** These routines provide access to the set of SQL language keywords +** These routines provide access to the set of SQL language keywords ** recognized by SQLite. Applications can uses these routines to determine ** whether or not a specific identifier needs to be escaped (for example, ** by enclosing in double-quotes) so as not to confuse the parser. @@ -7699,14 +8290,14 @@ typedef struct sqlite3_str sqlite3_str; ** ** ^The [sqlite3_str_new(D)] interface allocates and initializes ** a new [sqlite3_str] object. To avoid memory leaks, the object returned by -** [sqlite3_str_new()] must be freed by a subsequent call to +** [sqlite3_str_new()] must be freed by a subsequent call to ** [sqlite3_str_finish(X)]. ** ** ^The [sqlite3_str_new(D)] interface always returns a pointer to a ** valid [sqlite3_str] object, though in the event of an out-of-memory ** error the returned object might be a special singleton that will -** silently reject new text, always return SQLITE_NOMEM from -** [sqlite3_str_errcode()], always return 0 for +** silently reject new text, always return SQLITE_NOMEM from +** [sqlite3_str_errcode()], always return 0 for ** [sqlite3_str_length()], and always return NULL from ** [sqlite3_str_finish(X)]. It is always safe to use the value ** returned by [sqlite3_str_new(D)] as the sqlite3_str parameter @@ -7742,9 +8333,9 @@ SQLITE_API char *sqlite3_str_finish(sqlite3_str*); ** These interfaces add content to an sqlite3_str object previously obtained ** from [sqlite3_str_new()]. ** -** ^The [sqlite3_str_appendf(X,F,...)] and +** ^The [sqlite3_str_appendf(X,F,...)] and ** [sqlite3_str_vappendf(X,F,V)] interfaces uses the [built-in printf] -** functionality of SQLite to append formatted text onto the end of +** functionality of SQLite to append formatted text onto the end of ** [sqlite3_str] object X. ** ** ^The [sqlite3_str_append(X,S,N)] method appends exactly N bytes from string S @@ -7761,7 +8352,7 @@ SQLITE_API char *sqlite3_str_finish(sqlite3_str*); ** ^This method can be used, for example, to add whitespace indentation. ** ** ^The [sqlite3_str_reset(X)] method resets the string under construction -** inside [sqlite3_str] object X back to zero bytes in length. +** inside [sqlite3_str] object X back to zero bytes in length. ** ** These methods do not return a result code. ^If an error occurs, that fact ** is recorded in the [sqlite3_str] object and can be recovered by a @@ -7863,7 +8454,7 @@ SQLITE_API int sqlite3_status64( **
      This parameter records the largest memory allocation request ** handed to [sqlite3_malloc()] or [sqlite3_realloc()] (or their ** internal equivalents). Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
      )^ ** ** [[SQLITE_STATUS_MALLOC_COUNT]] ^(
      SQLITE_STATUS_MALLOC_COUNT
      @@ -7872,11 +8463,11 @@ SQLITE_API int sqlite3_status64( ** ** [[SQLITE_STATUS_PAGECACHE_USED]] ^(
      SQLITE_STATUS_PAGECACHE_USED
      **
      This parameter returns the number of pages used out of the -** [pagecache memory allocator] that was configured using +** [pagecache memory allocator] that was configured using ** [SQLITE_CONFIG_PAGECACHE]. The ** value returned is in pages, not in bytes.
      )^ ** -** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] +** [[SQLITE_STATUS_PAGECACHE_OVERFLOW]] ** ^(
      SQLITE_STATUS_PAGECACHE_OVERFLOW
      **
      This parameter returns the number of bytes of page cache ** allocation which could not be satisfied by the [SQLITE_CONFIG_PAGECACHE] @@ -7889,7 +8480,7 @@ SQLITE_API int sqlite3_status64( ** [[SQLITE_STATUS_PAGECACHE_SIZE]] ^(
      SQLITE_STATUS_PAGECACHE_SIZE
      **
      This parameter records the largest memory allocation request ** handed to the [pagecache memory allocator]. Only the value returned in the -** *pHighwater parameter to [sqlite3_status()] is of interest. +** *pHighwater parameter to [sqlite3_status()] is of interest. ** The value written into the *pCurrent parameter is undefined.
      )^ ** ** [[SQLITE_STATUS_SCRATCH_USED]]
      SQLITE_STATUS_SCRATCH_USED
      @@ -7902,7 +8493,7 @@ SQLITE_API int sqlite3_status64( **
      No longer used.
      ** ** [[SQLITE_STATUS_PARSER_STACK]] ^(
      SQLITE_STATUS_PARSER_STACK
      -**
      The *pHighwater parameter records the deepest parser stack. +**
      The *pHighwater parameter records the deepest parser stack. ** The *pCurrent value is undefined. The *pHighwater value is only ** meaningful if SQLite is compiled with [YYTRACKMAXSTACKDEPTH].
      )^ **
    @@ -7924,12 +8515,12 @@ SQLITE_API int sqlite3_status64( ** CAPI3REF: Database Connection Status ** METHOD: sqlite3 ** -** ^This interface is used to retrieve runtime status information +** ^This interface is used to retrieve runtime status information ** about a single [database connection]. ^The first argument is the ** database connection object to be interrogated. ^The second argument ** is an integer constant, taken from the set of ** [SQLITE_DBSTATUS options], that -** determines the parameter to interrogate. The set of +** determines the parameter to interrogate. The set of ** [SQLITE_DBSTATUS options] is likely ** to grow in future releases of SQLite. ** @@ -7964,7 +8555,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** checked out.)^ ** ** [[SQLITE_DBSTATUS_LOOKASIDE_HIT]] ^(
    SQLITE_DBSTATUS_LOOKASIDE_HIT
    -**
    This parameter returns the number of malloc attempts that were +**
    This parameter returns the number of malloc attempts that were ** satisfied using lookaside memory. Only the high-water value is meaningful; ** the current value is always zero.)^ ** @@ -7989,7 +8580,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** memory used by all pager caches associated with the database connection.)^ ** ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_USED is always 0. ** -** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] +** [[SQLITE_DBSTATUS_CACHE_USED_SHARED]] ** ^(
    SQLITE_DBSTATUS_CACHE_USED_SHARED
    **
    This parameter is similar to DBSTATUS_CACHE_USED, except that if a ** pager cache is shared between two or more connections the bytes of heap @@ -8004,7 +8595,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** [[SQLITE_DBSTATUS_SCHEMA_USED]] ^(
    SQLITE_DBSTATUS_SCHEMA_USED
    **
    This parameter returns the approximate number of bytes of heap ** memory used to store the schema for all databases associated -** with the connection - main, temp, and any [ATTACH]-ed databases.)^ +** with the connection - main, temp, and any [ATTACH]-ed databases.)^ ** ^The full amount of memory used by the schemas is reported, even if the ** schema memory is shared with other database connections due to ** [shared cache mode] being enabled. @@ -8019,13 +8610,13 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** ** [[SQLITE_DBSTATUS_CACHE_HIT]] ^(
    SQLITE_DBSTATUS_CACHE_HIT
    **
    This parameter returns the number of pager cache hits that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_HIT ** is always 0. **
    ** ** [[SQLITE_DBSTATUS_CACHE_MISS]] ^(
    SQLITE_DBSTATUS_CACHE_MISS
    **
    This parameter returns the number of pager cache misses that have -** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS +** occurred.)^ ^The highwater mark associated with SQLITE_DBSTATUS_CACHE_MISS ** is always 0. **
    ** @@ -8083,7 +8674,7 @@ SQLITE_API int sqlite3_db_status(sqlite3*, int op, int *pCur, int *pHiwtr, int r ** statements. For example, if the number of table steps greatly exceeds ** the number of table searches or result rows, that would tend to indicate ** that the prepared statement is using a full table scan rather than -** an index. +** an index. ** ** ^(This interface is used to retrieve and reset counter values from ** a [prepared statement]. The first argument is the prepared statement @@ -8110,7 +8701,7 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** [[SQLITE_STMTSTATUS_FULLSCAN_STEP]]
    SQLITE_STMTSTATUS_FULLSCAN_STEP
    **
    ^This is the number of times that SQLite has stepped forward in ** a table as part of a full table scan. Large numbers for this counter -** may indicate opportunities for performance improvement through +** may indicate opportunities for performance improvement through ** careful use of indices.
    ** ** [[SQLITE_STMTSTATUS_SORT]]
    SQLITE_STMTSTATUS_SORT
    @@ -8128,14 +8719,14 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** [[SQLITE_STMTSTATUS_VM_STEP]]
    SQLITE_STMTSTATUS_VM_STEP
    **
    ^This is the number of virtual machine operations executed ** by the prepared statement if that number is less than or equal -** to 2147483647. The number of virtual machine operations can be +** to 2147483647. The number of virtual machine operations can be ** used as a proxy for the total work done by the prepared statement. ** If the number of virtual machine operations exceeds 2147483647 ** then the value returned by this statement status code is undefined. ** ** [[SQLITE_STMTSTATUS_REPREPARE]]
    SQLITE_STMTSTATUS_REPREPARE
    **
    ^This is the number of times that the prepare statement has been -** automatically regenerated due to schema changes or changes to +** automatically regenerated due to schema changes or changes to ** [bound parameters] that might affect the query plan. ** ** [[SQLITE_STMTSTATUS_RUN]]
    SQLITE_STMTSTATUS_RUN
    @@ -8145,6 +8736,16 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); ** The counter is incremented on the first [sqlite3_step()] call of each ** cycle. ** +** [[SQLITE_STMTSTATUS_FILTER_MISS]] +** [[SQLITE_STMTSTATUS_FILTER HIT]] +**
    SQLITE_STMTSTATUS_FILTER_HIT
    +** SQLITE_STMTSTATUS_FILTER_MISS
    +**
    ^SQLITE_STMTSTATUS_FILTER_HIT is the number of times that a join +** step was bypassed because a Bloom filter returned not-found. The +** corresponding SQLITE_STMTSTATUS_FILTER_MISS value is the number of +** times that the Bloom filter returned a find, and thus the join step +** had to be processed as normal. +** ** [[SQLITE_STMTSTATUS_MEMUSED]]
    SQLITE_STMTSTATUS_MEMUSED
    **
    ^This is the approximate number of bytes of heap memory ** used to store the prepared statement. ^This value is not actually @@ -8159,6 +8760,8 @@ SQLITE_API int sqlite3_stmt_status(sqlite3_stmt*, int op,int resetFlg); #define SQLITE_STMTSTATUS_VM_STEP 4 #define SQLITE_STMTSTATUS_REPREPARE 5 #define SQLITE_STMTSTATUS_RUN 6 +#define SQLITE_STMTSTATUS_FILTER_MISS 7 +#define SQLITE_STMTSTATUS_FILTER_HIT 8 #define SQLITE_STMTSTATUS_MEMUSED 99 /* @@ -8195,15 +8798,15 @@ struct sqlite3_pcache_page { ** KEYWORDS: {page cache} ** ** ^(The [sqlite3_config]([SQLITE_CONFIG_PCACHE2], ...) interface can -** register an alternative page cache implementation by passing in an +** register an alternative page cache implementation by passing in an ** instance of the sqlite3_pcache_methods2 structure.)^ -** In many applications, most of the heap memory allocated by +** In many applications, most of the heap memory allocated by ** SQLite is used for the page cache. -** By implementing a +** By implementing a ** custom page cache using this API, an application can better control -** the amount of memory consumed by SQLite, the way in which -** that memory is allocated and released, and the policies used to -** determine exactly which parts of a database file are cached and for +** the amount of memory consumed by SQLite, the way in which +** that memory is allocated and released, and the policies used to +** determine exactly which parts of a database file are cached and for ** how long. ** ** The alternative page cache mechanism is an @@ -8216,19 +8819,19 @@ struct sqlite3_pcache_page { ** [sqlite3_config()] returns.)^ ** ** [[the xInit() page cache method]] -** ^(The xInit() method is called once for each effective +** ^(The xInit() method is called once for each effective ** call to [sqlite3_initialize()])^ ** (usually only once during the lifetime of the process). ^(The xInit() ** method is passed a copy of the sqlite3_pcache_methods2.pArg value.)^ -** The intent of the xInit() method is to set up global data structures -** required by the custom page cache implementation. -** ^(If the xInit() method is NULL, then the +** The intent of the xInit() method is to set up global data structures +** required by the custom page cache implementation. +** ^(If the xInit() method is NULL, then the ** built-in default page cache is used instead of the application defined ** page cache.)^ ** ** [[the xShutdown() page cache method]] ** ^The xShutdown() method is called by [sqlite3_shutdown()]. -** It can be used to clean up +** It can be used to clean up ** any outstanding resources before process shutdown, if required. ** ^The xShutdown() method may be NULL. ** @@ -8247,7 +8850,7 @@ struct sqlite3_pcache_page { ** though this is not guaranteed. ^The ** first parameter, szPage, is the size in bytes of the pages that must ** be allocated by the cache. ^szPage will always a power of two. ^The -** second parameter szExtra is a number of bytes of extra storage +** second parameter szExtra is a number of bytes of extra storage ** associated with each page cache entry. ^The szExtra parameter will ** a number less than 250. SQLite will use the ** extra szExtra bytes on each page to store metadata about the underlying @@ -8260,7 +8863,7 @@ struct sqlite3_pcache_page { ** it is purely advisory. ^On a cache where bPurgeable is false, SQLite will ** never invoke xUnpin() except to deliberately delete a page. ** ^In other words, calls to xUnpin() on a cache with bPurgeable set to -** false will always have the "discard" flag set to true. +** false will always have the "discard" flag set to true. ** ^Hence, a cache created with bPurgeable false will ** never contain any unpinned pages. ** @@ -8275,12 +8878,12 @@ struct sqlite3_pcache_page { ** [[the xPagecount() page cache methods]] ** The xPagecount() method must return the number of pages currently ** stored in the cache, both pinned and unpinned. -** +** ** [[the xFetch() page cache methods]] -** The xFetch() method locates a page in the cache and returns a pointer to +** The xFetch() method locates a page in the cache and returns a pointer to ** an sqlite3_pcache_page object associated with that page, or a NULL pointer. ** The pBuf element of the returned sqlite3_pcache_page object will be a -** pointer to a buffer of szPage bytes used to store the content of a +** pointer to a buffer of szPage bytes used to store the content of a ** single database page. The pExtra element of sqlite3_pcache_page will be ** a pointer to the szExtra bytes of extra storage that SQLite has requested ** for each entry in the page cache. @@ -8319,8 +8922,8 @@ struct sqlite3_pcache_page { ** page cache implementation. ^The page cache implementation ** may choose to evict unpinned pages at any time. ** -** The cache must not perform any reference counting. A single -** call to xUnpin() unpins the page regardless of the number of prior calls +** The cache must not perform any reference counting. A single +** call to xUnpin() unpins the page regardless of the number of prior calls ** to xFetch(). ** ** [[the xRekey() page cache methods]] @@ -8360,7 +8963,7 @@ struct sqlite3_pcache_methods2 { int (*xPagecount)(sqlite3_pcache*); sqlite3_pcache_page *(*xFetch)(sqlite3_pcache*, unsigned key, int createFlag); void (*xUnpin)(sqlite3_pcache*, sqlite3_pcache_page*, int discard); - void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, + void (*xRekey)(sqlite3_pcache*, sqlite3_pcache_page*, unsigned oldKey, unsigned newKey); void (*xTruncate)(sqlite3_pcache*, unsigned iLimit); void (*xDestroy)(sqlite3_pcache*); @@ -8405,7 +9008,7 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** The backup API copies the content of one database into another. ** It is useful either for creating backups of databases or -** for copying in-memory databases to or from persistent files. +** for copying in-memory databases to or from persistent files. ** ** See Also: [Using the SQLite Online Backup API] ** @@ -8416,36 +9019,36 @@ typedef struct sqlite3_backup sqlite3_backup; ** ^Thus, the backup may be performed on a live source database without ** preventing other database connections from ** reading or writing to the source database while the backup is underway. -** -** ^(To perform a backup operation: +** +** ^(To perform a backup operation: **
      **
    1. sqlite3_backup_init() is called once to initialize the -** backup, -**
    2. sqlite3_backup_step() is called one or more times to transfer +** backup, +**
    3. sqlite3_backup_step() is called one or more times to transfer ** the data between the two databases, and finally -**
    4. sqlite3_backup_finish() is called to release all resources -** associated with the backup operation. +**
    5. sqlite3_backup_finish() is called to release all resources +** associated with the backup operation. **
    )^ ** There should be exactly one call to sqlite3_backup_finish() for each ** successful call to sqlite3_backup_init(). ** ** [[sqlite3_backup_init()]] sqlite3_backup_init() ** -** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the -** [database connection] associated with the destination database +** ^The D and N arguments to sqlite3_backup_init(D,N,S,M) are the +** [database connection] associated with the destination database ** and the database name, respectively. ** ^The database name is "main" for the main database, "temp" for the ** temporary database, or the name specified after the AS keyword in ** an [ATTACH] statement for an attached database. -** ^The S and M arguments passed to +** ^The S and M arguments passed to ** sqlite3_backup_init(D,N,S,M) identify the [database connection] ** and database name of the source database, respectively. ** ^The source and destination [database connections] (parameters S and D) ** must be different or else sqlite3_backup_init(D,N,S,M) will fail with ** an error. ** -** ^A call to sqlite3_backup_init() will fail, returning NULL, if -** there is already a read or read-write transaction open on the +** ^A call to sqlite3_backup_init() will fail, returning NULL, if +** there is already a read or read-write transaction open on the ** destination database. ** ** ^If an error occurs within sqlite3_backup_init(D,N,S,M), then NULL is @@ -8457,14 +9060,14 @@ typedef struct sqlite3_backup sqlite3_backup; ** ^A successful call to sqlite3_backup_init() returns a pointer to an ** [sqlite3_backup] object. ** ^The [sqlite3_backup] object may be used with the sqlite3_backup_step() and -** sqlite3_backup_finish() functions to perform the specified backup +** sqlite3_backup_finish() functions to perform the specified backup ** operation. ** ** [[sqlite3_backup_step()]] sqlite3_backup_step() ** -** ^Function sqlite3_backup_step(B,N) will copy up to N pages between +** ^Function sqlite3_backup_step(B,N) will copy up to N pages between ** the source and destination databases specified by [sqlite3_backup] object B. -** ^If N is negative, all remaining source pages are copied. +** ^If N is negative, all remaining source pages are copied. ** ^If sqlite3_backup_step(B,N) successfully copies N pages and there ** are still more pages to be copied, then the function returns [SQLITE_OK]. ** ^If sqlite3_backup_step(B,N) successfully finishes copying all pages @@ -8486,8 +9089,8 @@ typedef struct sqlite3_backup sqlite3_backup; ** ** ^If sqlite3_backup_step() cannot obtain a required file-system lock, then ** the [sqlite3_busy_handler | busy-handler function] -** is invoked (if one is specified). ^If the -** busy-handler returns non-zero before the lock is available, then +** is invoked (if one is specified). ^If the +** busy-handler returns non-zero before the lock is available, then ** [SQLITE_BUSY] is returned to the caller. ^In this case the call to ** sqlite3_backup_step() can be retried later. ^If the source ** [database connection] @@ -8495,15 +9098,15 @@ typedef struct sqlite3_backup sqlite3_backup; ** is called, then [SQLITE_LOCKED] is returned immediately. ^Again, in this ** case the call to sqlite3_backup_step() can be retried later on. ^(If ** [SQLITE_IOERR_ACCESS | SQLITE_IOERR_XXX], [SQLITE_NOMEM], or -** [SQLITE_READONLY] is returned, then -** there is no point in retrying the call to sqlite3_backup_step(). These -** errors are considered fatal.)^ The application must accept -** that the backup operation has failed and pass the backup operation handle +** [SQLITE_READONLY] is returned, then +** there is no point in retrying the call to sqlite3_backup_step(). These +** errors are considered fatal.)^ The application must accept +** that the backup operation has failed and pass the backup operation handle ** to the sqlite3_backup_finish() to release associated resources. ** ** ^The first call to sqlite3_backup_step() obtains an exclusive lock -** on the destination file. ^The exclusive lock is not released until either -** sqlite3_backup_finish() is called or the backup operation is complete +** on the destination file. ^The exclusive lock is not released until either +** sqlite3_backup_finish() is called or the backup operation is complete ** and sqlite3_backup_step() returns [SQLITE_DONE]. ^Every call to ** sqlite3_backup_step() obtains a [shared lock] on the source database that ** lasts for the duration of the sqlite3_backup_step() call. @@ -8512,18 +9115,18 @@ typedef struct sqlite3_backup sqlite3_backup; ** through the backup process. ^If the source database is modified by an ** external process or via a database connection other than the one being ** used by the backup operation, then the backup will be automatically -** restarted by the next call to sqlite3_backup_step(). ^If the source +** restarted by the next call to sqlite3_backup_step(). ^If the source ** database is modified by the using the same database connection as is used ** by the backup operation, then the backup database is automatically ** updated at the same time. ** ** [[sqlite3_backup_finish()]] sqlite3_backup_finish() ** -** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the +** When sqlite3_backup_step() has returned [SQLITE_DONE], or when the ** application wishes to abandon the backup operation, the application ** should destroy the [sqlite3_backup] by passing it to sqlite3_backup_finish(). ** ^The sqlite3_backup_finish() interfaces releases all -** resources associated with the [sqlite3_backup] object. +** resources associated with the [sqlite3_backup] object. ** ^If sqlite3_backup_step() has not yet returned [SQLITE_DONE], then any ** active write-transaction on the destination database is rolled back. ** The [sqlite3_backup] object is invalid @@ -8563,23 +9166,23 @@ typedef struct sqlite3_backup sqlite3_backup; ** connections, then the source database connection may be used concurrently ** from within other threads. ** -** However, the application must guarantee that the destination -** [database connection] is not passed to any other API (by any thread) after +** However, the application must guarantee that the destination +** [database connection] is not passed to any other API (by any thread) after ** sqlite3_backup_init() is called and before the corresponding call to ** sqlite3_backup_finish(). SQLite does not currently check to see ** if the application incorrectly accesses the destination [database connection] ** and so no error code is reported, but the operations may malfunction ** nevertheless. Use of the destination database connection while a -** backup is in progress might also also cause a mutex deadlock. +** backup is in progress might also cause a mutex deadlock. ** ** If running in [shared cache mode], the application must ** guarantee that the shared cache used by the destination database ** is not accessed while the backup is running. In practice this means -** that the application must guarantee that the disk file being +** that the application must guarantee that the disk file being ** backed up to is not accessed by any connection within the process, ** not just the specific connection that was passed to sqlite3_backup_init(). ** -** The [sqlite3_backup] object itself is partially threadsafe. Multiple +** The [sqlite3_backup] object itself is partially threadsafe. Multiple ** threads may safely make multiple concurrent calls to sqlite3_backup_step(). ** However, the sqlite3_backup_remaining() and sqlite3_backup_pagecount() ** APIs are not strictly speaking threadsafe. If they are invoked at the @@ -8604,8 +9207,8 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ^When running in shared-cache mode, a database operation may fail with ** an [SQLITE_LOCKED] error if the required locks on the shared-cache or ** individual tables within the shared-cache cannot be obtained. See -** [SQLite Shared-Cache Mode] for a description of shared-cache locking. -** ^This API may be used to register a callback that SQLite will invoke +** [SQLite Shared-Cache Mode] for a description of shared-cache locking. +** ^This API may be used to register a callback that SQLite will invoke ** when the connection currently holding the required lock relinquishes it. ** ^This API is only available if the library was compiled with the ** [SQLITE_ENABLE_UNLOCK_NOTIFY] C-preprocessor symbol defined. @@ -8613,14 +9216,14 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** See Also: [Using the SQLite Unlock Notification Feature]. ** ** ^Shared-cache locks are released when a database connection concludes -** its current transaction, either by committing it or rolling it back. +** its current transaction, either by committing it or rolling it back. ** ** ^When a connection (known as the blocked connection) fails to obtain a ** shared-cache lock and SQLITE_LOCKED is returned to the caller, the ** identity of the database connection (the blocking connection) that -** has locked the required resource is stored internally. ^After an +** has locked the required resource is stored internally. ^After an ** application receives an SQLITE_LOCKED error, it may call the -** sqlite3_unlock_notify() method with the blocked connection handle as +** sqlite3_unlock_notify() method with the blocked connection handle as ** the first argument to register for a callback that will be invoked ** when the blocking connections current transaction is concluded. ^The ** callback is invoked from within the [sqlite3_step] or [sqlite3_close] @@ -8634,16 +9237,16 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** ^If the blocked connection is attempting to obtain a write-lock on a ** shared-cache table, and more than one other connection currently holds -** a read-lock on the same table, then SQLite arbitrarily selects one of +** a read-lock on the same table, then SQLite arbitrarily selects one of ** the other connections to use as the blocking connection. ** -** ^(There may be at most one unlock-notify callback registered by a +** ^(There may be at most one unlock-notify callback registered by a ** blocked connection. If sqlite3_unlock_notify() is called when the ** blocked connection already has a registered unlock-notify callback, ** then the new callback replaces the old.)^ ^If sqlite3_unlock_notify() is ** called with a NULL pointer as its second argument, then any existing -** unlock-notify callback is canceled. ^The blocked connections -** unlock-notify callback may also be canceled by closing the blocked +** unlock-notify callback is cancelled. ^The blocked connections +** unlock-notify callback may also be cancelled by closing the blocked ** connection using [sqlite3_close()]. ** ** The unlock-notify callback is not reentrant. If an application invokes @@ -8655,7 +9258,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** Callback Invocation Details ** -** When an unlock-notify callback is registered, the application provides a +** When an unlock-notify callback is registered, the application provides a ** single void* pointer that is passed to the callback when it is invoked. ** However, the signature of the callback function allows SQLite to pass ** it an array of void* context pointers. The first argument passed to @@ -8668,12 +9271,12 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** same callback function, then instead of invoking the callback function ** multiple times, it is invoked once with the set of void* context pointers ** specified by the blocked connections bundled together into an array. -** This gives the application an opportunity to prioritize any actions +** This gives the application an opportunity to prioritize any actions ** related to the set of unblocked database connections. ** ** Deadlock Detection ** -** Assuming that after registering for an unlock-notify callback a +** Assuming that after registering for an unlock-notify callback a ** database waits for the callback to be issued before taking any further ** action (a reasonable assumption), then using this API may cause the ** application to deadlock. For example, if connection X is waiting for @@ -8696,7 +9299,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** ** The "DROP TABLE" Exception ** -** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost +** When a call to [sqlite3_step()] returns SQLITE_LOCKED, it is almost ** always appropriate to call sqlite3_unlock_notify(). There is however, ** one exception. When executing a "DROP TABLE" or "DROP INDEX" statement, ** SQLite checks if there are any currently executing SELECT statements @@ -8709,7 +9312,7 @@ SQLITE_API int sqlite3_backup_pagecount(sqlite3_backup *p); ** One way around this problem is to check the extended error code returned ** by an sqlite3_step() call. ^(If there is a blocking connection, then the ** extended error code is set to SQLITE_LOCKED_SHAREDCACHE. Otherwise, in -** the special "DROP TABLE/INDEX" case, the extended error code is just +** the special "DROP TABLE/INDEX" case, the extended error code is just ** SQLITE_LOCKED.)^ */ SQLITE_API int sqlite3_unlock_notify( @@ -8800,8 +9403,8 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** ^The [sqlite3_wal_hook()] function is used to register a callback that ** is invoked each time data is committed to a database in wal mode. ** -** ^(The callback is invoked by SQLite after the commit has taken place and -** the associated write-lock on the database released)^, so the implementation +** ^(The callback is invoked by SQLite after the commit has taken place and +** the associated write-lock on the database released)^, so the implementation ** may read, write or [checkpoint] the database as required. ** ** ^The first parameter passed to the callback function when it is invoked @@ -8820,15 +9423,16 @@ SQLITE_API void sqlite3_log(int iErrCode, const char *zFormat, ...); ** that does not correspond to any valid SQLite error code, the results ** are undefined. ** -** A single database handle may have at most a single write-ahead log callback +** A single database handle may have at most a single write-ahead log callback ** registered at one time. ^Calling [sqlite3_wal_hook()] replaces any -** previously registered write-ahead log callback. ^Note that the -** [sqlite3_wal_autocheckpoint()] interface and the +** previously registered write-ahead log callback. ^The return value is +** a copy of the third parameter from the previous call, if any, or 0. +** ^Note that the [sqlite3_wal_autocheckpoint()] interface and the ** [wal_autocheckpoint pragma] both invoke [sqlite3_wal_hook()] and will ** overwrite any prior [sqlite3_wal_hook()] settings. */ SQLITE_API void *sqlite3_wal_hook( - sqlite3*, + sqlite3*, int(*)(void *,sqlite3*,const char*,int), void* ); @@ -8841,7 +9445,7 @@ SQLITE_API void *sqlite3_wal_hook( ** [sqlite3_wal_hook()] that causes any database on [database connection] D ** to automatically [checkpoint] ** after committing a transaction if there are N or -** more frames in the [write-ahead log] file. ^Passing zero or +** more frames in the [write-ahead log] file. ^Passing zero or ** a negative value as the nFrame parameter disables automatic ** checkpoints entirely. ** @@ -8871,7 +9475,7 @@ SQLITE_API int sqlite3_wal_autocheckpoint(sqlite3 *db, int N); ** ^(The sqlite3_wal_checkpoint(D,X) is equivalent to ** [sqlite3_wal_checkpoint_v2](D,X,[SQLITE_CHECKPOINT_PASSIVE],0,0).)^ ** -** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the +** In brief, sqlite3_wal_checkpoint(D,X) causes the content in the ** [write-ahead log] for database X on [database connection] D to be ** transferred into the database file and for the write-ahead log to ** be reset. See the [checkpointing] documentation for addition @@ -8897,10 +9501,10 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** **
    **
    SQLITE_CHECKPOINT_PASSIVE
    -** ^Checkpoint as many frames as possible without waiting for any database -** readers or writers to finish, then sync the database file if all frames +** ^Checkpoint as many frames as possible without waiting for any database +** readers or writers to finish, then sync the database file if all frames ** in the log were checkpointed. ^The [busy-handler callback] -** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. +** is never invoked in the SQLITE_CHECKPOINT_PASSIVE mode. ** ^On the other hand, passive mode might leave the checkpoint unfinished ** if there are concurrent readers or writers. ** @@ -8914,9 +9518,9 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** **
    SQLITE_CHECKPOINT_RESTART
    ** ^This mode works the same way as SQLITE_CHECKPOINT_FULL with the addition -** that after checkpointing the log file it blocks (calls the +** that after checkpointing the log file it blocks (calls the ** [busy-handler callback]) -** until all readers are reading from the database file only. ^This ensures +** until all readers are reading from the database file only. ^This ensures ** that the next writer will restart the log file from the beginning. ** ^Like SQLITE_CHECKPOINT_FULL, this mode blocks new ** database writer attempts while it is pending, but does not impede readers. @@ -8938,31 +9542,31 @@ SQLITE_API int sqlite3_wal_checkpoint(sqlite3 *db, const char *zDb); ** truncated to zero bytes and so both *pnLog and *pnCkpt will be set to zero. ** ** ^All calls obtain an exclusive "checkpoint" lock on the database file. ^If -** any other process is running a checkpoint operation at the same time, the -** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a +** any other process is running a checkpoint operation at the same time, the +** lock cannot be obtained and SQLITE_BUSY is returned. ^Even if there is a ** busy-handler configured, it will not be invoked in this case. ** -** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the +** ^The SQLITE_CHECKPOINT_FULL, RESTART and TRUNCATE modes also obtain the ** exclusive "writer" lock on the database file. ^If the writer lock cannot be ** obtained immediately, and a busy-handler is configured, it is invoked and ** the writer lock retried until either the busy-handler returns 0 or the lock ** is successfully obtained. ^The busy-handler is also invoked while waiting for ** database readers as described above. ^If the busy-handler returns 0 before ** the writer lock is obtained or while waiting for database readers, the -** checkpoint operation proceeds from that point in the same way as -** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible +** checkpoint operation proceeds from that point in the same way as +** SQLITE_CHECKPOINT_PASSIVE - checkpointing as many frames as possible ** without blocking any further. ^SQLITE_BUSY is returned in this case. ** ** ^If parameter zDb is NULL or points to a zero length string, then the -** specified operation is attempted on all WAL databases [attached] to +** specified operation is attempted on all WAL databases [attached] to ** [database connection] db. In this case the -** values written to output parameters *pnLog and *pnCkpt are undefined. ^If -** an SQLITE_BUSY error is encountered when processing one or more of the -** attached WAL databases, the operation is still attempted on any remaining -** attached databases and SQLITE_BUSY is returned at the end. ^If any other -** error occurs while processing an attached database, processing is abandoned -** and the error code is returned to the caller immediately. ^If no error -** (SQLITE_BUSY or otherwise) is encountered while processing the attached +** values written to output parameters *pnLog and *pnCkpt are undefined. ^If +** an SQLITE_BUSY error is encountered when processing one or more of the +** attached WAL databases, the operation is still attempted on any remaining +** attached databases and SQLITE_BUSY is returned at the end. ^If any other +** error occurs while processing an attached database, processing is abandoned +** and the error code is returned to the caller immediately. ^If no error +** (SQLITE_BUSY or otherwise) is encountered while processing the attached ** databases, SQLITE_OK is returned. ** ** ^If database zDb is the name of an attached database that is not in WAL @@ -8997,7 +9601,7 @@ SQLITE_API int sqlite3_wal_checkpoint_v2( */ #define SQLITE_CHECKPOINT_PASSIVE 0 /* Do as much as possible w/o blocking */ #define SQLITE_CHECKPOINT_FULL 1 /* Wait for writers, then checkpoint */ -#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for for readers */ +#define SQLITE_CHECKPOINT_RESTART 2 /* Like FULL but wait for readers */ #define SQLITE_CHECKPOINT_TRUNCATE 3 /* Like RESTART but also truncate WAL */ /* @@ -9022,7 +9626,7 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); /* ** CAPI3REF: Virtual Table Configuration Options -** KEYWORDS: {virtual table configuration options} +** KEYWORDS: {virtual table configuration options} ** KEYWORDS: {virtual table configuration option} ** ** These macros define the various options to the @@ -9045,27 +9649,27 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); ** If X is non-zero, then the virtual table implementation guarantees ** that if [xUpdate] returns [SQLITE_CONSTRAINT], it will do so before ** any modifications to internal or persistent data structures have been made. -** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite +** If the [ON CONFLICT] mode is ABORT, FAIL, IGNORE or ROLLBACK, SQLite ** is able to roll back a statement or database transaction, and abandon -** or continue processing the current SQL statement as appropriate. +** or continue processing the current SQL statement as appropriate. ** If the ON CONFLICT mode is REPLACE and the [xUpdate] method returns ** [SQLITE_CONSTRAINT], SQLite handles this as if the ON CONFLICT mode ** had been ABORT. ** ** Virtual table implementations that are required to handle OR REPLACE -** must do so within the [xUpdate] method. If a call to the -** [sqlite3_vtab_on_conflict()] function indicates that the current ON -** CONFLICT policy is REPLACE, the virtual table implementation should +** must do so within the [xUpdate] method. If a call to the +** [sqlite3_vtab_on_conflict()] function indicates that the current ON +** CONFLICT policy is REPLACE, the virtual table implementation should ** silently replace the appropriate rows within the xUpdate callback and ** return SQLITE_OK. Or, if this is not possible, it may return -** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT +** SQLITE_CONSTRAINT, in which case SQLite falls back to OR ABORT ** constraint handling. **
    ** ** [[SQLITE_VTAB_DIRECTONLY]]
    SQLITE_VTAB_DIRECTONLY
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_DIRECTONLY) from within the -** the [xConnect] or [xCreate] methods of a [virtual table] implmentation +** the [xConnect] or [xCreate] methods of a [virtual table] implementation ** prohibits that virtual table from being used from within triggers and ** views. **
    @@ -9073,18 +9677,28 @@ SQLITE_API int sqlite3_vtab_config(sqlite3*, int op, ...); ** [[SQLITE_VTAB_INNOCUOUS]]
    SQLITE_VTAB_INNOCUOUS
    **
    Calls of the form ** [sqlite3_vtab_config](db,SQLITE_VTAB_INNOCUOUS) from within the -** the [xConnect] or [xCreate] methods of a [virtual table] implmentation +** the [xConnect] or [xCreate] methods of a [virtual table] implementation ** identify that virtual table as being safe to use from within triggers ** and views. Conceptually, the SQLITE_VTAB_INNOCUOUS tag means that the ** virtual table can do no serious harm even if it is controlled by a ** malicious hacker. Developers should avoid setting the SQLITE_VTAB_INNOCUOUS ** flag unless absolutely necessary. **
    +** +** [[SQLITE_VTAB_USES_ALL_SCHEMAS]]
    SQLITE_VTAB_USES_ALL_SCHEMAS
    +**
    Calls of the form +** [sqlite3_vtab_config](db,SQLITE_VTAB_USES_ALL_SCHEMA) from within the +** the [xConnect] or [xCreate] methods of a [virtual table] implementation +** instruct the query planner to begin at least a read transaction on +** all schemas ("main", "temp", and any ATTACH-ed databases) whenever the +** virtual table is used. +**
    **
    */ #define SQLITE_VTAB_CONSTRAINT_SUPPORT 1 #define SQLITE_VTAB_INNOCUOUS 2 #define SQLITE_VTAB_DIRECTONLY 3 +#define SQLITE_VTAB_USES_ALL_SCHEMAS 4 /* ** CAPI3REF: Determine The Virtual Table Conflict Policy @@ -9102,10 +9716,11 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); ** CAPI3REF: Determine If Virtual Table Column Access Is For UPDATE ** ** If the sqlite3_vtab_nochange(X) routine is called within the [xColumn] -** method of a [virtual table], then it returns true if and only if the +** method of a [virtual table], then it might return true if the ** column is being fetched as part of an UPDATE operation during which the -** column value will not change. Applications might use this to substitute -** a return value that is less expensive to compute and that the corresponding +** column value will not change. The virtual table implementation can use +** this hint as permission to substitute a return value that is less +** expensive to compute and that the corresponding ** [xUpdate] method understands as a "no-change" value. ** ** If the [xColumn] method calls sqlite3_vtab_nochange() and finds that @@ -9114,23 +9729,285 @@ SQLITE_API int sqlite3_vtab_on_conflict(sqlite3 *); ** any of the [sqlite3_result_int|sqlite3_result_xxxxx() interfaces]. ** In that case, [sqlite3_value_nochange(X)] will return true for the ** same column in the [xUpdate] method. +** +** The sqlite3_vtab_nochange() routine is an optimization. Virtual table +** implementations should continue to give a correct answer even if the +** sqlite3_vtab_nochange() interface were to always return false. In the +** current implementation, the sqlite3_vtab_nochange() interface does always +** returns false for the enhanced [UPDATE FROM] statement. */ SQLITE_API int sqlite3_vtab_nochange(sqlite3_context*); /* ** CAPI3REF: Determine The Collation For a Virtual Table Constraint +** METHOD: sqlite3_index_info ** ** This function may only be called from within a call to the [xBestIndex] -** method of a [virtual table]. +** method of a [virtual table]. This function returns a pointer to a string +** that is the name of the appropriate collation sequence to use for text +** comparisons on the constraint identified by its arguments. +** +** The first argument must be the pointer to the [sqlite3_index_info] object +** that is the first parameter to the xBestIndex() method. The second argument +** must be an index into the aConstraint[] array belonging to the +** sqlite3_index_info structure passed to xBestIndex. +** +** Important: +** The first parameter must be the same pointer that is passed into the +** xBestMethod() method. The first parameter may not be a pointer to a +** different [sqlite3_index_info] object, even an exact copy. ** -** The first argument must be the sqlite3_index_info object that is the -** first parameter to the xBestIndex() method. The second argument must be -** an index into the aConstraint[] array belonging to the sqlite3_index_info -** structure passed to xBestIndex. This function returns a pointer to a buffer -** containing the name of the collation sequence for the corresponding -** constraint. +** The return value is computed as follows: +** +**
      +**
    1. If the constraint comes from a WHERE clause expression that contains +** a [COLLATE operator], then the name of the collation specified by +** that COLLATE operator is returned. +**

    2. If there is no COLLATE operator, but the column that is the subject +** of the constraint specifies an alternative collating sequence via +** a [COLLATE clause] on the column definition within the CREATE TABLE +** statement that was passed into [sqlite3_declare_vtab()], then the +** name of that alternative collating sequence is returned. +**

    3. Otherwise, "BINARY" is returned. +**

    */ -SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_info*,int); +SQLITE_API const char *sqlite3_vtab_collation(sqlite3_index_info*,int); + +/* +** CAPI3REF: Determine if a virtual table query is DISTINCT +** METHOD: sqlite3_index_info +** +** This API may only be used from within an [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this +** interface from outside of xBestIndex() is undefined and probably harmful. +** +** ^The sqlite3_vtab_distinct() interface returns an integer between 0 and +** 3. The integer returned by sqlite3_vtab_distinct() +** gives the virtual table additional information about how the query +** planner wants the output to be ordered. As long as the virtual table +** can meet the ordering requirements of the query planner, it may set +** the "orderByConsumed" flag. +** +**
    1. +** ^If the sqlite3_vtab_distinct() interface returns 0, that means +** that the query planner needs the virtual table to return all rows in the +** sort order defined by the "nOrderBy" and "aOrderBy" fields of the +** [sqlite3_index_info] object. This is the default expectation. If the +** virtual table outputs all rows in sorted order, then it is always safe for +** the xBestIndex method to set the "orderByConsumed" flag, regardless of +** the return value from sqlite3_vtab_distinct(). +**

    2. +** ^(If the sqlite3_vtab_distinct() interface returns 1, that means +** that the query planner does not need the rows to be returned in sorted order +** as long as all rows with the same values in all columns identified by the +** "aOrderBy" field are adjacent.)^ This mode is used when the query planner +** is doing a GROUP BY. +**

    3. +** ^(If the sqlite3_vtab_distinct() interface returns 2, that means +** that the query planner does not need the rows returned in any particular +** order, as long as rows with the same values in all "aOrderBy" columns +** are adjacent.)^ ^(Furthermore, only a single row for each particular +** combination of values in the columns identified by the "aOrderBy" field +** needs to be returned.)^ ^It is always ok for two or more rows with the same +** values in all "aOrderBy" columns to be returned, as long as all such rows +** are adjacent. ^The virtual table may, if it chooses, omit extra rows +** that have the same value for all columns identified by "aOrderBy". +** ^However omitting the extra rows is optional. +** This mode is used for a DISTINCT query. +**

    4. +** ^(If the sqlite3_vtab_distinct() interface returns 3, that means +** that the query planner needs only distinct rows but it does need the +** rows to be sorted.)^ ^The virtual table implementation is free to omit +** rows that are identical in all aOrderBy columns, if it wants to, but +** it is not required to omit any rows. This mode is used for queries +** that have both DISTINCT and ORDER BY clauses. +**

    +** +** ^For the purposes of comparing virtual table output values to see if the +** values are same value for sorting purposes, two NULL values are considered +** to be the same. In other words, the comparison operator is "IS" +** (or "IS NOT DISTINCT FROM") and not "==". +** +** If a virtual table implementation is unable to meet the requirements +** specified above, then it must not set the "orderByConsumed" flag in the +** [sqlite3_index_info] object or an incorrect answer may result. +** +** ^A virtual table implementation is always free to return rows in any order +** it wants, as long as the "orderByConsumed" flag is not set. ^When the +** the "orderByConsumed" flag is unset, the query planner will add extra +** [bytecode] to ensure that the final results returned by the SQL query are +** ordered correctly. The use of the "orderByConsumed" flag and the +** sqlite3_vtab_distinct() interface is merely an optimization. ^Careful +** use of the sqlite3_vtab_distinct() interface and the "orderByConsumed" +** flag might help queries against a virtual table to run faster. Being +** overly aggressive and setting the "orderByConsumed" flag when it is not +** valid to do so, on the other hand, might cause SQLite to return incorrect +** results. +*/ +SQLITE_API int sqlite3_vtab_distinct(sqlite3_index_info*); + +/* +** CAPI3REF: Identify and handle IN constraints in xBestIndex +** +** This interface may only be used from within an +** [xBestIndex|xBestIndex() method] of a [virtual table] implementation. +** The result of invoking this interface from any other context is +** undefined and probably harmful. +** +** ^(A constraint on a virtual table of the form +** "[IN operator|column IN (...)]" is +** communicated to the xBestIndex method as a +** [SQLITE_INDEX_CONSTRAINT_EQ] constraint.)^ If xBestIndex wants to use +** this constraint, it must set the corresponding +** aConstraintUsage[].argvIndex to a positive integer. ^(Then, under +** the usual mode of handling IN operators, SQLite generates [bytecode] +** that invokes the [xFilter|xFilter() method] once for each value +** on the right-hand side of the IN operator.)^ Thus the virtual table +** only sees a single value from the right-hand side of the IN operator +** at a time. +** +** In some cases, however, it would be advantageous for the virtual +** table to see all values on the right-hand of the IN operator all at +** once. The sqlite3_vtab_in() interfaces facilitates this in two ways: +** +**
      +**
    1. +** ^A call to sqlite3_vtab_in(P,N,-1) will return true (non-zero) +** if and only if the [sqlite3_index_info|P->aConstraint][N] constraint +** is an [IN operator] that can be processed all at once. ^In other words, +** sqlite3_vtab_in() with -1 in the third argument is a mechanism +** by which the virtual table can ask SQLite if all-at-once processing +** of the IN operator is even possible. +** +**

    2. +** ^A call to sqlite3_vtab_in(P,N,F) with F==1 or F==0 indicates +** to SQLite that the virtual table does or does not want to process +** the IN operator all-at-once, respectively. ^Thus when the third +** parameter (F) is non-negative, this interface is the mechanism by +** which the virtual table tells SQLite how it wants to process the +** IN operator. +**

    +** +** ^The sqlite3_vtab_in(P,N,F) interface can be invoked multiple times +** within the same xBestIndex method call. ^For any given P,N pair, +** the return value from sqlite3_vtab_in(P,N,F) will always be the same +** within the same xBestIndex call. ^If the interface returns true +** (non-zero), that means that the constraint is an IN operator +** that can be processed all-at-once. ^If the constraint is not an IN +** operator or cannot be processed all-at-once, then the interface returns +** false. +** +** ^(All-at-once processing of the IN operator is selected if both of the +** following conditions are met: +** +**
      +**
    1. The P->aConstraintUsage[N].argvIndex value is set to a positive +** integer. This is how the virtual table tells SQLite that it wants to +** use the N-th constraint. +** +**

    2. The last call to sqlite3_vtab_in(P,N,F) for which F was +** non-negative had F>=1. +**

    )^ +** +** ^If either or both of the conditions above are false, then SQLite uses +** the traditional one-at-a-time processing strategy for the IN constraint. +** ^If both conditions are true, then the argvIndex-th parameter to the +** xFilter method will be an [sqlite3_value] that appears to be NULL, +** but which can be passed to [sqlite3_vtab_in_first()] and +** [sqlite3_vtab_in_next()] to find all values on the right-hand side +** of the IN constraint. +*/ +SQLITE_API int sqlite3_vtab_in(sqlite3_index_info*, int iCons, int bHandle); + +/* +** CAPI3REF: Find all elements on the right-hand side of an IN constraint. +** +** These interfaces are only useful from within the +** [xFilter|xFilter() method] of a [virtual table] implementation. +** The result of invoking these interfaces from any other context +** is undefined and probably harmful. +** +** The X parameter in a call to sqlite3_vtab_in_first(X,P) or +** sqlite3_vtab_in_next(X,P) should be one of the parameters to the +** xFilter method which invokes these routines, and specifically +** a parameter that was previously selected for all-at-once IN constraint +** processing use the [sqlite3_vtab_in()] interface in the +** [xBestIndex|xBestIndex method]. ^(If the X parameter is not +** an xFilter argument that was selected for all-at-once IN constraint +** processing, then these routines return [SQLITE_ERROR].)^ +** +** ^(Use these routines to access all values on the right-hand side +** of the IN constraint using code like the following: +** +**
    +**    for(rc=sqlite3_vtab_in_first(pList, &pVal);
    +**        rc==SQLITE_OK && pVal;
    +**        rc=sqlite3_vtab_in_next(pList, &pVal)
    +**    ){
    +**      // do something with pVal
    +**    }
    +**    if( rc!=SQLITE_OK ){
    +**      // an error has occurred
    +**    }
    +** 
    )^ +** +** ^On success, the sqlite3_vtab_in_first(X,P) and sqlite3_vtab_in_next(X,P) +** routines return SQLITE_OK and set *P to point to the first or next value +** on the RHS of the IN constraint. ^If there are no more values on the +** right hand side of the IN constraint, then *P is set to NULL and these +** routines return [SQLITE_DONE]. ^The return value might be +** some other value, such as SQLITE_NOMEM, in the event of a malfunction. +** +** The *ppOut values returned by these routines are only valid until the +** next call to either of these routines or until the end of the xFilter +** method from which these routines were called. If the virtual table +** implementation needs to retain the *ppOut values for longer, it must make +** copies. The *ppOut values are [protected sqlite3_value|protected]. +*/ +SQLITE_API int sqlite3_vtab_in_first(sqlite3_value *pVal, sqlite3_value **ppOut); +SQLITE_API int sqlite3_vtab_in_next(sqlite3_value *pVal, sqlite3_value **ppOut); + +/* +** CAPI3REF: Constraint values in xBestIndex() +** METHOD: sqlite3_index_info +** +** This API may only be used from within the [xBestIndex|xBestIndex method] +** of a [virtual table] implementation. The result of calling this interface +** from outside of an xBestIndex method are undefined and probably harmful. +** +** ^When the sqlite3_vtab_rhs_value(P,J,V) interface is invoked from within +** the [xBestIndex] method of a [virtual table] implementation, with P being +** a copy of the [sqlite3_index_info] object pointer passed into xBestIndex and +** J being a 0-based index into P->aConstraint[], then this routine +** attempts to set *V to the value of the right-hand operand of +** that constraint if the right-hand operand is known. ^If the +** right-hand operand is not known, then *V is set to a NULL pointer. +** ^The sqlite3_vtab_rhs_value(P,J,V) interface returns SQLITE_OK if +** and only if *V is set to a value. ^The sqlite3_vtab_rhs_value(P,J,V) +** inteface returns SQLITE_NOTFOUND if the right-hand side of the J-th +** constraint is not available. ^The sqlite3_vtab_rhs_value() interface +** can return an result code other than SQLITE_OK or SQLITE_NOTFOUND if +** something goes wrong. +** +** The sqlite3_vtab_rhs_value() interface is usually only successful if +** the right-hand operand of a constraint is a literal value in the original +** SQL statement. If the right-hand operand is an expression or a reference +** to some other column or a [host parameter], then sqlite3_vtab_rhs_value() +** will probably return [SQLITE_NOTFOUND]. +** +** ^(Some constraints, such as [SQLITE_INDEX_CONSTRAINT_ISNULL] and +** [SQLITE_INDEX_CONSTRAINT_ISNOTNULL], have no right-hand operand. For such +** constraints, sqlite3_vtab_rhs_value() always returns SQLITE_NOTFOUND.)^ +** +** ^The [sqlite3_value] object returned in *V is a protected sqlite3_value +** and remains valid for the duration of the xBestIndex method call. +** ^When xBestIndex returns, the sqlite3_value object returned by +** sqlite3_vtab_rhs_value() is automatically deallocated. +** +** The "_rhs_" in the name of this routine is an abbreviation for +** "Right-Hand Side". +*/ +SQLITE_API int sqlite3_vtab_rhs_value(sqlite3_index_info*, int, sqlite3_value **ppVal); /* ** CAPI3REF: Conflict resolution modes @@ -9162,6 +10039,10 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ ** managed by the prepared statement S and will be automatically freed when ** S is finalized. ** +** Not all values are available for all query elements. When a value is +** not available, the output variable is set to -1 if the value is numeric, +** or to NULL if it is a string (SQLITE_SCANSTAT_NAME). +** **
    ** [[SQLITE_SCANSTAT_NLOOP]]
    SQLITE_SCANSTAT_NLOOP
    **
    ^The [sqlite3_int64] variable pointed to by the V parameter will be @@ -9189,12 +10070,24 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ ** to a zero-terminated UTF-8 string containing the [EXPLAIN QUERY PLAN] ** description for the X-th loop. ** -** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECT
    +** [[SQLITE_SCANSTAT_SELECTID]]
    SQLITE_SCANSTAT_SELECTID
    **
    ^The "int" variable pointed to by the V parameter will be set to the -** "select-id" for the X-th loop. The select-id identifies which query or -** subquery the loop is part of. The main query has a select-id of zero. -** The select-id is the same value as is output in the first column -** of an [EXPLAIN QUERY PLAN] query. +** id for the X-th query plan element. The id value is unique within the +** statement. The select-id is the same value as is output in the first +** column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_PARENTID]]
    SQLITE_SCANSTAT_PARENTID
    +**
    The "int" variable pointed to by the V parameter will be set to the +** the id of the parent of the current query element, if applicable, or +** to zero if the query element has no parent. This is the same value as +** returned in the second column of an [EXPLAIN QUERY PLAN] query. +** +** [[SQLITE_SCANSTAT_NCYCLE]]
    SQLITE_SCANSTAT_NCYCLE
    +**
    The sqlite3_int64 output value is set to the number of cycles, +** according to the processor time-stamp counter, that elapsed while the +** query element was being processed. This value is not available for +** all query elements - if it is unavailable the output variable is +** set to -1. **
    */ #define SQLITE_SCANSTAT_NLOOP 0 @@ -9203,12 +10096,14 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ #define SQLITE_SCANSTAT_NAME 3 #define SQLITE_SCANSTAT_EXPLAIN 4 #define SQLITE_SCANSTAT_SELECTID 5 +#define SQLITE_SCANSTAT_PARENTID 6 +#define SQLITE_SCANSTAT_NCYCLE 7 /* ** CAPI3REF: Prepared Statement Scan Status ** METHOD: sqlite3_stmt ** -** This interface returns information about the predicted and measured +** These interfaces return information about the predicted and measured ** performance for pStmt. Advanced applications can use this ** interface to compare the predicted and the measured performance and ** issue warnings and/or rerun [ANALYZE] if discrepancies are found. @@ -9219,19 +10114,25 @@ SQLITE_API SQLITE_EXPERIMENTAL const char *sqlite3_vtab_collation(sqlite3_index_ ** ** The "iScanStatusOp" parameter determines which status information to return. ** The "iScanStatusOp" must be one of the [scanstatus options] or the behavior -** of this interface is undefined. -** ^The requested measurement is written into a variable pointed to by -** the "pOut" parameter. -** Parameter "idx" identifies the specific loop to retrieve statistics for. -** Loops are numbered starting from zero. ^If idx is out of range - less than -** zero or greater than or equal to the total number of loops used to implement -** the statement - a non-zero value is returned and the variable that pOut -** points to is unchanged. -** -** ^Statistics might not be available for all loops in all statements. ^In cases -** where there exist loops with no available statistics, this function behaves -** as if the loop did not exist - it returns non-zero and leave the variable -** that pOut points to unchanged. +** of this interface is undefined. ^The requested measurement is written into +** a variable pointed to by the "pOut" parameter. +** +** The "flags" parameter must be passed a mask of flags. At present only +** one flag is defined - SQLITE_SCANSTAT_COMPLEX. If SQLITE_SCANSTAT_COMPLEX +** is specified, then status information is available for all elements +** of a query plan that are reported by "EXPLAIN QUERY PLAN" output. If +** SQLITE_SCANSTAT_COMPLEX is not specified, then only query plan elements +** that correspond to query loops (the "SCAN..." and "SEARCH..." elements of +** the EXPLAIN QUERY PLAN output) are available. Invoking API +** sqlite3_stmt_scanstatus() is equivalent to calling +** sqlite3_stmt_scanstatus_v2() with a zeroed flags parameter. +** +** Parameter "idx" identifies the specific query element to retrieve statistics +** for. Query elements are numbered starting from zero. A value of -1 may be +** to query for statistics regarding the entire query. ^If idx is out of range +** - less than -1 or greater than or equal to the total number of query +** elements used to implement the statement - a non-zero value is returned and +** the variable that pOut points to is unchanged. ** ** See also: [sqlite3_stmt_scanstatus_reset()] */ @@ -9240,7 +10141,20 @@ SQLITE_API int sqlite3_stmt_scanstatus( int idx, /* Index of loop to report on */ int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ void *pOut /* Result written here */ -); +); +SQLITE_API int sqlite3_stmt_scanstatus_v2( + sqlite3_stmt *pStmt, /* Prepared statement for which info desired */ + int idx, /* Index of loop to report on */ + int iScanStatusOp, /* Information desired. SQLITE_SCANSTAT_* */ + int flags, /* Mask of flags defined below */ + void *pOut /* Result written here */ +); + +/* +** CAPI3REF: Prepared Statement Scan Status +** KEYWORDS: {scan status flags} +*/ +#define SQLITE_SCANSTAT_COMPLEX 0x0001 /* ** CAPI3REF: Zero Scan-Status Counters @@ -9255,18 +10169,19 @@ SQLITE_API void sqlite3_stmt_scanstatus_reset(sqlite3_stmt*); /* ** CAPI3REF: Flush caches to disk mid-transaction +** METHOD: sqlite3 ** ** ^If a write-transaction is open on [database connection] D when the ** [sqlite3_db_cacheflush(D)] interface invoked, any dirty -** pages in the pager-cache that are not currently in use are written out +** pages in the pager-cache that are not currently in use are written out ** to disk. A dirty page may be in use if a database cursor created by an ** active SQL statement is reading from it, or if it is page 1 of a database ** file (page 1 is always "in use"). ^The [sqlite3_db_cacheflush(D)] ** interface flushes caches for all schemas - "main", "temp", and ** any [attached] databases. ** -** ^If this function needs to obtain extra database locks before dirty pages -** can be flushed to disk, it does so. ^If those locks cannot be obtained +** ^If this function needs to obtain extra database locks before dirty pages +** can be flushed to disk, it does so. ^If those locks cannot be obtained ** immediately and there is a busy-handler callback configured, it is invoked ** in the usual manner. ^If the required lock still cannot be obtained, then ** the database is skipped and an attempt made to flush any dirty pages @@ -9287,6 +10202,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); /* ** CAPI3REF: The pre-update hook. +** METHOD: sqlite3 ** ** ^These interfaces are only available if SQLite is compiled using the ** [SQLITE_ENABLE_PREUPDATE_HOOK] compile-time option. @@ -9304,7 +10220,7 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** ^The preupdate hook only fires for changes to real database tables; the ** preupdate hook is not invoked for changes to [virtual tables] or to -** system tables like sqlite_master or sqlite_stat1. +** system tables like sqlite_sequence or sqlite_stat1. ** ** ^The second parameter to the preupdate callback is a pointer to ** the [database connection] that registered the preupdate hook. @@ -9313,21 +10229,25 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** kind of update operation that is about to occur. ** ^(The fourth parameter to the preupdate callback is the name of the ** database within the database connection that is being modified. This -** will be "main" for the main database or "temp" for TEMP tables or +** will be "main" for the main database or "temp" for TEMP tables or ** the name given after the AS keyword in the [ATTACH] statement for attached ** databases.)^ ** ^The fifth parameter to the preupdate callback is the name of the ** table that is being modified. ** ** For an UPDATE or DELETE operation on a [rowid table], the sixth -** parameter passed to the preupdate callback is the initial [rowid] of the +** parameter passed to the preupdate callback is the initial [rowid] of the ** row being modified or deleted. For an INSERT operation on a rowid table, -** or any operation on a WITHOUT ROWID table, the value of the sixth +** or any operation on a WITHOUT ROWID table, the value of the sixth ** parameter is undefined. For an INSERT or UPDATE on a rowid table the ** seventh parameter is the final rowid value of the row being inserted ** or updated. The value of the seventh parameter passed to the callback ** function is not defined for operations on WITHOUT ROWID tables, or for -** INSERT operations on rowid tables. +** DELETE operations on rowid tables. +** +** ^The sqlite3_preupdate_hook(D,C,P) function returns the P argument from +** the previous call on the same [database connection] D, or NULL for +** the first call on D. ** ** The [sqlite3_preupdate_old()], [sqlite3_preupdate_new()], ** [sqlite3_preupdate_count()], and [sqlite3_preupdate_depth()] interfaces @@ -9361,10 +10281,19 @@ SQLITE_API int sqlite3_db_cacheflush(sqlite3*); ** ** ^The [sqlite3_preupdate_depth(D)] interface returns 0 if the preupdate ** callback was invoked as a result of a direct insert, update, or delete -** operation; or 1 for inserts, updates, or deletes invoked by top-level +** operation; or 1 for inserts, updates, or deletes invoked by top-level ** triggers; or 2 for changes resulting from triggers called by top-level ** triggers; and so forth. ** +** When the [sqlite3_blob_write()] API is used to update a blob column, +** the pre-update hook is invoked with SQLITE_DELETE. This is because the +** in this case the new values are not available. In this case, when a +** callback made with op==SQLITE_DELETE is actually a write using the +** sqlite3_blob_write() API, the [sqlite3_preupdate_blobwrite()] returns +** the index of the column being written. In other cases, where the +** pre-update hook is being invoked for some other reason, including a +** regular DELETE, sqlite3_preupdate_blobwrite() returns -1. +** ** See also: [sqlite3_update_hook()] */ #if defined(SQLITE_ENABLE_PREUPDATE_HOOK) @@ -9385,17 +10314,19 @@ SQLITE_API int sqlite3_preupdate_old(sqlite3 *, int, sqlite3_value **); SQLITE_API int sqlite3_preupdate_count(sqlite3 *); SQLITE_API int sqlite3_preupdate_depth(sqlite3 *); SQLITE_API int sqlite3_preupdate_new(sqlite3 *, int, sqlite3_value **); +SQLITE_API int sqlite3_preupdate_blobwrite(sqlite3 *); #endif /* ** CAPI3REF: Low-level system error code +** METHOD: sqlite3 ** ** ^Attempt to return the underlying operating system error code or error ** number that caused the most recent I/O error or failure to open a file. ** The return value is OS-dependent. For example, on unix systems, after ** [sqlite3_open_v2()] returns [SQLITE_CANTOPEN], this interface could be ** called to get back the underlying "errno" that caused the problem, such -** as ENOSPC, EAUTH, EISDIR, and so forth. +** as ENOSPC, EAUTH, EISDIR, and so forth. */ SQLITE_API int sqlite3_system_errno(sqlite3*); @@ -9433,12 +10364,12 @@ typedef struct sqlite3_snapshot { ** [sqlite3_snapshot_get(D,S,P)] interface writes a pointer to the newly ** created [sqlite3_snapshot] object into *P and returns SQLITE_OK. ** If there is not already a read-transaction open on schema S when -** this function is called, one is opened automatically. +** this function is called, one is opened automatically. ** ** The following must be true for this function to succeed. If any of ** the following statements are false when sqlite3_snapshot_get() is ** called, SQLITE_ERROR is returned. The final value of *P is undefined -** in this case. +** in this case. ** **
      **
    • The database handle must not be in [autocommit mode]. @@ -9450,13 +10381,13 @@ typedef struct sqlite3_snapshot { ** **
    • One or more transactions must have been written to the current wal ** file since it was created on disk (by any connection). This means -** that a snapshot cannot be taken on a wal mode database with no wal +** that a snapshot cannot be taken on a wal mode database with no wal ** file immediately after it is first opened. At least one transaction ** must be written to it first. **
    ** ** This function may also return SQLITE_NOMEM. If it is called with the -** database handle in autocommit mode but fails for some other reason, +** database handle in autocommit mode but fails for some other reason, ** whether or not a read transaction is opened on schema S is undefined. ** ** The [sqlite3_snapshot] object returned from a successful call to @@ -9476,38 +10407,38 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_get( ** CAPI3REF: Start a read transaction on an historical snapshot ** METHOD: sqlite3_snapshot ** -** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read -** transaction or upgrades an existing one for schema S of -** [database connection] D such that the read transaction refers to -** historical [snapshot] P, rather than the most recent change to the -** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK +** ^The [sqlite3_snapshot_open(D,S,P)] interface either starts a new read +** transaction or upgrades an existing one for schema S of +** [database connection] D such that the read transaction refers to +** historical [snapshot] P, rather than the most recent change to the +** database. ^The [sqlite3_snapshot_open()] interface returns SQLITE_OK ** on success or an appropriate [error code] if it fails. ** -** ^In order to succeed, the database connection must not be in +** ^In order to succeed, the database connection must not be in ** [autocommit mode] when [sqlite3_snapshot_open(D,S,P)] is called. If there ** is already a read transaction open on schema S, then the database handle ** must have no active statements (SELECT statements that have been passed -** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). +** to sqlite3_step() but not sqlite3_reset() or sqlite3_finalize()). ** SQLITE_ERROR is returned if either of these conditions is violated, or ** if schema S does not exist, or if the snapshot object is invalid. ** ** ^A call to sqlite3_snapshot_open() will fail to open if the specified -** snapshot has been overwritten by a [checkpoint]. In this case +** snapshot has been overwritten by a [checkpoint]. In this case ** SQLITE_ERROR_SNAPSHOT is returned. ** -** If there is already a read transaction open when this function is +** If there is already a read transaction open when this function is ** invoked, then the same read transaction remains open (on the same ** database snapshot) if SQLITE_ERROR, SQLITE_BUSY or SQLITE_ERROR_SNAPSHOT ** is returned. If another error code - for example SQLITE_PROTOCOL or an ** SQLITE_IOERR error code - is returned, then the final state of the -** read transaction is undefined. If SQLITE_OK is returned, then the +** read transaction is undefined. If SQLITE_OK is returned, then the ** read transaction is now open on database snapshot P. ** ** ^(A call to [sqlite3_snapshot_open(D,S,P)] will fail if the ** database connection D does not know that the database file for ** schema S is in [WAL mode]. A database connection might not know ** that the database file is in [WAL mode] if there has been no prior -** I/O on that database connection, or if the database entered [WAL mode] +** I/O on that database connection, or if the database entered [WAL mode] ** after the most recent I/O on the database connection.)^ ** (Hint: Run "[PRAGMA application_id]" against a newly opened ** database connection in order to make it ready to use snapshots.) @@ -9539,17 +10470,17 @@ SQLITE_API SQLITE_EXPERIMENTAL void sqlite3_snapshot_free(sqlite3_snapshot*); ** METHOD: sqlite3_snapshot ** ** The sqlite3_snapshot_cmp(P1, P2) interface is used to compare the ages -** of two valid snapshot handles. +** of two valid snapshot handles. ** -** If the two snapshot handles are not associated with the same database -** file, the result of the comparison is undefined. +** If the two snapshot handles are not associated with the same database +** file, the result of the comparison is undefined. ** ** Additionally, the result of the comparison is only valid if both of the ** snapshot handles were obtained by calling sqlite3_snapshot_get() since the ** last time the wal file was deleted. The wal file is deleted when the ** database is changed back to rollback mode or when the number of database -** clients drops to zero. If either snapshot handle was obtained before the -** wal file was last deleted, the value returned by this function +** clients drops to zero. If either snapshot handle was obtained before the +** wal file was last deleted, the value returned by this function ** is undefined. ** ** Otherwise, this API returns a negative value if P1 refers to an older @@ -9614,7 +10545,7 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c ** representation of the database will usually only exist if there has ** been a prior call to [sqlite3_deserialize(D,S,...)] with the same ** values of D and S. -** The size of the database is written into *P even if the +** The size of the database is written into *P even if the ** SQLITE_SERIALIZE_NOCOPY bit is set but no contiguous copy ** of the database exists. ** @@ -9622,8 +10553,8 @@ SQLITE_API SQLITE_EXPERIMENTAL int sqlite3_snapshot_recover(sqlite3 *db, const c ** SQLITE_SERIALIZE_NOCOPY bit is omitted from argument F if a memory ** allocation error occurs. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API unsigned char *sqlite3_serialize( sqlite3 *db, /* The database connection */ @@ -9651,7 +10582,7 @@ SQLITE_API unsigned char *sqlite3_serialize( /* ** CAPI3REF: Deserialize a database ** -** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the +** The sqlite3_deserialize(D,S,P,N,M,F) interface causes the ** [database connection] D to disconnect from database S and then ** reopen S as an in-memory database based on the serialization contained ** in P. The serialized database P is N bytes in size. M is the size of @@ -9670,12 +10601,16 @@ SQLITE_API unsigned char *sqlite3_serialize( ** database is currently in a read transaction or is involved in a backup ** operation. ** -** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the +** It is not possible to deserialized into the TEMP database. If the +** S argument to sqlite3_deserialize(D,S,P,N,M,F) is "temp" then the +** function returns SQLITE_ERROR. +** +** If sqlite3_deserialize(D,S,P,N,M,F) fails for any reason and if the ** SQLITE_DESERIALIZE_FREEONCLOSE bit is set in argument F, then ** [sqlite3_free()] is invoked on argument P prior to returning. ** -** This interface is only available if SQLite is compiled with the -** [SQLITE_ENABLE_DESERIALIZE] option. +** This interface is omitted if SQLite is compiled with the +** [SQLITE_OMIT_DESERIALIZE] option. */ SQLITE_API int sqlite3_deserialize( sqlite3 *db, /* The database connection */ @@ -9719,6 +10654,19 @@ SQLITE_API int sqlite3_deserialize( # undef double #endif +#if defined(__wasi__) +# undef SQLITE_WASI +# define SQLITE_WASI 1 +# undef SQLITE_OMIT_WAL +# define SQLITE_OMIT_WAL 1/* because it requires shared memory APIs */ +# ifndef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION +# endif +# ifndef SQLITE_THREADSAFE +# define SQLITE_THREADSAFE 0 +# endif +#endif + #ifdef __cplusplus } /* End of the 'extern "C"' block */ #endif @@ -9785,7 +10733,7 @@ struct sqlite3_rtree_geometry { }; /* -** Register a 2nd-generation geometry callback named zScore that can be +** Register a 2nd-generation geometry callback named zScore that can be ** used as part of an R-Tree geometry query as follows: ** ** SELECT ... FROM WHERE MATCH $zQueryFunc(... params ...) @@ -9800,7 +10748,7 @@ SQLITE_API int sqlite3_rtree_query_callback( /* -** A pointer to a structure of the following type is passed as the +** A pointer to a structure of the following type is passed as the ** argument to scored geometry callback registered using ** sqlite3_rtree_query_callback(). ** @@ -9895,7 +10843,7 @@ typedef struct sqlite3_changeset_iter sqlite3_changeset_iter; ** is not possible for an application to register a pre-update hook on a ** database handle that has one or more session objects attached. Nor is ** it possible to create a session object attached to a database handle for -** which a pre-update hook is already defined. The results of attempting +** which a pre-update hook is already defined. The results of attempting ** either of these things are undefined. ** ** The session object will be used to create changesets for tables in @@ -9913,17 +10861,62 @@ SQLITE_API int sqlite3session_create( ** CAPI3REF: Delete A Session Object ** DESTRUCTOR: sqlite3_session ** -** Delete a session object previously allocated using +** Delete a session object previously allocated using ** [sqlite3session_create()]. Once a session object has been deleted, the ** results of attempting to use pSession with any other session module ** function are undefined. ** ** Session objects must be deleted before the database handle to which they -** are attached is closed. Refer to the documentation for +** are attached is closed. Refer to the documentation for ** [sqlite3session_create()] for details. */ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); +/* +** CAPI3REF: Configure a Session Object +** METHOD: sqlite3_session +** +** This method is used to configure a session object after it has been +** created. At present the only valid values for the second parameter are +** [SQLITE_SESSION_OBJCONFIG_SIZE] and [SQLITE_SESSION_OBJCONFIG_ROWID]. +** +*/ +SQLITE_API int sqlite3session_object_config(sqlite3_session*, int op, void *pArg); + +/* +** CAPI3REF: Options for sqlite3session_object_config +** +** The following values may passed as the the 2nd parameter to +** sqlite3session_object_config(). +** +**
    SQLITE_SESSION_OBJCONFIG_SIZE
    +** This option is used to set, clear or query the flag that enables +** the [sqlite3session_changeset_size()] API. Because it imposes some +** computational overhead, this API is disabled by default. Argument +** pArg must point to a value of type (int). If the value is initially +** 0, then the sqlite3session_changeset_size() API is disabled. If it +** is greater than 0, then the same API is enabled. Or, if the initial +** value is less than zero, no change is made. In all cases the (int) +** variable is set to 1 if the sqlite3session_changeset_size() API is +** enabled following the current call, or 0 otherwise. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +** +**
    SQLITE_SESSION_OBJCONFIG_ROWID
    +** This option is used to set, clear or query the flag that enables +** collection of data for tables with no explicit PRIMARY KEY. +** +** Normally, tables with no explicit PRIMARY KEY are simply ignored +** by the sessions module. However, if this flag is set, it behaves +** as if such tables have a column "_rowid_ INTEGER PRIMARY KEY" inserted +** as their leftmost columns. +** +** It is an error (SQLITE_MISUSE) to attempt to modify this setting after +** the first table has been attached to the session object. +*/ +#define SQLITE_SESSION_OBJCONFIG_SIZE 1 +#define SQLITE_SESSION_OBJCONFIG_ROWID 2 /* ** CAPI3REF: Enable Or Disable A Session Object @@ -9937,10 +10930,10 @@ SQLITE_API void sqlite3session_delete(sqlite3_session *pSession); ** the eventual changesets. ** ** Passing zero to this function disables the session. Passing a value -** greater than zero enables it. Passing a value less than zero is a +** greater than zero enables it. Passing a value less than zero is a ** no-op, and may be used to query the current state of the session. ** -** The return value indicates the final state of the session object: 0 if +** The return value indicates the final state of the session object: 0 if ** the session is disabled, or 1 if it is enabled. */ SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); @@ -9955,7 +10948,7 @@ SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); **
      **
    • The session object "indirect" flag is set when the change is ** made, or -**
    • The change is made by an SQL trigger or foreign key action +**
    • The change is made by an SQL trigger or foreign key action ** instead of directly as a result of a users SQL statement. **
    ** @@ -9967,10 +10960,10 @@ SQLITE_API int sqlite3session_enable(sqlite3_session *pSession, int bEnable); ** flag. If the second argument passed to this function is zero, then the ** indirect flag is cleared. If it is greater than zero, the indirect flag ** is set. Passing a value less than zero does not modify the current value -** of the indirect flag, and may be used to query the current state of the +** of the indirect flag, and may be used to query the current state of the ** indirect flag for the specified session object. ** -** The return value indicates the final state of the indirect flag: 0 if +** The return value indicates the final state of the indirect flag: 0 if ** it is clear, or 1 if it is set. */ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect); @@ -9980,20 +10973,20 @@ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect) ** METHOD: sqlite3_session ** ** If argument zTab is not NULL, then it is the name of a table to attach -** to the session object passed as the first argument. All subsequent changes -** made to the table while the session object is enabled will be recorded. See +** to the session object passed as the first argument. All subsequent changes +** made to the table while the session object is enabled will be recorded. See ** documentation for [sqlite3session_changeset()] for further details. ** ** Or, if argument zTab is NULL, then changes are recorded for all tables -** in the database. If additional tables are added to the database (by -** executing "CREATE TABLE" statements) after this call is made, changes for +** in the database. If additional tables are added to the database (by +** executing "CREATE TABLE" statements) after this call is made, changes for ** the new tables are also recorded. ** ** Changes can only be recorded for tables that have a PRIMARY KEY explicitly -** defined as part of their CREATE TABLE statement. It does not matter if the +** defined as part of their CREATE TABLE statement. It does not matter if the ** PRIMARY KEY is an "INTEGER PRIMARY KEY" (rowid alias) or not. The PRIMARY ** KEY may consist of a single column, or may be a composite key. -** +** ** It is not an error if the named table does not exist in the database. Nor ** is it an error if the named table does not have a PRIMARY KEY. However, ** no changes will be recorded in either of these scenarios. @@ -10001,29 +10994,29 @@ SQLITE_API int sqlite3session_indirect(sqlite3_session *pSession, int bIndirect) ** Changes are not recorded for individual rows that have NULL values stored ** in one or more of their PRIMARY KEY columns. ** -** SQLITE_OK is returned if the call completes without error. Or, if an error +** SQLITE_OK is returned if the call completes without error. Or, if an error ** occurs, an SQLite error code (e.g. SQLITE_NOMEM) is returned. ** **

    Special sqlite_stat1 Handling

    ** -** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to +** As of SQLite version 3.22.0, the "sqlite_stat1" table is an exception to ** some of the rules above. In SQLite, the schema of sqlite_stat1 is: **
    -**        CREATE TABLE sqlite_stat1(tbl,idx,stat)  
    +**        CREATE TABLE sqlite_stat1(tbl,idx,stat)
     **  
    ** -** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are -** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes +** Even though sqlite_stat1 does not have a PRIMARY KEY, changes are +** recorded for it as if the PRIMARY KEY is (tbl,idx). Additionally, changes ** are recorded for rows for which (idx IS NULL) is true. However, for such ** rows a zero-length blob (SQL value X'') is stored in the changeset or ** patchset instead of a NULL value. This allows such changesets to be ** manipulated by legacy implementations of sqlite3changeset_invert(), ** concat() and similar. ** -** The sqlite3changeset_apply() function automatically converts the +** The sqlite3changeset_apply() function automatically converts the ** zero-length blob back to a NULL value when updating the sqlite_stat1 ** table. However, if the application calls sqlite3changeset_new(), -** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset +** sqlite3changeset_old() or sqlite3changeset_conflict on a changeset ** iterator directly (including on a changeset iterator passed to a ** conflict-handler callback) then the X'' value is returned. The application ** must translate X'' to NULL itself if required. @@ -10042,10 +11035,10 @@ SQLITE_API int sqlite3session_attach( ** CAPI3REF: Set a table filter on a Session Object. ** METHOD: sqlite3_session ** -** The second argument (xFilter) is the "filter callback". For changes to rows +** The second argument (xFilter) is the "filter callback". For changes to rows ** in tables that are not attached to the Session object, the filter is called -** to determine whether changes to the table's rows should be tracked or not. -** If xFilter returns 0, changes are not tracked. Note that once a table is +** to determine whether changes to the table's rows should be tracked or not. +** If xFilter returns 0, changes are not tracked. Note that once a table is ** attached, xFilter will not be called again. */ SQLITE_API void sqlite3session_table_filter( @@ -10061,9 +11054,9 @@ SQLITE_API void sqlite3session_table_filter( ** CAPI3REF: Generate A Changeset From A Session Object ** METHOD: sqlite3_session ** -** Obtain a changeset containing changes to the tables attached to the -** session object passed as the first argument. If successful, -** set *ppChangeset to point to a buffer containing the changeset +** Obtain a changeset containing changes to the tables attached to the +** session object passed as the first argument. If successful, +** set *ppChangeset to point to a buffer containing the changeset ** and *pnChangeset to the size of the changeset in bytes before returning ** SQLITE_OK. If an error occurs, set both *ppChangeset and *pnChangeset to ** zero and return an SQLite error code. @@ -10078,7 +11071,7 @@ SQLITE_API void sqlite3session_table_filter( ** modifies the values of primary key columns. If such a change is made, it ** is represented in a changeset as a DELETE followed by an INSERT. ** -** Changes are not recorded for rows that have NULL values stored in one or +** Changes are not recorded for rows that have NULL values stored in one or ** more of their PRIMARY KEY columns. If such a row is inserted or deleted, ** no corresponding change is present in the changesets returned by this ** function. If an existing row with one or more NULL values stored in @@ -10131,14 +11124,14 @@ SQLITE_API void sqlite3session_table_filter( **
      **
    • For each record generated by an insert, the database is queried ** for a row with a matching primary key. If one is found, an INSERT -** change is added to the changeset. If no such row is found, no change +** change is added to the changeset. If no such row is found, no change ** is added to the changeset. ** -**
    • For each record generated by an update or delete, the database is +**
    • For each record generated by an update or delete, the database is ** queried for a row with a matching primary key. If such a row is ** found and one or more of the non-primary key fields have been -** modified from their original values, an UPDATE change is added to -** the changeset. Or, if no such row is found in the table, a DELETE +** modified from their original values, an UPDATE change is added to +** the changeset. Or, if no such row is found in the table, a DELETE ** change is added to the changeset. If there is a row with a matching ** primary key in the database, but all fields contain their original ** values, no change is added to the changeset. @@ -10146,7 +11139,7 @@ SQLITE_API void sqlite3session_table_filter( ** ** This means, amongst other things, that if a row is inserted and then later ** deleted while a session object is active, neither the insert nor the delete -** will be present in the changeset. Or if a row is deleted and then later a +** will be present in the changeset. Or if a row is deleted and then later a ** row with the same primary key values inserted while a session object is ** active, the resulting changeset will contain an UPDATE change instead of ** a DELETE and an INSERT. @@ -10155,10 +11148,10 @@ SQLITE_API void sqlite3session_table_filter( ** it does not accumulate records when rows are inserted, updated or deleted. ** This may appear to have some counter-intuitive effects if a single row ** is written to more than once during a session. For example, if a row -** is inserted while a session object is enabled, then later deleted while +** is inserted while a session object is enabled, then later deleted while ** the same session object is disabled, no INSERT record will appear in the ** changeset, even though the delete took place while the session was disabled. -** Or, if one field of a row is updated while a session is disabled, and +** Or, if one field of a row is updated while a session is disabled, and ** another field of the same row is updated while the session is enabled, the ** resulting changeset will contain an UPDATE change that updates both fields. */ @@ -10168,6 +11161,22 @@ SQLITE_API int sqlite3session_changeset( void **ppChangeset /* OUT: Buffer containing changeset */ ); +/* +** CAPI3REF: Return An Upper-limit For The Size Of The Changeset +** METHOD: sqlite3_session +** +** By default, this function always returns 0. For it to return +** a useful result, the sqlite3_session object must have been configured +** to enable this API using sqlite3session_object_config() with the +** SQLITE_SESSION_OBJCONFIG_SIZE verb. +** +** When enabled, this function returns an upper limit, in bytes, for the size +** of the changeset that might be produced if sqlite3session_changeset() were +** called. The final changeset size might be equal to or smaller than the +** size in bytes returned by this function. +*/ +SQLITE_API sqlite3_int64 sqlite3session_changeset_size(sqlite3_session *pSession); + /* ** CAPI3REF: Load The Difference Between Tables Into A Session ** METHOD: sqlite3_session @@ -10179,7 +11188,7 @@ SQLITE_API int sqlite3session_changeset( ** an error). ** ** Argument zFromDb must be the name of a database ("main", "temp" etc.) -** attached to the same database handle as the session object that contains +** attached to the same database handle as the session object that contains ** a table compatible with the table attached to the session by this function. ** A table is considered compatible if it: ** @@ -10195,25 +11204,25 @@ SQLITE_API int sqlite3session_changeset( ** APIs, tables without PRIMARY KEYs are simply ignored. ** ** This function adds a set of changes to the session object that could be -** used to update the table in database zFrom (call this the "from-table") -** so that its content is the same as the table attached to the session +** used to update the table in database zFrom (call this the "from-table") +** so that its content is the same as the table attached to the session ** object (call this the "to-table"). Specifically: ** **
        -**
      • For each row (primary key) that exists in the to-table but not in +**
      • For each row (primary key) that exists in the to-table but not in ** the from-table, an INSERT record is added to the session object. ** -**
      • For each row (primary key) that exists in the to-table but not in +**
      • For each row (primary key) that exists in the to-table but not in ** the from-table, a DELETE record is added to the session object. ** -**
      • For each row (primary key) that exists in both tables, but features +**
      • For each row (primary key) that exists in both tables, but features ** different non-PK values in each, an UPDATE record is added to the -** session. +** session. **
      ** ** To clarify, if this function is called and then a changeset constructed -** using [sqlite3session_changeset()], then after applying that changeset to -** database zFrom the contents of the two compatible tables would be +** using [sqlite3session_changeset()], then after applying that changeset to +** database zFrom the contents of the two compatible tables would be ** identical. ** ** It an error if database zFrom does not exist or does not contain the @@ -10221,7 +11230,7 @@ SQLITE_API int sqlite3session_changeset( ** ** If the operation is successful, SQLITE_OK is returned. Otherwise, an SQLite ** error code. In this case, if argument pzErrMsg is not NULL, *pzErrMsg -** may be set to point to a buffer containing an English language error +** may be set to point to a buffer containing an English language error ** message. It is the responsibility of the caller to free this buffer using ** sqlite3_free(). */ @@ -10240,19 +11249,19 @@ SQLITE_API int sqlite3session_diff( ** The differences between a patchset and a changeset are that: ** **
        -**
      • DELETE records consist of the primary key fields only. The +**
      • DELETE records consist of the primary key fields only. The ** original values of other fields are omitted. -**
      • The original values of any modified fields are omitted from +**
      • The original values of any modified fields are omitted from ** UPDATE records. **
      ** -** A patchset blob may be used with up to date versions of all -** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), +** A patchset blob may be used with up to date versions of all +** sqlite3changeset_xxx API functions except for sqlite3changeset_invert(), ** which returns SQLITE_CORRUPT if it is passed a patchset. Similarly, ** attempting to use a patchset blob with old versions of the -** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. +** sqlite3changeset_xxx APIs also provokes an SQLITE_CORRUPT error. ** -** Because the non-primary key "old.*" fields are omitted, no +** Because the non-primary key "old.*" fields are omitted, no ** SQLITE_CHANGESET_DATA conflicts can be detected or reported if a patchset ** is passed to the sqlite3changeset_apply() API. Other conflict types work ** in the same way as for changesets. @@ -10271,22 +11280,30 @@ SQLITE_API int sqlite3session_patchset( /* ** CAPI3REF: Test if a changeset has recorded any changes. ** -** Return non-zero if no changes to attached tables have been recorded by -** the session object passed as the first argument. Otherwise, if one or +** Return non-zero if no changes to attached tables have been recorded by +** the session object passed as the first argument. Otherwise, if one or ** more changes have been recorded, return zero. ** ** Even if this function returns zero, it is possible that calling ** [sqlite3session_changeset()] on the session handle may still return a -** changeset that contains no changes. This can happen when a row in -** an attached table is modified and then later on the original values +** changeset that contains no changes. This can happen when a row in +** an attached table is modified and then later on the original values ** are restored. However, if this function returns non-zero, then it is -** guaranteed that a call to sqlite3session_changeset() will return a +** guaranteed that a call to sqlite3session_changeset() will return a ** changeset containing zero changes. */ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); /* -** CAPI3REF: Create An Iterator To Traverse A Changeset +** CAPI3REF: Query for the amount of heap memory used by a session object. +** +** This API returns the total amount of heap memory in bytes currently +** used by the session object passed as the only argument. +*/ +SQLITE_API sqlite3_int64 sqlite3session_memory_used(sqlite3_session *pSession); + +/* +** CAPI3REF: Create An Iterator To Traverse A Changeset ** CONSTRUCTOR: sqlite3_changeset_iter ** ** Create an iterator used to iterate through the contents of a changeset. @@ -10294,7 +11311,7 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); ** is returned. Otherwise, if an error occurs, *pp is set to zero and an ** SQLite error code is returned. ** -** The following functions can be used to advance and query a changeset +** The following functions can be used to advance and query a changeset ** iterator created by this function: ** **
        @@ -10311,12 +11328,12 @@ SQLITE_API int sqlite3session_isempty(sqlite3_session *pSession); ** ** Assuming the changeset blob was created by one of the ** [sqlite3session_changeset()], [sqlite3changeset_concat()] or -** [sqlite3changeset_invert()] functions, all changes within the changeset -** that apply to a single table are grouped together. This means that when -** an application iterates through a changeset using an iterator created by -** this function, all changes that relate to a single table are visited -** consecutively. There is no chance that the iterator will visit a change -** the applies to table X, then one for table Y, and then later on visit +** [sqlite3changeset_invert()] functions, all changes within the changeset +** that apply to a single table are grouped together. This means that when +** an application iterates through a changeset using an iterator created by +** this function, all changes that relate to a single table are visited +** consecutively. There is no chance that the iterator will visit a change +** the applies to table X, then one for table Y, and then later on visit ** another change for table X. ** ** The behavior of sqlite3changeset_start_v2() and its streaming equivalent @@ -10367,12 +11384,12 @@ SQLITE_API int sqlite3changeset_start_v2( ** point to the first change in the changeset. Each subsequent call advances ** the iterator to point to the next change in the changeset (if any). If ** no error occurs and the iterator points to a valid change after a call -** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. +** to sqlite3changeset_next() has advanced it, SQLITE_ROW is returned. ** Otherwise, if all changes in the changeset have already been visited, ** SQLITE_DONE is returned. ** -** If an error occurs, an SQLite error code is returned. Possible error -** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or +** If an error occurs, an SQLite error code is returned. Possible error +** codes include SQLITE_CORRUPT (if the changeset buffer is corrupt) or ** SQLITE_NOMEM. */ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); @@ -10387,18 +11404,23 @@ SQLITE_API int sqlite3changeset_next(sqlite3_changeset_iter *pIter); ** call to [sqlite3changeset_next()] must have returned [SQLITE_ROW]. If this ** is not the case, this function returns [SQLITE_MISUSE]. ** -** If argument pzTab is not NULL, then *pzTab is set to point to a -** nul-terminated utf-8 encoded string containing the name of the table -** affected by the current change. The buffer remains valid until either -** sqlite3changeset_next() is called on the iterator or until the -** conflict-handler function returns. If pnCol is not NULL, then *pnCol is -** set to the number of columns in the table affected by the change. If -** pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change +** Arguments pOp, pnCol and pzTab may not be NULL. Upon return, three +** outputs are set through these pointers: +** +** *pOp is set to one of [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], +** depending on the type of change that the iterator currently points to; +** +** *pnCol is set to the number of columns in the table affected by the change; and +** +** *pzTab is set to point to a nul-terminated utf-8 encoded string containing +** the name of the table affected by the current change. The buffer remains +** valid until either sqlite3changeset_next() is called on the iterator +** or until the conflict-handler function returns. +** +** If pbIndirect is not NULL, then *pbIndirect is set to true (1) if the change ** is an indirect change, or false (0) otherwise. See the documentation for ** [sqlite3session_indirect()] for a description of direct and indirect -** changes. Finally, if pOp is not NULL, then *pOp is set to one of -** [SQLITE_INSERT], [SQLITE_DELETE] or [SQLITE_UPDATE], depending on the -** type of change that the iterator currently points to. +** changes. ** ** If no error occurs, SQLITE_OK is returned. If an error does occur, an ** SQLite error code is returned. The values of the output variables may not @@ -10451,7 +11473,7 @@ SQLITE_API int sqlite3changeset_pk( ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator ** created by [sqlite3changeset_start()]. In the latter case, the most recent -** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. ** Furthermore, it may only be called if the type of change that the iterator ** currently points to is either [SQLITE_DELETE] or [SQLITE_UPDATE]. Otherwise, ** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. @@ -10461,9 +11483,9 @@ SQLITE_API int sqlite3changeset_pk( ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the vector of +** sqlite3_value object containing the iVal'th value from the vector of ** original row values stored as part of the UPDATE or DELETE change and -** returns SQLITE_OK. The name of the function comes from the fact that this +** returns SQLITE_OK. The name of the function comes from the fact that this ** is similar to the "old.*" columns available to update or delete triggers. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code @@ -10482,7 +11504,7 @@ SQLITE_API int sqlite3changeset_old( ** The pIter argument passed to this function may either be an iterator ** passed to a conflict-handler by [sqlite3changeset_apply()], or an iterator ** created by [sqlite3changeset_start()]. In the latter case, the most recent -** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. +** call to [sqlite3changeset_next()] must have returned SQLITE_ROW. ** Furthermore, it may only be called if the type of change that the iterator ** currently points to is either [SQLITE_UPDATE] or [SQLITE_INSERT]. Otherwise, ** this function returns [SQLITE_MISUSE] and sets *ppValue to NULL. @@ -10492,12 +11514,12 @@ SQLITE_API int sqlite3changeset_old( ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the vector of +** sqlite3_value object containing the iVal'th value from the vector of ** new row values stored as part of the UPDATE or INSERT change and ** returns SQLITE_OK. If the change is an UPDATE and does not include -** a new value for the requested column, *ppValue is set to NULL and -** SQLITE_OK returned. The name of the function comes from the fact that -** this is similar to the "new.*" columns available to update or delete +** a new value for the requested column, *ppValue is set to NULL and +** SQLITE_OK returned. The name of the function comes from the fact that +** this is similar to the "new.*" columns available to update or delete ** triggers. ** ** If some other error occurs (e.g. an OOM condition), an SQLite error code @@ -10524,7 +11546,7 @@ SQLITE_API int sqlite3changeset_new( ** [SQLITE_RANGE] is returned and *ppValue is set to NULL. ** ** If successful, this function sets *ppValue to point to a protected -** sqlite3_value object containing the iVal'th value from the +** sqlite3_value object containing the iVal'th value from the ** "conflicting row" associated with the current conflict-handler callback ** and returns SQLITE_OK. ** @@ -10568,7 +11590,7 @@ SQLITE_API int sqlite3changeset_fk_conflicts( ** call has no effect. ** ** If an error was encountered within a call to an sqlite3changeset_xxx() -** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an +** function (for example an [SQLITE_CORRUPT] in [sqlite3changeset_next()] or an ** [SQLITE_NOMEM] in [sqlite3changeset_new()]) then an error code corresponding ** to that error is returned by this function. Otherwise, SQLITE_OK is ** returned. This is to allow the following pattern (pseudo-code): @@ -10580,7 +11602,7 @@ SQLITE_API int sqlite3changeset_fk_conflicts( ** } ** rc = sqlite3changeset_finalize(); ** if( rc!=SQLITE_OK ){ -** // An error has occurred +** // An error has occurred ** } ** */ @@ -10608,7 +11630,7 @@ SQLITE_API int sqlite3changeset_finalize(sqlite3_changeset_iter *pIter); ** zeroed and an SQLite error code returned. ** ** It is the responsibility of the caller to eventually call sqlite3_free() -** on the *ppOut pointer to free the buffer allocation following a successful +** on the *ppOut pointer to free the buffer allocation following a successful ** call to this function. ** ** WARNING/TODO: This function currently assumes that the input is a valid @@ -10622,11 +11644,11 @@ SQLITE_API int sqlite3changeset_invert( /* ** CAPI3REF: Concatenate Two Changeset Objects ** -** This function is used to concatenate two changesets, A and B, into a +** This function is used to concatenate two changesets, A and B, into a ** single changeset. The result is a changeset equivalent to applying -** changeset A followed by changeset B. +** changeset A followed by changeset B. ** -** This function combines the two input changesets using an +** This function combines the two input changesets using an ** sqlite3_changegroup object. Calling it produces similar results as the ** following code fragment: ** @@ -10658,7 +11680,7 @@ SQLITE_API int sqlite3changeset_concat( /* ** CAPI3REF: Changegroup Handle ** -** A changegroup is an object used to combine two or more +** A changegroup is an object used to combine two or more ** [changesets] or [patchsets] */ typedef struct sqlite3_changegroup sqlite3_changegroup; @@ -10674,7 +11696,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; ** ** If successful, this function returns SQLITE_OK and populates (*pp) with ** a pointer to a new sqlite3_changegroup object before returning. The caller -** should eventually free the returned object using a call to +** should eventually free the returned object using a call to ** sqlite3changegroup_delete(). If an error occurs, an SQLite error code ** (i.e. SQLITE_NOMEM) is returned and *pp is set to NULL. ** @@ -10686,7 +11708,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; **
      • Zero or more changesets (or patchsets) are added to the object ** by calling sqlite3changegroup_add(). ** -**
      • The result of combining all input changesets together is obtained +**
      • The result of combining all input changesets together is obtained ** by the application via a call to sqlite3changegroup_output(). ** **
      • The object is deleted using a call to sqlite3changegroup_delete(). @@ -10695,7 +11717,7 @@ typedef struct sqlite3_changegroup sqlite3_changegroup; ** Any number of calls to add() and output() may be made between the calls to ** new() and delete(), and in any order. ** -** As well as the regular sqlite3changegroup_add() and +** As well as the regular sqlite3changegroup_add() and ** sqlite3changegroup_output() functions, also available are the streaming ** versions sqlite3changegroup_add_strm() and sqlite3changegroup_output_strm(). */ @@ -10706,7 +11728,7 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); ** METHOD: sqlite3_changegroup ** ** Add all changes within the changeset (or patchset) in buffer pData (size -** nData bytes) to the changegroup. +** nData bytes) to the changegroup. ** ** If the buffer contains a patchset, then all prior calls to this function ** on the same changegroup object must also have specified patchsets. Or, if @@ -10733,7 +11755,7 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); ** changeset was recorded immediately after the changesets already ** added to the changegroup. ** INSERT UPDATE -** The INSERT change remains in the changegroup. The values in the +** The INSERT change remains in the changegroup. The values in the ** INSERT change are modified as if the row was inserted by the ** existing change and then updated according to the new change. ** INSERT DELETE @@ -10744,17 +11766,17 @@ SQLITE_API int sqlite3changegroup_new(sqlite3_changegroup **pp); ** changeset was recorded immediately after the changesets already ** added to the changegroup. ** UPDATE UPDATE -** The existing UPDATE remains within the changegroup. It is amended -** so that the accompanying values are as if the row was updated once +** The existing UPDATE remains within the changegroup. It is amended +** so that the accompanying values are as if the row was updated once ** by the existing change and then again by the new change. ** UPDATE DELETE ** The existing UPDATE is replaced by the new DELETE within the ** changegroup. ** DELETE INSERT ** If one or more of the column values in the row inserted by the -** new change differ from those in the row deleted by the existing +** new change differ from those in the row deleted by the existing ** change, the existing DELETE is replaced by an UPDATE within the -** changegroup. Otherwise, if the inserted row is exactly the same +** changegroup. Otherwise, if the inserted row is exactly the same ** as the deleted row, the existing DELETE is simply discarded. ** DELETE UPDATE ** The new change is ignored. This case does not occur if the new @@ -10799,7 +11821,7 @@ SQLITE_API int sqlite3changegroup_add(sqlite3_changegroup*, int nData, void *pDa ** ** If an error occurs, an SQLite error code is returned and the output ** variables (*pnData) and (*ppData) are set to 0. Otherwise, SQLITE_OK -** is returned and the output variables are set to the size of and a +** is returned and the output variables are set to the size of and a ** pointer to the output buffer, respectively. In this case it is the ** responsibility of the caller to eventually free the buffer using a ** call to sqlite3_free(). @@ -10821,7 +11843,7 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** ** Apply a changeset or patchset to a database. These functions attempt to ** update the "main" database attached to handle db with the changes found in -** the changeset passed via the second and third arguments. +** the changeset passed via the second and third arguments. ** ** The fourth argument (xFilter) passed to these functions is the "filter ** callback". If it is not NULL, then for each table affected by at least one @@ -10832,16 +11854,16 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** Otherwise, if the return value is non-zero or the xFilter argument to ** is NULL, all changes related to the table are attempted. ** -** For each table that is not excluded by the filter callback, this function -** tests that the target database contains a compatible table. A table is +** For each table that is not excluded by the filter callback, this function +** tests that the target database contains a compatible table. A table is ** considered compatible if all of the following are true: ** **
          -**
        • The table has the same name as the name recorded in the +**
        • The table has the same name as the name recorded in the ** changeset, and -**
        • The table has at least as many columns as recorded in the +**
        • The table has at least as many columns as recorded in the ** changeset, and -**
        • The table has primary key columns in the same position as +**
        • The table has primary key columns in the same position as ** recorded in the changeset. **
        ** @@ -10850,11 +11872,11 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** via the sqlite3_log() mechanism with the error code SQLITE_SCHEMA. At most ** one such warning is issued for each table in the changeset. ** -** For each change for which there is a compatible table, an attempt is made -** to modify the table contents according to the UPDATE, INSERT or DELETE -** change. If a change cannot be applied cleanly, the conflict handler -** function passed as the fifth argument to sqlite3changeset_apply() may be -** invoked. A description of exactly when the conflict handler is invoked for +** For each change for which there is a compatible table, an attempt is made +** to modify the table contents according to the UPDATE, INSERT or DELETE +** change. If a change cannot be applied cleanly, the conflict handler +** function passed as the fifth argument to sqlite3changeset_apply() may be +** invoked. A description of exactly when the conflict handler is invoked for ** each type of change is below. ** ** Unlike the xFilter argument, xConflict may not be passed NULL. The results @@ -10862,23 +11884,23 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** argument are undefined. ** ** Each time the conflict handler function is invoked, it must return one -** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or +** of [SQLITE_CHANGESET_OMIT], [SQLITE_CHANGESET_ABORT] or ** [SQLITE_CHANGESET_REPLACE]. SQLITE_CHANGESET_REPLACE may only be returned ** if the second argument passed to the conflict handler is either ** SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If the conflict-handler ** returns an illegal value, any changes already made are rolled back and -** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different +** the call to sqlite3changeset_apply() returns SQLITE_MISUSE. Different ** actions are taken by sqlite3changeset_apply() depending on the value ** returned by each invocation of the conflict-handler function. Refer to -** the documentation for the three +** the documentation for the three ** [SQLITE_CHANGESET_OMIT|available return values] for details. ** **
        **
        DELETE Changes
        -** For each DELETE change, the function checks if the target database -** contains a row with the same primary key value (or values) as the -** original row values stored in the changeset. If it does, and the values -** stored in all non-primary key columns also match the values stored in +** For each DELETE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values +** stored in all non-primary key columns also match the values stored in ** the changeset the row is deleted from the target database. ** ** If a row with matching primary key values is found, but one or more of @@ -10907,22 +11929,22 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** database table, the trailing fields are populated with their default ** values. ** -** If the attempt to insert the row fails because the database already +** If the attempt to insert the row fails because the database already ** contains a row with the same primary key values, the conflict handler -** function is invoked with the second argument set to +** function is invoked with the second argument set to ** [SQLITE_CHANGESET_CONFLICT]. ** ** If the attempt to insert the row fails because of some other constraint -** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is +** violation (e.g. NOT NULL or UNIQUE), the conflict handler function is ** invoked with the second argument set to [SQLITE_CHANGESET_CONSTRAINT]. -** This includes the case where the INSERT operation is re-attempted because -** an earlier call to the conflict handler function returned +** This includes the case where the INSERT operation is re-attempted because +** an earlier call to the conflict handler function returned ** [SQLITE_CHANGESET_REPLACE]. ** **
        UPDATE Changes
        -** For each UPDATE change, the function checks if the target database -** contains a row with the same primary key value (or values) as the -** original row values stored in the changeset. If it does, and the values +** For each UPDATE change, the function checks if the target database +** contains a row with the same primary key value (or values) as the +** original row values stored in the changeset. If it does, and the values ** stored in all modified non-primary key columns also match the values ** stored in the changeset the row is updated within the target database. ** @@ -10938,12 +11960,12 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** the conflict-handler function is invoked with [SQLITE_CHANGESET_NOTFOUND] ** passed as the second argument. ** -** If the UPDATE operation is attempted, but SQLite returns -** SQLITE_CONSTRAINT, the conflict-handler function is invoked with +** If the UPDATE operation is attempted, but SQLite returns +** SQLITE_CONSTRAINT, the conflict-handler function is invoked with ** [SQLITE_CHANGESET_CONSTRAINT] passed as the second argument. -** This includes the case where the UPDATE operation is attempted after +** This includes the case where the UPDATE operation is attempted after ** an earlier call to the conflict handler function returned -** [SQLITE_CHANGESET_REPLACE]. +** [SQLITE_CHANGESET_REPLACE]. **
        ** ** It is safe to execute SQL statements, including those that write to the @@ -10954,12 +11976,12 @@ SQLITE_API void sqlite3changegroup_delete(sqlite3_changegroup*); ** All changes made by these functions are enclosed in a savepoint transaction. ** If any other error (aside from a constraint failure when attempting to ** write to the target database) occurs, then the savepoint transaction is -** rolled back, restoring the target database to its original state, and an +** rolled back, restoring the target database to its original state, and an ** SQLite error code returned. ** ** If the output parameters (ppRebase) and (pnRebase) are non-NULL and ** the input is a changeset (not a patchset), then sqlite3changeset_apply_v2() -** may set (*ppRebase) to point to a "rebase" that may be used with the +** may set (*ppRebase) to point to a "rebase" that may be used with the ** sqlite3_rebaser APIs buffer before returning. In this case (*pnRebase) ** is set to the size of the buffer in bytes. It is the responsibility of the ** caller to eventually free any such buffer using sqlite3_free(). The buffer @@ -11020,18 +12042,32 @@ SQLITE_API int sqlite3changeset_apply_v2( ** SAVEPOINT is committed if the changeset or patchset is successfully ** applied, or rolled back if an error occurs. Specifying this flag ** causes the sessions module to omit this savepoint. In this case, if the -** caller has an open transaction or savepoint when apply_v2() is called, +** caller has an open transaction or savepoint when apply_v2() is called, ** it may revert the partially applied changeset by rolling it back. ** **
        SQLITE_CHANGESETAPPLY_INVERT
        ** Invert the changeset before applying it. This is equivalent to inverting ** a changeset using sqlite3changeset_invert() before applying it. It is ** an error to specify this flag with a patchset. +** +**
        SQLITE_CHANGESETAPPLY_IGNORENOOP
        +** Do not invoke the conflict handler callback for any changes that +** would not actually modify the database even if they were applied. +** Specifically, this means that the conflict handler is not invoked +** for: +**
          +**
        • a delete change if the row being deleted cannot be found, +**
        • an update change if the modified fields are already set to +** their new values in the conflicting row, or +**
        • an insert change if all fields of the conflicting row match +** the row being inserted. +**
        */ #define SQLITE_CHANGESETAPPLY_NOSAVEPOINT 0x0001 #define SQLITE_CHANGESETAPPLY_INVERT 0x0002 +#define SQLITE_CHANGESETAPPLY_IGNORENOOP 0x0004 -/* +/* ** CAPI3REF: Constants Passed To The Conflict Handler ** ** Values that may be passed as the second argument to a conflict-handler. @@ -11040,32 +12076,32 @@ SQLITE_API int sqlite3changeset_apply_v2( **
        SQLITE_CHANGESET_DATA
        ** The conflict handler is invoked with CHANGESET_DATA as the second argument ** when processing a DELETE or UPDATE change if a row with the required -** PRIMARY KEY fields is present in the database, but one or more other -** (non primary-key) fields modified by the update do not contain the +** PRIMARY KEY fields is present in the database, but one or more other +** (non primary-key) fields modified by the update do not contain the ** expected "before" values. -** +** ** The conflicting row, in this case, is the database row with the matching ** primary key. -** +** **
        SQLITE_CHANGESET_NOTFOUND
        ** The conflict handler is invoked with CHANGESET_NOTFOUND as the second ** argument when processing a DELETE or UPDATE change if a row with the ** required PRIMARY KEY fields is not present in the database. -** +** ** There is no conflicting row in this case. The results of invoking the ** sqlite3changeset_conflict() API are undefined. -** +** **
        SQLITE_CHANGESET_CONFLICT
        ** CHANGESET_CONFLICT is passed as the second argument to the conflict -** handler while processing an INSERT change if the operation would result +** handler while processing an INSERT change if the operation would result ** in duplicate primary key values. -** +** ** The conflicting row in this case is the database row with the matching ** primary key. ** **
        SQLITE_CHANGESET_FOREIGN_KEY
        ** If foreign key handling is enabled, and applying a changeset leaves the -** database in a state containing foreign key violations, the conflict +** database in a state containing foreign key violations, the conflict ** handler is invoked with CHANGESET_FOREIGN_KEY as the second argument ** exactly once before the changeset is committed. If the conflict handler ** returns CHANGESET_OMIT, the changes, including those that caused the @@ -11075,12 +12111,12 @@ SQLITE_API int sqlite3changeset_apply_v2( ** No current or conflicting row information is provided. The only function ** it is possible to call on the supplied sqlite3_changeset_iter handle ** is sqlite3changeset_fk_conflicts(). -** +** **
        SQLITE_CHANGESET_CONSTRAINT
        -** If any other constraint violation occurs while applying a change (i.e. -** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is +** If any other constraint violation occurs while applying a change (i.e. +** a UNIQUE, CHECK or NOT NULL constraint), the conflict handler is ** invoked with CHANGESET_CONSTRAINT as the second argument. -** +** ** There is no conflicting row in this case. The results of invoking the ** sqlite3changeset_conflict() API are undefined. ** @@ -11092,7 +12128,7 @@ SQLITE_API int sqlite3changeset_apply_v2( #define SQLITE_CHANGESET_CONSTRAINT 4 #define SQLITE_CHANGESET_FOREIGN_KEY 5 -/* +/* ** CAPI3REF: Constants Returned By The Conflict Handler ** ** A conflict handler callback must return one of the following three values. @@ -11100,13 +12136,13 @@ SQLITE_API int sqlite3changeset_apply_v2( **
        **
        SQLITE_CHANGESET_OMIT
        ** If a conflict handler returns this value no special action is taken. The -** change that caused the conflict is not applied. The session module +** change that caused the conflict is not applied. The session module ** continues to the next change in the changeset. ** **
        SQLITE_CHANGESET_REPLACE
        ** This value may only be returned if the second argument to the conflict ** handler was SQLITE_CHANGESET_DATA or SQLITE_CHANGESET_CONFLICT. If this -** is not the case, any changes applied so far are rolled back and the +** is not the case, any changes applied so far are rolled back and the ** call to sqlite3changeset_apply() returns SQLITE_MISUSE. ** ** If CHANGESET_REPLACE is returned by an SQLITE_CHANGESET_DATA conflict @@ -11119,7 +12155,7 @@ SQLITE_API int sqlite3changeset_apply_v2( ** the original row is restored to the database before continuing. ** **
        SQLITE_CHANGESET_ABORT
        -** If this value is returned, any changes applied so far are rolled back +** If this value is returned, any changes applied so far are rolled back ** and the call to sqlite3changeset_apply() returns SQLITE_ABORT. **
        */ @@ -11127,20 +12163,20 @@ SQLITE_API int sqlite3changeset_apply_v2( #define SQLITE_CHANGESET_REPLACE 1 #define SQLITE_CHANGESET_ABORT 2 -/* +/* ** CAPI3REF: Rebasing changesets ** EXPERIMENTAL ** ** Suppose there is a site hosting a database in state S0. And that ** modifications are made that move that database to state S1 and a ** changeset recorded (the "local" changeset). Then, a changeset based -** on S0 is received from another site (the "remote" changeset) and -** applied to the database. The database is then in state +** on S0 is received from another site (the "remote" changeset) and +** applied to the database. The database is then in state ** (S1+"remote"), where the exact state depends on any conflict ** resolution decisions (OMIT or REPLACE) made while applying "remote". -** Rebasing a changeset is to update it to take those conflict +** Rebasing a changeset is to update it to take those conflict ** resolution decisions into account, so that the same conflicts -** do not have to be resolved elsewhere in the network. +** do not have to be resolved elsewhere in the network. ** ** For example, if both the local and remote changesets contain an ** INSERT of the same key on "CREATE TABLE t1(a PRIMARY KEY, b)": @@ -11159,7 +12195,7 @@ SQLITE_API int sqlite3changeset_apply_v2( ** **
        **
        Local INSERT
        -** This may only conflict with a remote INSERT. If the conflict +** This may only conflict with a remote INSERT. If the conflict ** resolution was OMIT, then add an UPDATE change to the rebased ** changeset. Or, if the conflict resolution was REPLACE, add ** nothing to the rebased changeset. @@ -11183,12 +12219,12 @@ SQLITE_API int sqlite3changeset_apply_v2( ** the old.* values are rebased using the new.* values in the remote ** change. Or, if the resolution is REPLACE, then the change is copied ** into the rebased changeset with updates to columns also updated by -** the conflicting remote UPDATE removed. If this means no columns would +** the conflicting remote UPDATE removed. If this means no columns would ** be updated, the change is omitted. **
        ** -** A local change may be rebased against multiple remote changes -** simultaneously. If a single key is modified by multiple remote +** A local change may be rebased against multiple remote changes +** simultaneously. If a single key is modified by multiple remote ** changesets, they are combined as follows before the local changeset ** is rebased: ** @@ -11201,10 +12237,10 @@ SQLITE_API int sqlite3changeset_apply_v2( ** of the OMIT resolutions. **
      ** -** Note that conflict resolutions from multiple remote changesets are -** combined on a per-field basis, not per-row. This means that in the -** case of multiple remote UPDATE operations, some fields of a single -** local change may be rebased for REPLACE while others are rebased for +** Note that conflict resolutions from multiple remote changesets are +** combined on a per-field basis, not per-row. This means that in the +** case of multiple remote UPDATE operations, some fields of a single +** local change may be rebased for REPLACE while others are rebased for ** OMIT. ** ** In order to rebase a local changeset, the remote changeset must first @@ -11212,7 +12248,7 @@ SQLITE_API int sqlite3changeset_apply_v2( ** the buffer of rebase information captured. Then: ** **
        -**
      1. An sqlite3_rebaser object is created by calling +**
      2. An sqlite3_rebaser object is created by calling ** sqlite3rebaser_create(). **
      3. The new object is configured with the rebase buffer obtained from ** sqlite3changeset_apply_v2() by calling sqlite3rebaser_configure(). @@ -11233,8 +12269,8 @@ typedef struct sqlite3_rebaser sqlite3_rebaser; ** ** Allocate a new changeset rebaser object. If successful, set (*ppNew) to ** point to the new object and return SQLITE_OK. Otherwise, if an error -** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) -** to NULL. +** occurs, return an SQLite error code (e.g. SQLITE_NOMEM) and set (*ppNew) +** to NULL. */ SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); @@ -11248,9 +12284,9 @@ SQLITE_API int sqlite3rebaser_create(sqlite3_rebaser **ppNew); ** sqlite3changeset_apply_v2(). */ SQLITE_API int sqlite3rebaser_configure( - sqlite3_rebaser*, + sqlite3_rebaser*, int nRebase, const void *pRebase -); +); /* ** CAPI3REF: Rebase a changeset @@ -11260,7 +12296,7 @@ SQLITE_API int sqlite3rebaser_configure( ** in size. This function allocates and populates a buffer with a copy ** of the changeset rebased according to the configuration of the ** rebaser object passed as the first argument. If successful, (*ppOut) -** is set to point to the new buffer containing the rebased changeset and +** is set to point to the new buffer containing the rebased changeset and ** (*pnOut) to its size in bytes and SQLITE_OK returned. It is the ** responsibility of the caller to eventually free the new buffer using ** sqlite3_free(). Otherwise, if an error occurs, (*ppOut) and (*pnOut) @@ -11268,8 +12304,8 @@ SQLITE_API int sqlite3rebaser_configure( */ SQLITE_API int sqlite3rebaser_rebase( sqlite3_rebaser*, - int nIn, const void *pIn, - int *pnOut, void **ppOut + int nIn, const void *pIn, + int *pnOut, void **ppOut ); /* @@ -11280,30 +12316,30 @@ SQLITE_API int sqlite3rebaser_rebase( ** should be one call to this function for each successful invocation ** of sqlite3rebaser_create(). */ -SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); +SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); /* ** CAPI3REF: Streaming Versions of API functions. ** -** The six streaming API xxx_strm() functions serve similar purposes to the +** The six streaming API xxx_strm() functions serve similar purposes to the ** corresponding non-streaming API functions: ** ** ** -**
        Streaming functionNon-streaming equivalent
        sqlite3changeset_apply_strm[sqlite3changeset_apply] -**
        sqlite3changeset_apply_strm_v2[sqlite3changeset_apply_v2] -**
        sqlite3changeset_concat_strm[sqlite3changeset_concat] -**
        sqlite3changeset_invert_strm[sqlite3changeset_invert] -**
        sqlite3changeset_start_strm[sqlite3changeset_start] -**
        sqlite3session_changeset_strm[sqlite3session_changeset] -**
        sqlite3session_patchset_strm[sqlite3session_patchset] +**
        sqlite3changeset_apply_strm[sqlite3changeset_apply] +**
        sqlite3changeset_apply_strm_v2[sqlite3changeset_apply_v2] +**
        sqlite3changeset_concat_strm[sqlite3changeset_concat] +**
        sqlite3changeset_invert_strm[sqlite3changeset_invert] +**
        sqlite3changeset_start_strm[sqlite3changeset_start] +**
        sqlite3session_changeset_strm[sqlite3session_changeset] +**
        sqlite3session_patchset_strm[sqlite3session_patchset] **
        ** ** Non-streaming functions that accept changesets (or patchsets) as input -** require that the entire changeset be stored in a single buffer in memory. -** Similarly, those that return a changeset or patchset do so by returning -** a pointer to a single large buffer allocated using sqlite3_malloc(). -** Normally this is convenient. However, if an application running in a +** require that the entire changeset be stored in a single buffer in memory. +** Similarly, those that return a changeset or patchset do so by returning +** a pointer to a single large buffer allocated using sqlite3_malloc(). +** Normally this is convenient. However, if an application running in a ** low-memory environment is required to handle very large changesets, the ** large contiguous memory allocations required can become onerous. ** @@ -11325,12 +12361,12 @@ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); ** ** ** Each time the xInput callback is invoked by the sessions module, the first -** argument passed is a copy of the supplied pIn context pointer. The second -** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no -** error occurs the xInput method should copy up to (*pnData) bytes of data -** into the buffer and set (*pnData) to the actual number of bytes copied -** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) -** should be set to zero to indicate this. Or, if an error occurs, an SQLite +** argument passed is a copy of the supplied pIn context pointer. The second +** argument, pData, points to a buffer (*pnData) bytes in size. Assuming no +** error occurs the xInput method should copy up to (*pnData) bytes of data +** into the buffer and set (*pnData) to the actual number of bytes copied +** before returning SQLITE_OK. If the input is completely exhausted, (*pnData) +** should be set to zero to indicate this. Or, if an error occurs, an SQLite ** error code should be returned. In all cases, if an xInput callback returns ** an error, all processing is abandoned and the streaming API function ** returns a copy of the error code to the caller. @@ -11338,7 +12374,7 @@ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); ** In the case of sqlite3changeset_start_strm(), the xInput callback may be ** invoked by the sessions module at any point during the lifetime of the ** iterator. If such an xInput callback returns an error, the iterator enters -** an error state, whereby all subsequent calls to iterator functions +** an error state, whereby all subsequent calls to iterator functions ** immediately fail with the same error code as returned by xInput. ** ** Similarly, streaming API functions that return changesets (or patchsets) @@ -11368,7 +12404,7 @@ SQLITE_API void sqlite3rebaser_delete(sqlite3_rebaser *p); ** is immediately abandoned and the streaming API function returns a copy ** of the xOutput error code to the application. ** -** The sessions module never invokes an xOutput callback with the third +** The sessions module never invokes an xOutput callback with the third ** parameter set to a value less than or equal to zero. Other than this, ** no guarantees are made as to the size of the chunks of data returned. */ @@ -11439,12 +12475,12 @@ SQLITE_API int sqlite3session_patchset_strm( int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); -SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, +SQLITE_API int sqlite3changegroup_add_strm(sqlite3_changegroup*, int (*xInput)(void *pIn, void *pData, int *pnData), void *pIn ); SQLITE_API int sqlite3changegroup_output_strm(sqlite3_changegroup*, - int (*xOutput)(void *pOut, const void *pData, int nData), + int (*xOutput)(void *pOut, const void *pData, int nData), void *pOut ); SQLITE_API int sqlite3rebaser_rebase_strm( @@ -11459,16 +12495,16 @@ SQLITE_API int sqlite3rebaser_rebase_strm( ** CAPI3REF: Configure global parameters ** ** The sqlite3session_config() interface is used to make global configuration -** changes to the sessions module in order to tune it to the specific needs +** changes to the sessions module in order to tune it to the specific needs ** of the application. ** ** The sqlite3session_config() interface is not threadsafe. If it is invoked ** while any other thread is inside any other sessions method then the ** results are undefined. Furthermore, if it is invoked after any sessions -** related objects have been created, the results are also undefined. +** related objects have been created, the results are also undefined. ** ** The first argument to the sqlite3session_config() function must be one -** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The +** of the SQLITE_SESSION_CONFIG_XXX constants defined below. The ** interpretation of the (void*) value passed as the second parameter and ** the effect of calling this function depends on the value of the first ** parameter. @@ -11518,7 +12554,7 @@ SQLITE_API int sqlite3session_config(int op, void *pArg); ** ****************************************************************************** ** -** Interfaces to extend FTS5. Using the interfaces defined in this file, +** Interfaces to extend FTS5. Using the interfaces defined in this file, ** FTS5 may be extended with: ** ** * custom tokenizers, and @@ -11562,19 +12598,19 @@ struct Fts5PhraseIter { ** EXTENSION API FUNCTIONS ** ** xUserData(pFts): -** Return a copy of the context pointer the extension function was +** Return a copy of the context pointer the extension function was ** registered with. ** ** xColumnTotalSize(pFts, iCol, pnToken): ** If parameter iCol is less than zero, set output variable *pnToken ** to the total number of tokens in the FTS5 table. Or, if iCol is ** non-negative but less than the number of columns in the table, return -** the total number of tokens in column iCol, considering all rows in +** the total number of tokens in column iCol, considering all rows in ** the FTS5 table. ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** xColumnCount(pFts): @@ -11588,7 +12624,7 @@ struct Fts5PhraseIter { ** ** If parameter iCol is greater than or equal to the number of columns ** in the table, SQLITE_RANGE is returned. Or, if an error occurs (e.g. -** an OOM condition or IO error), an appropriate SQLite error code is +** an OOM condition or IO error), an appropriate SQLite error code is ** returned. ** ** This function may be quite inefficient if used with an FTS5 table @@ -11615,8 +12651,8 @@ struct Fts5PhraseIter { ** an error code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always returns 0. ** ** xInst: @@ -11631,7 +12667,7 @@ struct Fts5PhraseIter { ** code (i.e. SQLITE_NOMEM) if an error occurs. ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. +** "detail=none" or "detail=column" option. ** ** xRowid: ** Returns the rowid of the current row. @@ -11647,11 +12683,11 @@ struct Fts5PhraseIter { ** ** with $p set to a phrase equivalent to the phrase iPhrase of the ** current query is executed. Any column filter that applies to -** phrase iPhrase of the current query is included in $p. For each -** row visited, the callback function passed as the fourth argument -** is invoked. The context and API objects passed to the callback +** phrase iPhrase of the current query is included in $p. For each +** row visited, the callback function passed as the fourth argument +** is invoked. The context and API objects passed to the callback ** function may be used to access the properties of each matched row. -** Invoking Api.xUserData() returns a copy of the pointer passed as +** Invoking Api.xUserData() returns a copy of the pointer passed as ** the third argument to pUserData. ** ** If the callback function returns any value other than SQLITE_OK, the @@ -11666,14 +12702,14 @@ struct Fts5PhraseIter { ** ** xSetAuxdata(pFts5, pAux, xDelete) ** -** Save the pointer passed as the second argument as the extension function's +** Save the pointer passed as the second argument as the extension function's ** "auxiliary data". The pointer may then be retrieved by the current or any ** future invocation of the same fts5 extension function made as part of ** the same MATCH query using the xGetAuxdata() API. ** ** Each extension function is allocated a single auxiliary data slot for -** each FTS query (MATCH expression). If the extension function is invoked -** more than once for a single FTS query, then all invocations share a +** each FTS query (MATCH expression). If the extension function is invoked +** more than once for a single FTS query, then all invocations share a ** single auxiliary data context. ** ** If there is already an auxiliary data pointer when this function is @@ -11692,7 +12728,7 @@ struct Fts5PhraseIter { ** ** xGetAuxdata(pFts5, bClear) ** -** Returns the current auxiliary data pointer for the fts5 extension +** Returns the current auxiliary data pointer for the fts5 extension ** function. See the xSetAuxdata() method for details. ** ** If the bClear argument is non-zero, then the auxiliary data is cleared @@ -11712,7 +12748,7 @@ struct Fts5PhraseIter { ** method, to iterate through all instances of a single query phrase within ** the current row. This is the same information as is accessible via the ** xInstCount/xInst APIs. While the xInstCount/xInst APIs are more convenient -** to use, this API may be faster under some circumstances. To iterate +** to use, this API may be faster under some circumstances. To iterate ** through instances of phrase iPhrase, use the following code: ** ** Fts5PhraseIter iter; @@ -11730,8 +12766,8 @@ struct Fts5PhraseIter { ** xPhraseFirstColumn() and xPhraseNextColumn() as illustrated below). ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" or "detail=column" option. If the FTS5 table is created -** with either "detail=none" or "detail=column" and "content=" option +** "detail=none" or "detail=column" option. If the FTS5 table is created +** with either "detail=none" or "detail=column" and "content=" option ** (i.e. if it is a contentless table), then this API always iterates ** through an empty set (all calls to xPhraseFirst() set iCol to -1). ** @@ -11755,22 +12791,22 @@ struct Fts5PhraseIter { ** } ** ** This API can be quite slow if used with an FTS5 table created with the -** "detail=none" option. If the FTS5 table is created with either -** "detail=none" "content=" option (i.e. if it is a contentless table), -** then this API always iterates through an empty set (all calls to +** "detail=none" option. If the FTS5 table is created with either +** "detail=none" "content=" option (i.e. if it is a contentless table), +** then this API always iterates through an empty set (all calls to ** xPhraseFirstColumn() set iCol to -1). ** ** The information accessed using this API and its companion ** xPhraseFirstColumn() may also be obtained using xPhraseFirst/xPhraseNext ** (or xInst/xInstCount). The chief advantage of this API is that it is ** significantly more efficient than those alternatives when used with -** "detail=column" tables. +** "detail=column" tables. ** ** xPhraseNextColumn() ** See xPhraseFirstColumn above. */ struct Fts5ExtensionApi { - int iVersion; /* Currently always set to 3 */ + int iVersion; /* Currently always set to 2 */ void *(*xUserData)(Fts5Context*); @@ -11778,7 +12814,7 @@ struct Fts5ExtensionApi { int (*xRowCount)(Fts5Context*, sqlite3_int64 *pnRow); int (*xColumnTotalSize)(Fts5Context*, int iCol, sqlite3_int64 *pnToken); - int (*xTokenize)(Fts5Context*, + int (*xTokenize)(Fts5Context*, const char *pText, int nText, /* Text to tokenize */ void *pCtx, /* Context passed to xToken() */ int (*xToken)(void*, int, const char*, int, int, int) /* Callback */ @@ -11807,15 +12843,15 @@ struct Fts5ExtensionApi { void (*xPhraseNextColumn)(Fts5Context*, Fts5PhraseIter*, int *piCol); }; -/* +/* ** CUSTOM AUXILIARY FUNCTIONS *************************************************************************/ /************************************************************************* ** CUSTOM TOKENIZERS ** -** Applications may also register custom tokenizer types. A tokenizer -** is registered by providing fts5 with a populated instance of the +** Applications may also register custom tokenizer types. A tokenizer +** is registered by providing fts5 with a populated instance of the ** following structure. All structure methods must be defined, setting ** any member of the fts5_tokenizer struct to NULL leads to undefined ** behaviour. The structure methods are expected to function as follows: @@ -11826,16 +12862,16 @@ struct Fts5ExtensionApi { ** ** The first argument passed to this function is a copy of the (void*) ** pointer provided by the application when the fts5_tokenizer object -** was registered with FTS5 (the third argument to xCreateTokenizer()). +** was registered with FTS5 (the third argument to xCreateTokenizer()). ** The second and third arguments are an array of nul-terminated strings ** containing the tokenizer arguments, if any, specified following the ** tokenizer name as part of the CREATE VIRTUAL TABLE statement used ** to create the FTS5 table. ** -** The final argument is an output variable. If successful, (*ppOut) +** The final argument is an output variable. If successful, (*ppOut) ** should be set to point to the new tokenizer handle and SQLITE_OK ** returned. If an error occurs, some value other than SQLITE_OK should -** be returned. In this case, fts5 assumes that the final value of *ppOut +** be returned. In this case, fts5 assumes that the final value of *ppOut ** is undefined. ** ** xDelete: @@ -11844,7 +12880,7 @@ struct Fts5ExtensionApi { ** be invoked exactly once for each successful call to xCreate(). ** ** xTokenize: -** This function is expected to tokenize the nText byte string indicated +** This function is expected to tokenize the nText byte string indicated ** by argument pText. pText may or may not be nul-terminated. The first ** argument passed to this function is a pointer to an Fts5Tokenizer object ** returned by an earlier call to xCreate(). @@ -11858,8 +12894,8 @@ struct Fts5ExtensionApi { ** determine the set of tokens to add to (or delete from) the ** FTS index. ** -**
      4. FTS5_TOKENIZE_QUERY - A MATCH query is being executed -** against the FTS index. The tokenizer is being called to tokenize +**
      5. FTS5_TOKENIZE_QUERY - A MATCH query is being executed +** against the FTS index. The tokenizer is being called to tokenize ** a bareword or quoted string specified as part of the query. ** **
      6. (FTS5_TOKENIZE_QUERY | FTS5_TOKENIZE_PREFIX) - Same as @@ -11867,10 +12903,10 @@ struct Fts5ExtensionApi { ** followed by a "*" character, indicating that the last token ** returned by the tokenizer will be treated as a token prefix. ** -**
      7. FTS5_TOKENIZE_AUX - The tokenizer is being invoked to +**
      8. FTS5_TOKENIZE_AUX - The tokenizer is being invoked to ** satisfy an fts5_api.xTokenize() request made by an auxiliary ** function. Or an fts5_api.xColumnSize() request made by the same -** on a columnsize=0 database. +** on a columnsize=0 database. **
    ** ** For each token in the input string, the supplied callback xToken() must @@ -11882,10 +12918,10 @@ struct Fts5ExtensionApi { ** which the token is derived within the input. ** ** The second argument passed to the xToken() callback ("tflags") should -** normally be set to 0. The exception is if the tokenizer supports +** normally be set to 0. The exception is if the tokenizer supports ** synonyms. In this case see the discussion below for details. ** -** FTS5 assumes the xToken() callback is invoked for each token in the +** FTS5 assumes the xToken() callback is invoked for each token in the ** order that they occur within the input text. ** ** If an xToken() callback returns any value other than SQLITE_OK, then @@ -11899,7 +12935,7 @@ struct Fts5ExtensionApi { ** SYNONYM SUPPORT ** ** Custom tokenizers may also support synonyms. Consider a case in which a -** user wishes to query for a phrase such as "first place". Using the +** user wishes to query for a phrase such as "first place". Using the ** built-in tokenizers, the FTS5 query 'first + place' will match instances ** of "first place" within the document set, but not alternative forms ** such as "1st place". In some applications, it would be better to match @@ -11919,34 +12955,34 @@ struct Fts5ExtensionApi { ** **
  • By querying the index for all synonyms of each query term ** separately. In this case, when tokenizing query text, the -** tokenizer may provide multiple synonyms for a single term -** within the document. FTS5 then queries the index for each +** tokenizer may provide multiple synonyms for a single term +** within the document. FTS5 then queries the index for each ** synonym individually. For example, faced with the query: ** ** ** ... MATCH 'first place' ** ** the tokenizer offers both "1st" and "first" as synonyms for the -** first token in the MATCH query and FTS5 effectively runs a query +** first token in the MATCH query and FTS5 effectively runs a query ** similar to: ** ** ** ... MATCH '(first OR 1st) place' ** ** except that, for the purposes of auxiliary functions, the query -** still appears to contain just two phrases - "(first OR 1st)" +** still appears to contain just two phrases - "(first OR 1st)" ** being treated as a single phrase. ** **
  • By adding multiple synonyms for a single term to the FTS index. ** Using this method, when tokenizing document text, the tokenizer -** provides multiple synonyms for each token. So that when a +** provides multiple synonyms for each token. So that when a ** document such as "I won first place" is tokenized, entries are ** added to the FTS index for "i", "won", "first", "1st" and ** "place". ** ** This way, even if the tokenizer does not provide synonyms ** when tokenizing query text (it should not - to do so would be -** inefficient), it doesn't matter if the user queries for +** inefficient), it doesn't matter if the user queries for ** 'first + place' or '1st + place', as there are entries in the ** FTS index corresponding to both forms of the first token. ** @@ -11967,11 +13003,11 @@ struct Fts5ExtensionApi { ** ** It is an error to specify the FTS5_TOKEN_COLOCATED flag the first time ** xToken() is called. Multiple synonyms may be specified for a single token -** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. +** by making multiple calls to xToken(FTS5_TOKEN_COLOCATED) in sequence. ** There is no limit to the number of synonyms that may be provided for a ** single token. ** -** In many cases, method (1) above is the best approach. It does not add +** In many cases, method (1) above is the best approach. It does not add ** extra data to the FTS index or require FTS5 to query for multiple terms, ** so it is efficient in terms of disk space and query speed. However, it ** does not support prefix queries very well. If, as suggested above, the @@ -11983,24 +13019,24 @@ struct Fts5ExtensionApi { ** will not match documents that contain the token "1st" (as the tokenizer ** will probably not map "1s" to any prefix of "first"). ** -** For full prefix support, method (3) may be preferred. In this case, +** For full prefix support, method (3) may be preferred. In this case, ** because the index contains entries for both "first" and "1st", prefix ** queries such as 'fi*' or '1s*' will match correctly. However, because ** extra entries are added to the FTS index, this method uses more space ** within the database. ** ** Method (2) offers a midpoint between (1) and (3). Using this method, -** a query such as '1s*' will match documents that contain the literal +** a query such as '1s*' will match documents that contain the literal ** token "1st", but not "first" (assuming the tokenizer is not able to ** provide synonyms for prefixes). However, a non-prefix query like '1st' ** will match against "1st" and "first". This method does not require -** extra disk space, as no extra entries are added to the FTS index. +** extra disk space, as no extra entries are added to the FTS index. ** On the other hand, it may require more CPU cycles to run MATCH queries, ** as separate queries of the FTS index are required for each synonym. ** ** When using methods (2) or (3), it is important that the tokenizer only -** provide synonyms when tokenizing document text (method (2)) or query -** text (method (3)), not both. Doing so will not cause any errors, but is +** provide synonyms when tokenizing document text (method (3)) or query +** text (method (2)), not both. Doing so will not cause any errors, but is ** inefficient. */ typedef struct Fts5Tokenizer Fts5Tokenizer; @@ -12008,10 +13044,10 @@ typedef struct fts5_tokenizer fts5_tokenizer; struct fts5_tokenizer { int (*xCreate)(void*, const char **azArg, int nArg, Fts5Tokenizer **ppOut); void (*xDelete)(Fts5Tokenizer*); - int (*xTokenize)(Fts5Tokenizer*, + int (*xTokenize)(Fts5Tokenizer*, void *pCtx, int flags, /* Mask of FTS5_TOKENIZE_* flags */ - const char *pText, int nText, + const char *pText, int nText, int (*xToken)( void *pCtx, /* Copy of 2nd argument to xTokenize() */ int tflags, /* Mask of FTS5_TOKEN_* flags */ @@ -12048,7 +13084,7 @@ struct fts5_api { int (*xCreateTokenizer)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_tokenizer *pTokenizer, void (*xDestroy)(void*) ); @@ -12057,7 +13093,7 @@ struct fts5_api { int (*xFindTokenizer)( fts5_api *pApi, const char *zName, - void **ppContext, + void **ppUserData, fts5_tokenizer *pTokenizer ); @@ -12065,7 +13101,7 @@ struct fts5_api { int (*xCreateFunction)( fts5_api *pApi, const char *zName, - void *pContext, + void *pUserData, fts5_extension_function xFunction, void (*xDestroy)(void*) ); diff --git a/sqlite/sqlite3ext.h b/sqlite/sqlite3ext.h index bdd0a85ed..711638099 100644 --- a/sqlite/sqlite3ext.h +++ b/sqlite/sqlite3ext.h @@ -330,6 +330,39 @@ struct sqlite3_api_routines { const char *(*filename_database)(const char*); const char *(*filename_journal)(const char*); const char *(*filename_wal)(const char*); + /* Version 3.32.0 and later */ + const char *(*create_filename)(const char*,const char*,const char*, + int,const char**); + void (*free_filename)(const char*); + sqlite3_file *(*database_file_object)(const char*); + /* Version 3.34.0 and later */ + int (*txn_state)(sqlite3*,const char*); + /* Version 3.36.1 and later */ + sqlite3_int64 (*changes64)(sqlite3*); + sqlite3_int64 (*total_changes64)(sqlite3*); + /* Version 3.37.0 and later */ + int (*autovacuum_pages)(sqlite3*, + unsigned int(*)(void*,const char*,unsigned int,unsigned int,unsigned int), + void*, void(*)(void*)); + /* Version 3.38.0 and later */ + int (*error_offset)(sqlite3*); + int (*vtab_rhs_value)(sqlite3_index_info*,int,sqlite3_value**); + int (*vtab_distinct)(sqlite3_index_info*); + int (*vtab_in)(sqlite3_index_info*,int,int); + int (*vtab_in_first)(sqlite3_value*,sqlite3_value**); + int (*vtab_in_next)(sqlite3_value*,sqlite3_value**); + /* Version 3.39.0 and later */ + int (*deserialize)(sqlite3*,const char*,unsigned char*, + sqlite3_int64,sqlite3_int64,unsigned); + unsigned char *(*serialize)(sqlite3*,const char *,sqlite3_int64*, + unsigned int); + const char *(*db_name)(sqlite3*,int); + /* Version 3.40.0 and later */ + int (*value_encoding)(sqlite3_value*); + /* Version 3.41.0 and later */ + int (*is_interrupted)(sqlite3*); + /* Version 3.43.0 and later */ + int (*stmt_explain)(sqlite3_stmt*,int); }; /* @@ -630,6 +663,36 @@ typedef int (*sqlite3_loadext_entry)( #define sqlite3_filename_database sqlite3_api->filename_database #define sqlite3_filename_journal sqlite3_api->filename_journal #define sqlite3_filename_wal sqlite3_api->filename_wal +/* Version 3.32.0 and later */ +#define sqlite3_create_filename sqlite3_api->create_filename +#define sqlite3_free_filename sqlite3_api->free_filename +#define sqlite3_database_file_object sqlite3_api->database_file_object +/* Version 3.34.0 and later */ +#define sqlite3_txn_state sqlite3_api->txn_state +/* Version 3.36.1 and later */ +#define sqlite3_changes64 sqlite3_api->changes64 +#define sqlite3_total_changes64 sqlite3_api->total_changes64 +/* Version 3.37.0 and later */ +#define sqlite3_autovacuum_pages sqlite3_api->autovacuum_pages +/* Version 3.38.0 and later */ +#define sqlite3_error_offset sqlite3_api->error_offset +#define sqlite3_vtab_rhs_value sqlite3_api->vtab_rhs_value +#define sqlite3_vtab_distinct sqlite3_api->vtab_distinct +#define sqlite3_vtab_in sqlite3_api->vtab_in +#define sqlite3_vtab_in_first sqlite3_api->vtab_in_first +#define sqlite3_vtab_in_next sqlite3_api->vtab_in_next +/* Version 3.39.0 and later */ +#ifndef SQLITE_OMIT_DESERIALIZE +#define sqlite3_deserialize sqlite3_api->deserialize +#define sqlite3_serialize sqlite3_api->serialize +#endif +#define sqlite3_db_name sqlite3_api->db_name +/* Version 3.40.0 and later */ +#define sqlite3_value_encoding sqlite3_api->value_encoding +/* Version 3.41.0 and later */ +#define sqlite3_is_interrupted sqlite3_api->is_interrupted +/* Version 3.43.0 and later */ +#define sqlite3_stmt_explain sqlite3_api->stmt_explain #endif /* !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) */ #if !defined(SQLITE_CORE) && !defined(SQLITE_OMIT_LOAD_EXTENSION) From 19e325d4b84b4de00e67898e497e257bc5de2fc9 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 5 Oct 2023 19:57:49 +0200 Subject: [PATCH 297/469] Use memset instead --- urbackupcommon/CompressedPipeZstd.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urbackupcommon/CompressedPipeZstd.cpp b/urbackupcommon/CompressedPipeZstd.cpp index 4403272a8..c3ed596bc 100644 --- a/urbackupcommon/CompressedPipeZstd.cpp +++ b/urbackupcommon/CompressedPipeZstd.cpp @@ -743,8 +743,8 @@ bool CompressedPipeZstd::setCompressionSettings(const SCompressionSettings& para usage_add += "adaptive flush_timeout="+convert(params.adaptive_comp_flush_timeout)+" n_threads="+convert(n_threads); adaptive->flush_timeout = params.adaptive_comp_flush_timeout; - adaptive->zfp_prev = {}; - adaptive->zfp_prev_corr = {}; + memset(&adaptive->zfp_prev, 0, sizeof(ZSTD_frameProgression)); + memset(&adaptive->zfp_prev_corr, 0, sizeof(ZSTD_frameProgression)); adaptive->n_zstd_workers = n_threads; adaptive->zstd_input_blocked = 0; adaptive->zstd_input_presented = 0; From bbfe4b44aa6ca0fb6729347f2fce6018a4be8898 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 5 Oct 2023 21:42:10 +0200 Subject: [PATCH 298/469] Fixup main definition --- sqlite/shell.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sqlite/shell.c b/sqlite/shell.c index df3d56f1a..f13f4d9c6 100644 --- a/sqlite/shell.c +++ b/sqlite/shell.c @@ -27849,7 +27849,7 @@ static void sayAbnormalExit(void){ #endif #if SQLITE_SHELL_IS_UTF8 -int SQLITE_CDECL main(int argc, char **argv){ +int SQLITE_CDECL shell_main(int argc, char **argv){ #else int SQLITE_CDECL shell_wmain(int argc, wchar_t **wargv){ char **argv; From 56cfd646435aac5782186f804ae1892b7c1feaab Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 19 Oct 2023 21:40:49 +0200 Subject: [PATCH 299/469] Only delete snapshot from db if it has been deleted successfully --- urbackupclient/client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 67b810a53..e933fdbb8 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -3865,7 +3865,7 @@ bool IndexThread::release_shadowcopy(SCDirs *dir, bool for_imagebackup, int save VSSLog("Deleting shadowcopy for path \""+dir->target+"\" -2", LL_DEBUG); ok = deleteShadowcopy(dir); - if(dir->ref->save_id!=-1) + if(ok && dir->ref->save_id!=-1) { cd->deleteShadowcopy(dir->ref->save_id); } From e014f35c27e24b2be9b2516c5db776020bfa38de Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 28 Nov 2023 20:34:47 +0100 Subject: [PATCH 300/469] Fix adding root directory via command line on Linux Also fix removing directory again --- clientctl/main.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/clientctl/main.cpp b/clientctl/main.cpp index 9aa8e76f8..d171498ab 100644 --- a/clientctl/main.cpp +++ b/clientctl/main.cpp @@ -1258,7 +1258,7 @@ int action_add_backupdir(std::vector args) new_dir.path = os_get_final_path(new_dir.path); } - if (!new_dir.path.empty() && new_dir.path[new_dir.path.size() - 1] == os_file_sep()[0]) + if (new_dir.path.size()>1 && new_dir.path[new_dir.path.size() - 1] == os_file_sep()[0]) { new_dir.path.erase(new_dir.path.size() - 1, 1); } @@ -1518,7 +1518,7 @@ int action_remove_backupdir(std::vector args) } bool del_ok = false; - bool del_server_default = true; + bool del_server_default = false; for (size_t i = 0; i < backup_dirs.size();) { @@ -1528,6 +1528,7 @@ int action_remove_backupdir(std::vector args) if (backup_dirs[i].server_default) { del_server_default = true; + ++i; } else { @@ -1541,6 +1542,7 @@ int action_remove_backupdir(std::vector args) if (backup_dirs[i].server_default) { del_server_default = true; + ++i; } else { From c3447a0c88461e26c309fa7d180d02105c902564 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 28 Nov 2023 20:35:13 +0100 Subject: [PATCH 301/469] Disable locking session to user-agent and IP per default --- urbackupserver/cmdline_preprocessor.cpp | 13 +++++++++++++ urbackupserver/serverinterface/helper.cpp | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/urbackupserver/cmdline_preprocessor.cpp b/urbackupserver/cmdline_preprocessor.cpp index 708c3fd25..fea63904e 100644 --- a/urbackupserver/cmdline_preprocessor.cpp +++ b/urbackupserver/cmdline_preprocessor.cpp @@ -423,6 +423,19 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("0"); } } + if (settings->getValue("LOCK_SESSION_TO_IP_AND_USER_AGENT", &val)) + { + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "1" || + strlower(val) == "true" || + strlower(val) == "yes")) + { + real_args.push_back("--lock_session_to_ip_and_user_agent"); + real_args.push_back("1"); + } + } } if(destroy_server) diff --git a/urbackupserver/serverinterface/helper.cpp b/urbackupserver/serverinterface/helper.cpp index b872d2995..edd835da6 100644 --- a/urbackupserver/serverinterface/helper.cpp +++ b/urbackupserver/serverinterface/helper.cpp @@ -262,6 +262,11 @@ bool Helper::failedLoginRateLimit() std::string Helper::getIdentData() { + static bool lock_session = Server->getServerParameter("lock_session_to_ip_and_user_agent") == "1"; + + if (!lock_session) + return std::string(); + std::string user_agent = (*PARAMS)["HTTP_USER_AGENT"]; return remoteAddr() + user_agent; } From ba40b9966c7fede2628c172fe484899ae8ce22fd Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 22 Dec 2023 21:22:56 +0100 Subject: [PATCH 302/469] Fix archive settings migration --- urbackupserver/ClientMain.cpp | 7 ++ urbackupserver/dllmain.cpp | 124 ++++++++++++++++++-- urbackupserver/server_archive.cpp | 7 +- urbackupserver/serverinterface/settings.cpp | 78 ++++++------ 4 files changed, 166 insertions(+), 50 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index d6a4982a2..894d221ae 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -1177,6 +1177,13 @@ int ClientMain::getClientID(IDatabase *db, const std::string &clientname, Server q_insert_setting->Write(); q_insert_setting->Reset(); + q_insert_setting->Bind("archive_update"); + q_insert_setting->Bind("1"); + q_insert_setting->Bind(rid); + q_insert_setting->Bind(c_use_value); + q_insert_setting->Write(); + q_insert_setting->Reset(); + if (client_group != NULL) { q_insert_setting->Bind("group_id"); diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index 670d2db73..cfd7a14f6 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2194,16 +2194,16 @@ std::string archiveSettingsParamStr(IDatabase* db, int clientid, std::string pre std::string idx = "_" + prefix + convert(i); if (!ret.empty()) ret += "&"; - ret += "every"+ idx +"=" + res[i]["interval"]; - ret += "&every_unit" + idx + "=" + res[i]["interval_unit"]; - ret += "&for" + idx + "=" + res[i]["interval_unit"]; - ret += "&for_unit" + idx + "=" + res[i]["interval_unit"]; - ret += "&backup_types" + idx + "=" + ServerAutomaticArchive::getBackupType(watoi(res[i]["backup_types"])); - ret += "&archive_window" + idx + "=" + res[i]["archive_window"]; - ret += "&letters" + idx + "=" + res[i]["letters"]; + ret += "every"+ idx +"=" + EscapeParamString(res[i]["interval"]); + ret += "&every_unit" + idx + "=" + EscapeParamString(res[i]["interval_unit"]); + ret += "&for" + idx + "=" + EscapeParamString(res[i]["length"]); + ret += "&for_unit" + idx + "=" + EscapeParamString(res[i]["length_unit"]); + ret += "&backup_type" + idx + "=" + EscapeParamString(ServerAutomaticArchive::getBackupType(watoi(res[i]["backup_types"]))); + ret += "&archive_window" + idx + "=" + EscapeParamString(res[i]["archive_window"]); + ret += "&letters" + idx + "=" + EscapeParamString(res[i]["letters"]); ret += "&uuid"+idx+"=" + bytesToHex(uuid); - q_set_uuid->Bind(uuid); + q_set_uuid->Bind(uuid.data(), uuid.size()); q_set_uuid->Bind(res[i]["id"]); q_set_uuid->Write(); q_set_uuid->Reset(); @@ -2212,6 +2212,63 @@ std::string archiveSettingsParamStr(IDatabase* db, int clientid, std::string pre return ret; } +std::string fixArchiveMigration(IDatabase* db, const std::string& archive_str) +{ + str_map archive_settings; + ParseParamStrHttp(archive_str, &archive_settings); + + IQuery* q_find = db->Prepare("SELECT length, length_unit FROM settings_db.automatic_archival WHERE uuid=?"); + + for (str_map::iterator it = archive_settings.begin(); + it != archive_settings.end(); ++it) + { + if (next(it->first, 0, "backup_types_")) + { + std::string idx = getafter("backup_types_", it->first); + + auto it_for = archive_settings.find("for_" + idx); + auto it_for_unit = archive_settings.find("for_unit_" + idx); + auto it_uuid = archive_settings.find("uuid_" + idx); + if (it_uuid != archive_settings.end() && + it_for != archive_settings.end() && + it_for_unit != archive_settings.end() && + it_for->second == it_for_unit->second) + { + q_find->Bind(it_uuid->second.data(), it_uuid->second.size()); + db_results res = q_find->Read(); + q_find->Reset(); + + if (!res.empty()) + { + archive_settings["for_" + idx] = res[0]["length"]; + archive_settings["for_unit_" + idx] = res[0]["length_unit"]; + } + } + } + } + + std::string ret; + for (str_map::iterator it = archive_settings.begin(); + it != archive_settings.end(); ++it) + { + if (!ret.empty()) + ret += "&"; + + if (next(it->first, 0, "backup_types_")) + { + std::string idx = getafter("backup_types_", it->first); + + ret += "backup_type_" + idx + "=" + EscapeParamString(it->second); + } + else + { + ret += it->first + "=" + EscapeParamString(it->second); + } + } + + return ret; +} + bool upgrade60_61() { IDatabase *db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); @@ -2348,6 +2405,48 @@ bool upgrade65_66() return db->Write(std::string("UPDATE settings_db.settings SET use=") + c_use_value_str + " WHERE key='virtual_clients_add'"); } +bool upgrade66_67() +{ + IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); + + db_results res = db->Read("SELECT rowid, value FROM settings_db.settings WHERE key='archive'"); + + IQuery* q_update = db->Prepare("UPDATE settings_db.settings SET value=? WHERE rowid=?"); + + if (!db->Write("UPDATE settings_db.automatic_archival SET uuid=unhex(hex(uuid))")) + return false; + + bool ret = true; + + for (size_t i = 0; i < res.size(); ++i) + { + std::string new_res = fixArchiveMigration(db, res[i]["value"]); + if (new_res != res[i]["value"]) + { + q_update->Bind(new_res); + q_update->Bind(res[i]["rowid"]); + ret &= q_update->Write(); + q_update->Reset(); + } + } + + IQuery* q_insert_setting = db->Prepare("INSERT INTO settings_db.settings (key, value, clientid, use) VALUES (?, ?, ?, ?)"); + db_results res_clients = db->Read("SELECT id FROM clients"); + for (size_t i = 0; i < res_clients.size(); ++i) + { + const int clientid = watoi(res_clients[i]["id"]); + + q_insert_setting->Bind("archive_update"); + q_insert_setting->Bind("1"); + q_insert_setting->Bind(clientid); + q_insert_setting->Bind(0); + ret &= q_insert_setting->Write(); + q_insert_setting->Reset(); + } + + return true; +} + void upgrade(void) { Server->destroyAllDatabases(); @@ -2369,7 +2468,7 @@ void upgrade(void) int ver=watoi(res_v[0]["tvalue"]); int old_v; - int max_v=66; + int max_v=67; { IScopedLock lock(startup_status.mutex); startup_status.target_db_version=max_v; @@ -2775,6 +2874,13 @@ void upgrade(void) } ++ver; break; + case 66: + if (!upgrade66_67()) + { + has_error = true; + } + ++ver; + break; default: break; } diff --git a/urbackupserver/server_archive.cpp b/urbackupserver/server_archive.cpp index ec29e8896..31cb8955b 100644 --- a/urbackupserver/server_archive.cpp +++ b/urbackupserver/server_archive.cpp @@ -370,8 +370,8 @@ void ServerAutomaticArchive::updateArchiveSettings(int clientid) IQuery *q_next = db->Prepare("SELECT next_archival FROM settings_db.automatic_archival WHERE clientid=? AND uuid=?"); - IQuery *q_insert_all = db->Prepare("INSERT INTO settings_db.automatic_archival (next_archival, interval, interval_unit, length, length_unit, backup_types, clientid, archive_window, letters)" - "VALUES (?,?,?,?,?,?,?,?,?)"); + IQuery *q_insert_all = db->Prepare("INSERT INTO settings_db.automatic_archival (next_archival, interval, interval_unit, length, length_unit, backup_types, clientid, archive_window, letters, uuid)" + "VALUES (?,?,?,?,?,?,?,?,?,?)"); std::string prefix = "d"; std::string idx; @@ -392,7 +392,7 @@ void ServerAutomaticArchive::updateArchiveSettings(int clientid) archive.uuid = hexToBytes(params["uuid_" + idx]); q_next->Bind(clientid); - q_next->Bind(archive.uuid); + q_next->Bind(archive.uuid.data(), archive.uuid.size()); db_results res_next = q_next->Read(); @@ -433,6 +433,7 @@ void ServerAutomaticArchive::updateArchiveSettings(int clientid) q_insert_all->Bind(clientid); q_insert_all->Bind(archive.window); q_insert_all->Bind(archive.letters); + q_insert_all->Bind(archive.uuid.data(), archive.uuid.size()); q_insert_all->Write(); q_insert_all->Reset(); } diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index 678b7cb18..fb5e6acae 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -124,15 +124,53 @@ JSON::Array getAlertScripts(IDatabase* db) return ret; } +std::string addNextArchival(IDatabase* db, int clientid, IQuery* get_next, std::string archive_str) +{ + str_map params; + ParseParamStrHttp(archive_str, ¶ms); + + for (str_map::iterator it = params.begin(); it != params.end(); ++it) + { + if (next(it->first, 0, "uuid_")) + { + std::string idx = getafter("uuid_", it->first); + std::string buuid = hexToBytes(it->second); + + get_next->Bind(clientid); + get_next->Bind(buuid.data(), buuid.size()); + db_results res = get_next->Read(); + get_next->Reset(); + + if (!res.empty()) + { + int64 next_archival = watoi64(res[0]["next_archival"]); + archive_str += "&next_archival_" + idx + "=" + res[0]["next_archival"]; + archive_str += "&timeleft_" + idx + "=" + convert(next_archival - (_i64)Server->getTimeSeconds()); + } + } + } + + return archive_str; +} + JSON::Object getJSONClientSettings(IDatabase* db, int t_clientid) { std::map settings = ServerSettings::getClientSettings(db, t_clientid); + IQuery* get_next = db->Prepare("SELECT next_archival FROM settings_db.automatic_archival WHERE clientid=? AND uuid=?"); + JSON::Object ret; for (std::map::iterator it = settings.begin(); it != settings.end(); ++it) { + if (it->first == "archive") + { + it->second.value = addNextArchival(db, t_clientid, get_next, it->second.value.getString()); + it->second.value_client = addNextArchival(db, t_clientid, get_next, it->second.value_client.getString()); + it->second.value_group = addNextArchival(db, t_clientid, get_next, it->second.value_group.getString()); + } + JSON::Object jobj; if (it->second.use != -1) jobj.set("use", it->second.use); @@ -149,42 +187,6 @@ JSON::Object getJSONClientSettings(IDatabase* db, int t_clientid) return ret; } -void addNextArchival(IDatabase* db, int clientid, JSON::Object& obj) -{ - JSON::Value j_archive = obj.get("archive"); - - IQuery* get_next = db->Prepare("SELECT next_archival FROM settings_db.automatic_archival WHERE clientid=? AND uuid=?"); - - if (j_archive.getType() == JSON::str_type) - { - str_map params; - std::string archive_str = j_archive.getString(); - ParseParamStrHttp(archive_str, ¶ms); - - for (str_map::iterator it = params.begin(); it != params.end(); ++it) - { - if (next(it->first, 0, "uuid_")) - { - std::string idx = getafter("uuid_", it->first); - - get_next->Bind(clientid); - get_next->Bind(it->second); - db_results res = get_next->Read(); - get_next->Reset(); - - if (!res.empty()) - { - int64 next_archival = watoi64(res[0]["next_archival"]); - archive_str += "&next_archival_" + idx + "=" + res[0]["next_archival"]; - archive_str += "&timeleft_" + idx + "=" + convert(next_archival - (_i64)Server->getTimeSeconds()); - } - } - } - - obj.set("archive", archive_str); - } -} - void getGeneralSettings(JSON::Object& obj, IDatabase *db, ServerSettings &settings) { std::auto_ptr settings_db(Server->createDBSettingsReader(db, "settings_db.settings", @@ -453,7 +455,7 @@ void archiveParamsSetUuid(str_map& POST) std::string uuid; uuid.resize(16); Server->secureRandomFill(&uuid[0], uuid.size()); - it->second = uuid; + it->second = bytesToHex(uuid); mod = true; } } @@ -542,7 +544,7 @@ void updateClientSettings(int t_clientid, str_map &POST, IDatabase *db) void updateArchiveSettings(int clientid, IDatabase *db) { IQuery* q = db->Prepare("INSERT INTO settings_db.settings(key, value, clientid) VALUES ('archive_update', '1', ?)"); - if (clientid == 0) + if (clientid <= 0) { db_results res_ids = db->Read("SELECT id FROM clients"); From f07f3d044c44ddc47a5812a3b1daa58786c1f228 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 18 Nov 2023 21:19:26 +0100 Subject: [PATCH 303/469] Don't reset internet_authkey use value (cherry picked from commit 154c0a238d8689adf577c2ce1ecc5c56268df90c) # Conflicts: # urbackupserver/dllmain.cpp --- urbackupserver/dllmain.cpp | 16 +++++++++++++--- urbackupserver/serverinterface/settings.cpp | 6 ++++++ urbackupserver/www/js/urbackup.js | 3 +++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index cfd7a14f6..1f4e04c34 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2409,9 +2409,12 @@ bool upgrade66_67() { IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); - db_results res = db->Read("SELECT rowid, value FROM settings_db.settings WHERE key='archive'"); + return db->Write(std::string("UPDATE settings_db.settings SET use=") + c_use_value_str + " WHERE key='internet_authkey'"); +} - IQuery* q_update = db->Prepare("UPDATE settings_db.settings SET value=? WHERE rowid=?"); +bool upgrade67_68() +{ + IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); if (!db->Write("UPDATE settings_db.automatic_archival SET uuid=unhex(hex(uuid))")) return false; @@ -2468,7 +2471,7 @@ void upgrade(void) int ver=watoi(res_v[0]["tvalue"]); int old_v; - int max_v=67; + int max_v=68; { IScopedLock lock(startup_status.mutex); startup_status.target_db_version=max_v; @@ -2881,6 +2884,13 @@ void upgrade(void) } ++ver; break; + case 66: + if (!upgrade66_67()) + { + has_error = true; + } + ++ver; + break; default: break; } diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index fb5e6acae..c32bec126 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -508,6 +508,9 @@ void updateClientSettings(int t_clientid, str_map &POST, IDatabase *db) std::sort(sset_client_merge.begin(), sset_client_merge.end()); std::vector sset_client_use = getClientConfigurableSettingsList(); std::sort(sset_client_use.begin(), sset_client_use.end()); + std::vector sset_localized = getLocalizedSettingsList(); + std::sort(sset_localized.begin(), sset_localized.end()); + std::vector sset=getSettingsList(); for(size_t i=0;isecond), q_get, q_update, q_insert, &use, &use_last_modified); diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 29e62b3be..3527309d1 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -2789,6 +2789,9 @@ function settingSwitchReset(key) for(var i=0;i Date: Sat, 6 Jan 2024 15:55:15 +0100 Subject: [PATCH 304/469] Fix update (cherry picked from commit 88a285a283b20b0933584e6055bf2f8de5751ba2) # Conflicts: # urbackupserver/dllmain.cpp --- urbackupserver/dllmain.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index 1f4e04c34..0161785bc 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2416,6 +2416,10 @@ bool upgrade67_68() { IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); + db_results res = db->Read("SELECT rowid, value FROM settings_db.settings WHERE key='archive'"); + + IQuery* q_update = db->Prepare("UPDATE settings_db.settings SET value=? WHERE rowid=?"); + if (!db->Write("UPDATE settings_db.automatic_archival SET uuid=unhex(hex(uuid))")) return false; @@ -2884,8 +2888,8 @@ void upgrade(void) } ++ver; break; - case 66: - if (!upgrade66_67()) + case 67: + if (!upgrade67_68()) { has_error = true; } From 7bec12c4eb96b3e43a3d5f33e0672b52754e96cf Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Jan 2024 16:06:12 +0100 Subject: [PATCH 305/469] Fix build (cherry picked from commit 8b7ec4268296a88543f1531eeb1bfc8565c2cc46) --- urbackupserver/serverinterface/settings.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index c32bec126..8ff2c2308 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -943,7 +943,6 @@ ACTION_IMPL(settings) ServerSettings settings(db, t_clientid); JSON::Object obj=getJSONClientSettings(db, t_clientid); - addNextArchival(db, t_clientid, obj); obj.set("clientid", t_clientid); obj.set("alert_scripts", getAlertScripts(db)); if (helper.getRights(RIGHT_ALERT_SCRIPTS) == RIGHT_ALL) From 98297f475614a707aa76bc607e9a4505f2626a9a Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Jan 2024 17:06:03 +0100 Subject: [PATCH 306/469] Also migrate window (cherry picked from commit 89afe022f3431527c0ba2e9ebe52e8b6761ab69a) # Conflicts: # urbackupserver/dllmain.cpp --- urbackupserver/dllmain.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index 0161785bc..07caeaa9e 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2199,7 +2199,7 @@ std::string archiveSettingsParamStr(IDatabase* db, int clientid, std::string pre ret += "&for" + idx + "=" + EscapeParamString(res[i]["length"]); ret += "&for_unit" + idx + "=" + EscapeParamString(res[i]["length_unit"]); ret += "&backup_type" + idx + "=" + EscapeParamString(ServerAutomaticArchive::getBackupType(watoi(res[i]["backup_types"]))); - ret += "&archive_window" + idx + "=" + EscapeParamString(res[i]["archive_window"]); + ret += "&window" + idx + "=" + EscapeParamString(res[i]["archive_window"]); ret += "&letters" + idx + "=" + EscapeParamString(res[i]["letters"]); ret += "&uuid"+idx+"=" + bytesToHex(uuid); @@ -2260,6 +2260,12 @@ std::string fixArchiveMigration(IDatabase* db, const std::string& archive_str) ret += "backup_type_" + idx + "=" + EscapeParamString(it->second); } + } + else if (next(it->first, 0, "archive_window_")) + { + std::string idx = getafter("archive_window_", it->first); + + ret += "window_" + idx + "=" + EscapeParamString(it->second); else { ret += it->first + "=" + EscapeParamString(it->second); From 9639e1fda45fb7a57ae92f59ff8b18af935e6cdc Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Jan 2024 17:18:53 +0100 Subject: [PATCH 307/469] Only use value if there isn't a new one (cherry picked from commit 0d4dc163ca2a406a07c186842f27beebf511ab60) # Conflicts: # urbackupserver/dllmain.cpp --- urbackupserver/dllmain.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index 07caeaa9e..282fccb12 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2258,14 +2258,16 @@ std::string fixArchiveMigration(IDatabase* db, const std::string& archive_str) { std::string idx = getafter("backup_types_", it->first); - ret += "backup_type_" + idx + "=" + EscapeParamString(it->second); + if(archive_settings.find("backup_type_" + idx)== archive_settings.end()) + ret += "backup_type_" + idx + "=" + EscapeParamString(it->second); } } else if (next(it->first, 0, "archive_window_")) { std::string idx = getafter("archive_window_", it->first); - ret += "window_" + idx + "=" + EscapeParamString(it->second); + if (archive_settings.find("window_" + idx) == archive_settings.end()) + ret += "window_" + idx + "=" + EscapeParamString(it->second); else { ret += it->first + "=" + EscapeParamString(it->second); From 12b422c3a4a9151f2fcddeb9fc0b71404fefa92f Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Jan 2024 18:08:47 +0100 Subject: [PATCH 308/469] Fix build --- urbackupserver/dllmain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index 282fccb12..165394f8f 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2261,13 +2261,13 @@ std::string fixArchiveMigration(IDatabase* db, const std::string& archive_str) if(archive_settings.find("backup_type_" + idx)== archive_settings.end()) ret += "backup_type_" + idx + "=" + EscapeParamString(it->second); } - } else if (next(it->first, 0, "archive_window_")) { std::string idx = getafter("archive_window_", it->first); if (archive_settings.find("window_" + idx) == archive_settings.end()) ret += "window_" + idx + "=" + EscapeParamString(it->second); + } else { ret += it->first + "=" + EscapeParamString(it->second); From fff37507c6364ef520112028c545e9169f2609f8 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Jan 2024 18:09:10 +0100 Subject: [PATCH 309/469] Fix adding archival uuid --- urbackupserver/serverinterface/settings.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index 8ff2c2308..5de9b153c 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -124,8 +124,13 @@ JSON::Array getAlertScripts(IDatabase* db) return ret; } -std::string addNextArchival(IDatabase* db, int clientid, IQuery* get_next, std::string archive_str) +JSON::Value addNextArchival(IDatabase* db, int clientid, IQuery* get_next, const JSON::Value& archive_val) { + if (archive_val.getType() != JSON::Value_type::str_type) + return archive_val; + + std::string archive_str = archive_val.getString(); + str_map params; ParseParamStrHttp(archive_str, ¶ms); @@ -150,7 +155,7 @@ std::string addNextArchival(IDatabase* db, int clientid, IQuery* get_next, std:: } } - return archive_str; + return JSON::Value(archive_str); } @@ -166,9 +171,9 @@ JSON::Object getJSONClientSettings(IDatabase* db, int t_clientid) { if (it->first == "archive") { - it->second.value = addNextArchival(db, t_clientid, get_next, it->second.value.getString()); - it->second.value_client = addNextArchival(db, t_clientid, get_next, it->second.value_client.getString()); - it->second.value_group = addNextArchival(db, t_clientid, get_next, it->second.value_group.getString()); + it->second.value = addNextArchival(db, t_clientid, get_next, it->second.value); + it->second.value_client = addNextArchival(db, t_clientid, get_next, it->second.value_client); + it->second.value_group = addNextArchival(db, t_clientid, get_next, it->second.value_group); } JSON::Object jobj; From 5de6aa5363444f4f1a6019b25f3d6271f2e13c8d Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Jan 2024 19:20:57 +0100 Subject: [PATCH 310/469] Use proper binary uuid (cherry picked from commit 017ec1323dce1b8824e0d8dfd4e65269dab45f52) --- urbackupserver/dllmain.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index 165394f8f..cd50f4fac 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2234,7 +2234,8 @@ std::string fixArchiveMigration(IDatabase* db, const std::string& archive_str) it_for_unit != archive_settings.end() && it_for->second == it_for_unit->second) { - q_find->Bind(it_uuid->second.data(), it_uuid->second.size()); + std::string uuid = hexToBytes(it_uuid->second); + q_find->Bind(uuid.data(), uuid.size()); db_results res = q_find->Read(); q_find->Reset(); From 5965baf2681601eabf1e907fea6b72a5da8e0fcf Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 6 Mar 2024 20:36:59 +0100 Subject: [PATCH 311/469] Use CR LF instead of just LF to end lines in mails --- urbackupserver/Backup.cpp | 10 ++-- urbackupserver/FileBackup.cpp | 8 +-- urbackupserver/report.lua | 12 ++--- urbackupserver/report_lua.h | 82 +++++++++++++++---------------- urbackupserver/server_cleanup.cpp | 24 ++++----- 5 files changed, 68 insertions(+), 68 deletions(-) diff --git a/urbackupserver/Backup.cpp b/urbackupserver/Backup.cpp index b4b3ab09b..55157ea59 100644 --- a/urbackupserver/Backup.cpp +++ b/urbackupserver/Backup.cpp @@ -292,9 +292,9 @@ void Backup::sendLogdataMail(bool r_success, int image, int incremental, bool re msg+="file "; subj+="file "; } - subj+="backup of \""+clientname+"\"\n"; - msg+="backup of \""+clientname+"\".\n"; - msg+="\nReport:\n"; + subj+="backup of \""+clientname+"\"\r\n"; + msg+="backup of \""+clientname+"\".\r\n"; + msg+="\r\nReport:\r\n"; msg+="( "+convert(infos); if(infos!=1) msg+=" infos, "; else msg+=" info, "; @@ -304,7 +304,7 @@ void Backup::sendLogdataMail(bool r_success, int image, int incremental, bool re msg+=convert(errors); if(errors!=1) msg+=" errors"; else msg+=" error"; - msg+=" )\n\n"; + msg+=" )\r\n\r\n"; std::vector msgs; Tokenize(data, msgs, "\n"); @@ -320,7 +320,7 @@ void Backup::sendLogdataMail(bool r_success, int image, int incremental, bool re std::string lls="info"; if(li==1) lls="warning"; else if(li==2) lls="error"; - msg+=(tt)+"("+lls+"): "+(m)+"\n"; + msg+=(tt)+"("+lls+"): "+(m)+"\r\n"; } if(!r_success) subj+=" - failed"; diff --git a/urbackupserver/FileBackup.cpp b/urbackupserver/FileBackup.cpp index 52638a300..dabb77413 100644 --- a/urbackupserver/FileBackup.cpp +++ b/urbackupserver/FileBackup.cpp @@ -1296,7 +1296,7 @@ bool FileBackup::verify_file_backup(IFile *fileentries) std::ostringstream log; - log << "Verification of file backup with id " << backupid << ". Path=" << (backuppath) << " Tree-hashing=" << convert(BackupServer::useTreeHashing()) << std::endl; + log << "Verification of file backup with id " << backupid << ". Path=" << (backuppath) << " Tree-hashing=" << convert(BackupServer::useTreeHashing()) << "\r\n"; unsigned int read; char buffer[4096]; @@ -1377,7 +1377,7 @@ bool FileBackup::verify_file_backup(IFile *fileentries) std::string msg = "No hash for file \"" + (curr_path + os_file_sep() + cf.name) + "\" found. Verification failed."; verify_ok = false; ServerLogger::Log(logid, msg, LL_ERROR); - log << msg << std::endl; + log << msg << "\r\n"; } } else @@ -1389,7 +1389,7 @@ bool FileBackup::verify_file_backup(IFile *fileentries) std::string msg="Hashes for \""+(curr_path+os_file_sep()+cf.name)+"\" differ (client side hash). Verification failed."; verify_ok=false; ServerLogger::Log(logid, msg, LL_ERROR); - log << msg << std::endl; + log << msg << "\r\n"; save_debug_data(remote_path+"/"+cf.name, base64_encode_dash(getSHADef(curr_path+os_file_sep()+cfn)), shabase64); @@ -1407,7 +1407,7 @@ bool FileBackup::verify_file_backup(IFile *fileentries) std::string msg="Hashes for \""+(curr_path+os_file_sep()+cf.name)+"\" differ. Verification failed."; verify_ok=false; ServerLogger::Log(logid, msg, LL_ERROR); - log << msg << std::endl; + log << msg << "\r\n"; } ++verified_files; diff --git a/urbackupserver/report.lua b/urbackupserver/report.lua index c7d87f21e..c8b84fcaa 100644 --- a/urbackupserver/report.lua +++ b/urbackupserver/report.lua @@ -31,9 +31,9 @@ else subj = subj .. "file " end -subj = subj .. "backup of \"" .. params.clientname .. "\"\n" -msg = msg .. "backup of \"" .. params.clientname .. "\".\n" -msg = msg .. "\nReport:\n" +subj = subj .. "backup of \"" .. params.clientname .. "\"\r\n" +msg = msg .. "backup of \"" .. params.clientname .. "\".\r\n" +msg = msg .. "\r\nReport:\r\n" msg = msg .. "( " .. params.infos if params.infos~=1 then msg = msg .. " infos, " else msg = msg .. " info, " end @@ -41,15 +41,15 @@ msg = msg .. params.warnings if params.warnings~=1 then msg = msg .. " warnings, " else msg = msg .. " warning, " end msg = msg .. params.errors -if params.errors~=1 then msg = msg .. " errors)\n\n" -else msg = msg .. " error)\n\n" end +if params.errors~=1 then msg = msg .. " errors)\r\n\r\n" +else msg = msg .. " error)\r\n\r\n" end for i, v in ipairs(params.data) do local ll = "(info)" if v.ll==1 then ll="(warning)" elseif v.ll==2 then ll="(error)" end - msg = msg .. os.date("%Y-%m-%d %H:%M:%S", v.time) .. ll .. ": " .. v.msg .. "\n" + msg = msg .. os.date("%Y-%m-%d %H:%M:%S", v.time) .. ll .. ": " .. v.msg .. "\r\n" end if params.success diff --git a/urbackupserver/report_lua.h b/urbackupserver/report_lua.h index 06e99bfef..4ebcebe47 100644 --- a/urbackupserver/report_lua.h +++ b/urbackupserver/report_lua.h @@ -1,43 +1,43 @@ unsigned char report_lua_z[] = { - 0x78, 0x9c, 0x9d, 0x54, 0x4d, 0x4b, 0xc4, 0x30, 0x10, 0x3d, 0x6f, 0xa1, - 0xff, 0x61, 0x08, 0x14, 0x5a, 0xd8, 0x2d, 0xea, 0xb1, 0xb0, 0x1e, 0x3c, - 0x88, 0x1e, 0xbc, 0x28, 0x1e, 0x84, 0x82, 0xc4, 0x36, 0x5d, 0xa3, 0x49, - 0xba, 0x24, 0xed, 0x7a, 0xf3, 0xb7, 0x9b, 0xaf, 0x76, 0x9b, 0xa5, 0x5d, - 0xc5, 0x4b, 0x77, 0x3b, 0xef, 0xcd, 0x9b, 0x97, 0xe9, 0x4c, 0x58, 0x5b, - 0x61, 0x06, 0xaa, 0x7f, 0xfb, 0x80, 0x2d, 0xa0, 0x67, 0x79, 0x83, 0xab, - 0xcf, 0x7e, 0x5f, 0x00, 0x8a, 0x23, 0x66, 0x21, 0xae, 0x76, 0x53, 0x04, - 0x3e, 0x7a, 0xd5, 0x41, 0x4d, 0x6b, 0xc3, 0x88, 0x23, 0xda, 0xc0, 0x1e, - 0x4b, 0xcc, 0x55, 0x4e, 0x45, 0x25, 0x09, 0x27, 0xa2, 0xc3, 0xec, 0xfa, - 0x22, 0x8e, 0xba, 0x77, 0x22, 0xe2, 0x68, 0x75, 0xc4, 0x25, 0x51, 0x3d, - 0x27, 0xb5, 0x8e, 0x79, 0x68, 0xe5, 0x94, 0xcd, 0x33, 0xcf, 0x01, 0x61, - 0xf0, 0x0c, 0x98, 0x28, 0x99, 0x22, 0xab, 0x95, 0x77, 0x67, 0x7f, 0x0c, - 0xf5, 0x71, 0x81, 0x48, 0x98, 0x22, 0x33, 0xc2, 0xe2, 0x77, 0xc5, 0xfb, - 0x53, 0x25, 0xa1, 0x8d, 0x7a, 0xb9, 0xff, 0x1c, 0xa1, 0xe9, 0xd9, 0x2f, - 0xde, 0x47, 0xc6, 0x82, 0xe9, 0x33, 0x12, 0xb7, 0x63, 0xaa, 0x75, 0x69, - 0x1e, 0xc1, 0x87, 0xe0, 0x78, 0x47, 0x26, 0x9f, 0x20, 0x14, 0xb6, 0xa8, - 0xcd, 0x3e, 0x95, 0x1d, 0x11, 0xef, 0x28, 0xcc, 0x6b, 0x28, 0x9b, 0x4f, - 0x1b, 0x00, 0xef, 0xe3, 0x14, 0x7e, 0x73, 0x53, 0xd3, 0x36, 0x50, 0x22, - 0x64, 0x22, 0xde, 0x65, 0xc5, 0xa8, 0x6e, 0xb7, 0xc0, 0x9c, 0x58, 0x5a, - 0x89, 0x4a, 0xa1, 0x45, 0xc2, 0x9a, 0x7f, 0xce, 0xcd, 0x67, 0x92, 0x4b, - 0xf1, 0x48, 0xf6, 0xad, 0xec, 0x8a, 0x19, 0x2c, 0x85, 0xa9, 0x1c, 0x15, - 0x4d, 0xab, 0xc2, 0x51, 0xd6, 0x81, 0xef, 0xed, 0x25, 0x98, 0x0e, 0x42, - 0x98, 0x0a, 0x16, 0x5c, 0x0f, 0x7d, 0x9a, 0x43, 0x35, 0x08, 0xb6, 0x1b, - 0x01, 0xe6, 0xb5, 0xbf, 0xb0, 0x14, 0x54, 0xec, 0x82, 0x7a, 0x43, 0x6c, - 0xa9, 0xe4, 0x80, 0x2f, 0x56, 0xf5, 0x84, 0xb3, 0x85, 0x89, 0x94, 0xad, - 0x0c, 0xca, 0xba, 0xc8, 0x52, 0x51, 0x87, 0x66, 0xa5, 0xb0, 0xfd, 0x9b, - 0xab, 0x6a, 0x19, 0x8e, 0x00, 0xfe, 0xeb, 0x37, 0xad, 0x04, 0xba, 0x86, - 0x83, 0xee, 0x03, 0xd0, 0x3d, 0xa6, 0x52, 0xa5, 0xbe, 0x58, 0x8d, 0x3b, - 0x9c, 0xc5, 0x51, 0xdd, 0xea, 0x11, 0x72, 0x17, 0x8b, 0x1e, 0x62, 0x7d, - 0xaf, 0xa4, 0xa6, 0x63, 0x19, 0x72, 0x7b, 0x76, 0xc8, 0x19, 0xdb, 0x0e, - 0x7e, 0xf4, 0x5f, 0x94, 0xfa, 0x93, 0x65, 0xc3, 0xa6, 0x8c, 0xa4, 0xab, - 0x09, 0xc9, 0x19, 0xf1, 0x26, 0xc2, 0xb9, 0x6d, 0x6d, 0x65, 0x92, 0xa2, - 0xe4, 0x65, 0x93, 0xf0, 0x4d, 0x52, 0x43, 0x72, 0x57, 0x24, 0x0f, 0x45, - 0xf2, 0x84, 0xb4, 0xcd, 0xbc, 0xa3, 0x9c, 0x64, 0x86, 0xa7, 0xcd, 0x98, - 0x33, 0x15, 0x6e, 0x30, 0x0e, 0xf9, 0x71, 0x8a, 0xd0, 0xcc, 0x8a, 0xa9, - 0xbe, 0xaa, 0x88, 0x52, 0xe3, 0x8a, 0x9d, 0x0e, 0x3d, 0x6c, 0xc0, 0x53, - 0x8e, 0xeb, 0x34, 0xc3, 0x69, 0xb0, 0xde, 0x9d, 0xfa, 0x58, 0x80, 0xeb, - 0xf7, 0x74, 0xbc, 0x6b, 0xcc, 0xf0, 0xbe, 0x9a, 0xd0, 0xda, 0x26, 0xad, - 0xcd, 0x91, 0x32, 0x43, 0x93, 0xa4, 0xeb, 0xa5, 0x00, 0xbd, 0xe1, 0x3f, - 0x20, 0xa5, 0xac, 0xaa + 0x78, 0x9c, 0x9d, 0x54, 0xb1, 0x6e, 0x83, 0x30, 0x10, 0x9d, 0x83, 0xc4, + 0x3f, 0x9c, 0x2c, 0x21, 0x81, 0x44, 0x50, 0xdb, 0x11, 0x29, 0x1d, 0x3a, + 0x54, 0xed, 0xd0, 0x25, 0x55, 0x87, 0x4a, 0x91, 0x2a, 0x07, 0x4c, 0xea, + 0xd4, 0x36, 0x91, 0x0d, 0xe9, 0xd6, 0x6f, 0xaf, 0x6d, 0x0c, 0xc1, 0x11, + 0x24, 0x55, 0x17, 0x12, 0xee, 0xbd, 0x7b, 0xf7, 0x7c, 0xdc, 0x99, 0xd5, + 0x05, 0x66, 0xa0, 0xda, 0xed, 0x1e, 0x56, 0x80, 0xde, 0xe4, 0x03, 0x2e, + 0xbe, 0xda, 0x43, 0x0e, 0x28, 0x0c, 0x98, 0x85, 0xb8, 0xda, 0x8d, 0x11, + 0xd8, 0xb7, 0xaa, 0x81, 0x92, 0x96, 0x86, 0x11, 0x06, 0xb4, 0x82, 0x03, + 0x96, 0x98, 0xab, 0x8c, 0x8a, 0x42, 0x12, 0x4e, 0x44, 0x83, 0xd9, 0xfd, + 0x4d, 0x18, 0x34, 0x9f, 0x44, 0x84, 0xc1, 0xe2, 0x84, 0x4b, 0xa2, 0x5a, + 0x4e, 0x4a, 0x1d, 0x73, 0xd0, 0xa2, 0x53, 0x36, 0xcf, 0x2c, 0x03, 0x84, + 0xc1, 0x31, 0x60, 0xa4, 0x64, 0x8a, 0x2c, 0x16, 0xce, 0x9d, 0xfd, 0x31, + 0xd4, 0xf5, 0x0c, 0x91, 0x30, 0x45, 0x26, 0x84, 0xc5, 0x75, 0xc5, 0xe7, + 0x73, 0x25, 0xa1, 0x8d, 0x3a, 0xb9, 0xff, 0x1c, 0xa1, 0x6a, 0xd9, 0x15, + 0xef, 0x03, 0x63, 0xc6, 0xf4, 0x05, 0x89, 0xc7, 0x21, 0xd5, 0xba, 0x34, + 0x0f, 0xef, 0x43, 0x70, 0xbc, 0x23, 0xa3, 0x4f, 0xe0, 0x0b, 0x5b, 0xd4, + 0x66, 0x9f, 0xcb, 0x0e, 0x88, 0x73, 0xe4, 0xe7, 0x55, 0x94, 0x4d, 0xa7, + 0xf5, 0x80, 0xf3, 0x71, 0x0e, 0x6f, 0xbb, 0xa9, 0xa9, 0x2b, 0xd8, 0x20, + 0x64, 0x22, 0xce, 0x65, 0xc1, 0xa8, 0x6e, 0xb7, 0xc0, 0x9c, 0x58, 0xda, + 0x06, 0x6d, 0xe4, 0x46, 0x68, 0x19, 0xbf, 0xea, 0x9f, 0xb3, 0xb3, 0xc9, + 0x74, 0x13, 0x5c, 0x93, 0x43, 0x2d, 0x9b, 0x7c, 0x12, 0x8f, 0x61, 0x2c, + 0x4a, 0x45, 0x55, 0x2b, 0x7f, 0xa4, 0x75, 0xe0, 0x67, 0x75, 0x0b, 0xa6, + 0x93, 0xe0, 0xa7, 0x82, 0x05, 0xd3, 0xbe, 0x5f, 0x53, 0xa8, 0x06, 0xc1, + 0x76, 0xc5, 0xc3, 0x9c, 0xf6, 0x37, 0x96, 0x82, 0x8a, 0x9d, 0x57, 0xaf, + 0x8f, 0xcd, 0x95, 0xec, 0xf1, 0xd9, 0xaa, 0x8e, 0x70, 0xb1, 0x30, 0x91, + 0xb2, 0x96, 0x5e, 0xd9, 0x2e, 0x32, 0x57, 0xb4, 0x43, 0x13, 0xd3, 0x3f, + 0xd7, 0xc3, 0xa9, 0xca, 0x96, 0x75, 0x22, 0x81, 0x9b, 0x86, 0xaa, 0x96, + 0x40, 0x53, 0x38, 0xea, 0x7e, 0x00, 0x3d, 0x60, 0x2a, 0x55, 0xec, 0x8a, + 0x96, 0xb8, 0xc1, 0x49, 0x18, 0x94, 0xb5, 0x1e, 0xa9, 0xee, 0xa2, 0xd1, + 0x43, 0xad, 0xef, 0x99, 0xd8, 0x74, 0x2e, 0x41, 0xdd, 0xde, 0x1d, 0x33, + 0xc6, 0x56, 0xbd, 0x2f, 0xfd, 0x17, 0xc5, 0xee, 0x84, 0x49, 0xbf, 0x39, + 0x03, 0xe9, 0x6e, 0x44, 0xea, 0xcc, 0x38, 0x13, 0xfe, 0x1c, 0xd7, 0xb6, + 0x32, 0x89, 0x51, 0xf4, 0xbe, 0x8c, 0xf8, 0x32, 0x2a, 0x21, 0x7a, 0xca, + 0xa3, 0x97, 0x3c, 0x7a, 0x45, 0xda, 0x66, 0xd6, 0x50, 0x4e, 0x12, 0xc3, + 0xd3, 0x66, 0xcc, 0xb9, 0xf2, 0x6e, 0x40, 0x8e, 0xd9, 0x78, 0xa2, 0xd0, + 0xc4, 0xd2, 0xa9, 0xb6, 0x28, 0x88, 0x52, 0xc3, 0xd2, 0x9d, 0xaf, 0x01, + 0x2c, 0xc1, 0x51, 0x4e, 0x0b, 0x36, 0xc1, 0xa9, 0xb0, 0xde, 0xa6, 0xf2, + 0x54, 0x80, 0xeb, 0xf7, 0x78, 0xb8, 0x7d, 0xcc, 0x20, 0x7f, 0x98, 0x50, + 0x6a, 0x93, 0x52, 0x73, 0xa8, 0xc4, 0xd0, 0x24, 0x69, 0x5a, 0x29, 0x40, + 0xef, 0xfc, 0x2f, 0xab, 0x85, 0xb3, 0xe8 }; -unsigned int report_lua_z_len = 472; +unsigned int report_lua_z_len = 475; diff --git a/urbackupserver/server_cleanup.cpp b/urbackupserver/server_cleanup.cpp index 1ebfb66b0..2f5e2c87b 100644 --- a/urbackupserver/server_cleanup.cpp +++ b/urbackupserver/server_cleanup.cpp @@ -2015,16 +2015,16 @@ void ServerCleanupThread::enforce_quotas(void) { ServerLogger::Log(logid, "Enforcing quota for client \"" + (clients[i].name)+ "\" (id="+convert(clients[i].id)+")", LL_INFO); std::ostringstream log; - log << "Quota enforcement report for client \"" << (clients[i].name) << "\" (id=" << clients[i].id << ")" << std::endl; + log << "Quota enforcement report for client \"" << (clients[i].name) << "\" (id=" << clients[i].id << ")\r\n"; if(!enforce_quota(clients[i].id, log)) { ClientMain::sendMailToAdmins("Quota enforcement failed", log.str()); - ServerLogger::Log(logid, log.str(), LL_ERROR); + ServerLogger::Log(logid, trim(log.str()), LL_ERROR); } else { - ServerLogger::Log(logid, log.str(), LL_DEBUG); + ServerLogger::Log(logid, trim(log.str()), LL_DEBUG); } } @@ -2042,7 +2042,7 @@ bool ServerCleanupThread::enforce_quota(int clientid, std::ostringstream& log) std::string client_quota = trim(client_settings.getSettings()->client_quota); if(client_quota.empty() || client_quota=="100%" || client_quota=="-") { - log << "Client does not have a quota or quota is 100%" << std::endl; + log << "Client does not have a quota or quota is 100%\r\n"; return true; } @@ -2052,22 +2052,22 @@ bool ServerCleanupThread::enforce_quota(int clientid, std::ostringstream& log) ServerCleanupDao::CondInt64 used_storage=cleanupdao->getUsedStorage(clientid); if(!used_storage.exists || used_storage.value<0) { - log << "Error getting used storage of client" << std::endl; + log << "Error getting used storage of client\r\n"; return false; } int64 client_quota=cleanup_amount(client_settings.getSettings()->client_quota, db); - log << "Client uses " << PrettyPrintBytes(used_storage.value) << " and has a quota of " << PrettyPrintBytes(client_quota) << std::endl; + log << "Client uses " << PrettyPrintBytes(used_storage.value) << " and has a quota of " << PrettyPrintBytes(client_quota) << "\r\n"; if(used_storage.value<=client_quota) { - log << "Client within assigned quota." << std::endl; + log << "Client within assigned quota.\r\n"; return true; } else { - log << "This requires enforcement of the quota." << std::endl; + log << "This requires enforcement of the quota.\r\n"; } did_remove_something=false; @@ -2080,7 +2080,7 @@ bool ServerCleanupThread::enforce_quota(int clientid, std::ostringstream& log) if(available_space==-1) { - log << "Error getting free space -5" << std::endl; + log << "Error getting free space -5\r\n"; return false; } @@ -2090,7 +2090,7 @@ bool ServerCleanupThread::enforce_quota(int clientid, std::ostringstream& log) if(target_minspace<0) { - log << "Error. Target space is negative" << std::endl; + log << "Error. Target space is negative\r\n"; return false; } @@ -2102,7 +2102,7 @@ bool ServerCleanupThread::enforce_quota(int clientid, std::ostringstream& log) { if (cleanup_one_imagebackup_client(clientid, target_minspace, imagebid)) { - log << "Removed image backupd with id " << imagebid << std::endl; + log << "Removed image backupd with id " << imagebid << "\r\n"; did_remove_something = true; //TODO: wait here for btrfs subvol remove to finish if (hasEnoughFreeSpace(target_minspace, &client_settings)) @@ -2129,7 +2129,7 @@ bool ServerCleanupThread::enforce_quota(int clientid, std::ostringstream& log) int filebid; if(cleanup_one_filebackup_client(clientid, target_minspace, filebid)) { - log << "Removed file backup with id " << filebid << std::endl; + log << "Removed file backup with id " << filebid << "\r\n"; did_remove_something=true; nopc=0; From ce00e4fdb22275775a4f2e5f99e5b31213276d57 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 25 Feb 2024 21:59:26 +0100 Subject: [PATCH 312/469] Merge pull request #96 from joser93/patch-1 Compile fix for crc.h (cherry picked from commit 399941d44051c0465dc50dfb0b332f5c87e878f0) --- blockalign_src/crc.h | 1 + 1 file changed, 1 insertion(+) diff --git a/blockalign_src/crc.h b/blockalign_src/crc.h index fe4edee62..1c9219ec1 100644 --- a/blockalign_src/crc.h +++ b/blockalign_src/crc.h @@ -7,6 +7,7 @@ #pragma once #include +#include namespace cryptopp_crc { From 6bc523655c1e97c3e80ad9b31b64736d4b6977c6 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 25 Feb 2024 21:53:05 +0100 Subject: [PATCH 313/469] Merge pull request #13 from mat02/dev Add backup ID and client ID to web interface (cherry picked from commit e0655ae89aa13abcc436f6e27938331f23fb8dea) --- urbackupserver/www/templates/backups_backups.htm | 4 +++- urbackupserver/www/translations/urbackup.webinterface/en.po | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/urbackupserver/www/templates/backups_backups.htm b/urbackupserver/www/templates/backups_backups.htm index c52c02d4e..41c013035 100644 --- a/urbackupserver/www/templates/backups_backups.htm +++ b/urbackupserver/www/templates/backups_backups.htm @@ -3,7 +3,7 @@ {?show_client_breadcrumb} {tClients} > {/show_client_breadcrumb} - {clientname} + {clientname} (ID: {clientid})
    {?backups} @@ -12,6 +12,7 @@

    {tFile backups}

      {tBackup time} + {tBackup ID} {tIncremental} {tSize} {tArchived}? @@ -21,6 +22,7 @@

    {tFile backups}

      {backuptime} + {id} {incr} {size_bytes} {archived|s} diff --git a/urbackupserver/www/translations/urbackup.webinterface/en.po b/urbackupserver/www/translations/urbackup.webinterface/en.po index ee4e80ac0..da8aba0e3 100644 --- a/urbackupserver/www/translations/urbackup.webinterface/en.po +++ b/urbackupserver/www/translations/urbackup.webinterface/en.po @@ -317,6 +317,12 @@ msgstr "Send" msgid "tBackup time" msgstr "Backup time" +msgid "tBackup ID" +msgstr "Backup ID" + +msgid "tErrors" +msgstr "Errors" + msgid "tStorage usage" msgstr "Storage usage" From dd1edf784529672958ef874d200e07a20e38ec17 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 17 Mar 2024 09:35:56 +0100 Subject: [PATCH 314/469] Update templates --- urbackupserver/www/js/templates.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 2801b1b32..102c8d030 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,11 +1,11 @@ -(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)

    A lot of work has gone into UrBackup.If you like it and want to support the ongoing development please consider donating.
    Via PayPal:

    Contribution of build server or testing infrastructure is welcome as well. The most appreciated contribution would be your time in form of help.

    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)



    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
     

    ").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



    ").f(ctx.get(["tAlert script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); (function(){dust.register("backup_restore_wait",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tPreparing restore. Please be patient..."], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_access_denied",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAccess denied"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong or you do not have the required rights to access this file or folder."], false),ctx,"h").x(ctx.get(["errcode"], false),ctx,{"block":body_1},{}).w("

    ").f(ctx.get(["tLogin with username and password"], false),ctx,"h").w("

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("(").f(ctx.get(["errcode"], false),ctx,"h").w(")");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w("
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); +(function(){dust.register("backups_backups",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" (ID: ").f(ctx.get(["clientid"], false),ctx,"h").w(")
    ").x(ctx.get(["backups"], false),ctx,{"block":body_2},{}).x(ctx.get(["backup_images"], false),ctx,{"block":body_11},{}).nx(ctx.get(["backups"], false),ctx,{"block":body_20},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tFile backups"], false),ctx,"h").w("

    ").x(ctx.get(["has_actions"], false),ctx,{"block":body_3},{}).w("").s(ctx.get(["backups"], false),ctx,{"block":body_4},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tBackup ID"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["id"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_5},{}).w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_6},{}).w("");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_7},{});}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_8},{});}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_9,"block":body_10},{});}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("

    ").f(ctx.get(["tImage backups"], false),ctx,"h").w("

    \t\t\t\t").x(ctx.get(["has_actions"], false),ctx,{"block":body_12},{}).w("").s(ctx.get(["backup_images"], false),ctx,{"block":body_13},{}).w("
     ").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVolume"], false),ctx,"h").w("").f(ctx.get(["tIncremental"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tArchived"], false),ctx,"h").w("?
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("").f(ctx.get(["tActions"], false),ctx,"h").w("");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w(" ").f(ctx.get(["backuptime"], false),ctx,"h").w("").f(ctx.get(["letter"], false),ctx,"h").w("").f(ctx.get(["incr"], false),ctx,"h").w("").f(ctx.get(["size_bytes"], false),ctx,"h").w("").f(ctx.get(["archived"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_actions"], false),ctx,{"block":body_14},{}).w("");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("").nx(ctx.get(["is_archived"], false),ctx,{"block":body_15},{}).w("");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.nx(ctx.get(["disable_delete"], false),ctx,{"block":body_16},{});}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_delete"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.x(ctx.get(["delete_pending"], false),ctx,{"else":body_18,"block":body_19},{});}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w(" ").f(ctx.get(["tDelete"], false),ctx,"h").w("");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("").f(ctx.get(["tBackup is marked for deletion. Do not delete"], false),ctx,"h").w(" ").f(ctx.get(["tDelete now"], false),ctx,"h").w("");}body_19.__dustBody=!0;function body_20(chk,ctx){return chk.nx(ctx.get(["backup_images"], false),ctx,{"block":body_21},{});}body_20.__dustBody=!0;function body_21(chk,ctx){return chk.w("

    ").f(ctx.get(["tNo backups"], false),ctx,"h").w("

    ").f(ctx.get(["tNo backups of this client yet"], false),ctx,"h");}body_21.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_clients",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClients"], false),ctx,"h").w("
    ").f(ctx.get(["rows"], false),ctx,"h",["s"]).w("
     ").f(ctx.get(["tComputer name"], false),ctx,"h").w("").f(ctx.get(["tLast file backup"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_clients_row",body_0);function body_0(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["lastbackup"], false),ctx,"h",["s"]).w("");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backups_error",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tError while accessing backups"], false),ctx,"h").w("
    ").f(ctx.get(["tSorry, something went wrong:"], false),ctx,"h").w(" ").f(ctx.get(["err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); From 164b585491acc1a365ffa64c8442814b14dfba6d Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 17 Mar 2024 09:38:13 +0100 Subject: [PATCH 315/469] Increment version --- configure.ac_client | 2 +- configure.ac_server | 2 +- urbackupserver/www/js/urbackup.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/configure.ac_client b/configure.ac_client index 804b5f287..742781b8b 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.25.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.26.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/configure.ac_server b/configure.ac_server index 4cb5ac03c..8d3cb8a13 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.32.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.33.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 3527309d1..e0a8e5bc6 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5,7 +5,7 @@ g.startup=true; g.no_tab_mouse_click=false; g.tabberidx=-1; g.progress_stop_id=-1; -g.current_version=2005003200; +g.current_version=2005003300; g.status_show_all=false; g.ldap_login=false; g.datatable_default_config={}; From a61902380916b2570ed09756b4cd83113fb99342 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 17 Mar 2024 09:58:17 +0100 Subject: [PATCH 316/469] Use VS 2022 --- build_server.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_server.bat b/build_server.bat index 655d1182a..e8bd84e5b 100644 --- a/build_server.bat +++ b/build_server.bat @@ -1,4 +1,4 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" +call "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" git reset --hard python build\replace_versions.py From 8fd444d0c7655eb94ff4d51636ff8a3a0139ec9f Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 17 Mar 2024 10:14:26 +0100 Subject: [PATCH 317/469] Fix VS path --- build_server.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build_server.bat b/build_server.bat index e8bd84e5b..7154e0355 100644 --- a/build_server.bat +++ b/build_server.bat @@ -1,4 +1,4 @@ -call "C:\Program Files (x86)\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" +call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsamd64_x86.bat" git reset --hard python build\replace_versions.py From ac806cbe0c722bfd1d27b3eef397feb09f030037 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 17 Mar 2024 10:54:06 +0100 Subject: [PATCH 318/469] Replace with --- blockalign_src/crc.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockalign_src/crc.h b/blockalign_src/crc.h index 1c9219ec1..556a2f542 100644 --- a/blockalign_src/crc.h +++ b/blockalign_src/crc.h @@ -7,7 +7,7 @@ #pragma once #include -#include +#include namespace cryptopp_crc { From d00ed5eecd9196defba721d977b58be7e2bc884c Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 17 Mar 2024 21:46:29 +0100 Subject: [PATCH 319/469] Demodernize --- urbackupserver/dllmain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index cd50f4fac..befb485d2 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2226,9 +2226,9 @@ std::string fixArchiveMigration(IDatabase* db, const std::string& archive_str) { std::string idx = getafter("backup_types_", it->first); - auto it_for = archive_settings.find("for_" + idx); - auto it_for_unit = archive_settings.find("for_unit_" + idx); - auto it_uuid = archive_settings.find("uuid_" + idx); + str_map::iterator it_for = archive_settings.find("for_" + idx); + str_map::iterator it_for_unit = archive_settings.find("for_unit_" + idx); + str_map::iterator it_uuid = archive_settings.find("uuid_" + idx); if (it_uuid != archive_settings.end() && it_for != archive_settings.end() && it_for_unit != archive_settings.end() && From da9b44b4d01514ad6a047450e2aa93e8faa0fcc4 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 17 Mar 2024 23:52:31 +0100 Subject: [PATCH 320/469] Demodernize --- urbackupserver/serverinterface/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index 5de9b153c..0be1de6c3 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -126,7 +126,7 @@ JSON::Array getAlertScripts(IDatabase* db) JSON::Value addNextArchival(IDatabase* db, int clientid, IQuery* get_next, const JSON::Value& archive_val) { - if (archive_val.getType() != JSON::Value_type::str_type) + if (archive_val.getType() != JSON::str_type) return archive_val; std::string archive_str = archive_val.getString(); From c07938f469dfa25137715955e5795bddc67dac6f Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 21 Apr 2024 18:38:14 +0200 Subject: [PATCH 321/469] Fix image restore --- urbackupclient/client_restore.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/client_restore.cpp b/urbackupclient/client_restore.cpp index a6a1f0269..8d9dbaf84 100644 --- a/urbackupclient/client_restore.cpp +++ b/urbackupclient/client_restore.cpp @@ -590,7 +590,7 @@ EDownloadResult downloadImage(int img_id, std::string img_time, std::string outf std::string dl_args; if (img_id != 0 || !img_time.empty()) { - dl_args = "&img_id = "+convert(img_id)+" & time = "+img_time; + dl_args = "&img_id="+convert(img_id)+"&time="+img_time; } else if (login_data.has_login_data && !login_data.token.empty()) From 9ea4a65582780ccb0274ed755f3cb70c9fdc8789 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 5 May 2024 23:25:01 +0200 Subject: [PATCH 322/469] Fix vhd(x) file used size calculation --- fsimageplugin/vhdfile.cpp | 3 ++- fsimageplugin/vhdxfile.cpp | 12 ++++++------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/fsimageplugin/vhdfile.cpp b/fsimageplugin/vhdfile.cpp index 7f20184b8..c2bc4a865 100644 --- a/fsimageplugin/vhdfile.cpp +++ b/fsimageplugin/vhdfile.cpp @@ -1179,12 +1179,13 @@ uint64 VHDFile::getRealSize(void) uint64 VHDFile::usedSize(void) { - uint64 offset_backup=curr_offset; + const uint64 offset_backup=curr_offset; uint64 used_size=0; for(uint64 i=0;i(bat_buf.data()) + block; - - if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT || - bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) + spos = i; + if(has_sector()) ret += block_size; } + spos = spos_backup; + return ret; } From ef9865c38a6abc89e88e57ca97bd90bb4f661318 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 5 May 2024 23:26:13 +0200 Subject: [PATCH 323/469] Increase image restore buffer size --- urbackupclient/ClientService.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 2f9bc01af..91cffd4ca 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -3114,7 +3114,7 @@ void ClientConnector::downloadImage(str_map params, IScopedLock& backup_mutex_lo return; } - const size_t c_buffer_size=32768; + const size_t c_buffer_size=512*1024; const unsigned int c_blocksize=4096; char buf[c_buffer_size]; _i64 read=0; From 53e711ff505e360d1819d40fcb93fb47f1afbcf2 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 5 May 2024 23:26:34 +0200 Subject: [PATCH 324/469] Improve image restore performance --- urbackupserver/server_channel.cpp | 47 ++++++++++++++++--------------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/urbackupserver/server_channel.cpp b/urbackupserver/server_channel.cpp index 6e3f1ae19..4d8a779c2 100644 --- a/urbackupserver/server_channel.cpp +++ b/urbackupserver/server_channel.cpp @@ -1227,10 +1227,13 @@ void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) return; } unsigned int blocksize=vhdfile->getBlocksize(); - char buffer[4096]; + char buffer_with_offset[4096+sizeof(uint64)]; + char* buffer = buffer_with_offset + sizeof(uint64); size_t read; uint64 currpos=offset; _i64 currblock=(currpos+skip)%blocksize; + const _u32 update_every = 1024 * 1024; + uint64 update_pos = currpos; /*vhdfile->Read(buffer, 512, read); if(read!=512) @@ -1293,12 +1296,9 @@ void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) Server->Log("Error reading from VHD file during restore. "+os_last_error_str(), LL_ERROR); } - uint64 currpos_endian = little_endian(currpos); - bool b = input->Write((char*)&currpos_endian, sizeof(uint64), img_send_timeout, false); - if (b) - { - b = input->Write(buffer, (_u32)read, img_send_timeout, false); - } + const uint64 currpos_endian = little_endian(currpos); + memcpy(buffer_with_offset, &currpos_endian, sizeof(currpos_endian)); + bool b = input->Write(buffer_with_offset, static_cast<_u32>(sizeof(currpos_endian) + read), img_send_timeout, false); if(!b) { Server->Log("Writing to output pipe failed processMsg-1", LL_ERROR); @@ -1312,13 +1312,10 @@ void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) { if(Server->getTimeMS()-lasttime>30000) { - uint64 currpos_endian = little_endian(currpos); - bool b = input->Write((char*)&currpos_endian, sizeof(uint64), img_send_timeout, false); + const uint64 currpos_endian = little_endian(currpos); + memcpy(buffer_with_offset, &currpos_endian, sizeof(currpos_endian)); memset(buffer, 0, 4096); - if (b) - { - b = input->Write(buffer, (_u32)4096, img_send_timeout, true); - } + bool b = input->Write(buffer_with_offset, static_cast<_u32>(sizeof(currpos_endian)+4096), img_send_timeout, false); if (!b) { Server->Log("Sending keep-alive block failed", LL_DEBUG); @@ -1332,20 +1329,24 @@ void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) } currpos+=read; - if(Server->getTimeMS()-last_update_time>60000) + if(currpos - update_pos > update_every) { - last_update_time=Server->getTimeMS(); - ServerStatus::updateActive(); - - if (used_bytes > 0) + update_pos = currpos; + if (Server->getTimeMS() - last_update_time > 60000) { - int pcdone_new = static_cast((used_transferred_bytes * 100) / used_bytes); - if (pcdone_new != pcdone) + last_update_time = Server->getTimeMS(); + ServerStatus::updateActive(); + + if (used_bytes > 0) { - pcdone = pcdone_new; - ServerStatus::setProcessPcDone(clientname, restore_process.getStatusId(), pcdone); + int pcdone_new = static_cast((used_transferred_bytes * 100) / used_bytes); + if (pcdone_new != pcdone) + { + pcdone = pcdone_new; + ServerStatus::setProcessPcDone(clientname, restore_process.getStatusId(), pcdone); + } } - } + } } } while( is_ok && (_i64)currpos Date: Thu, 20 Jun 2024 23:17:28 +0200 Subject: [PATCH 325/469] Fix iteration over letters to archive (cherry picked from commit 6d82e78b6df26423c5f10f50252bc9be72671070) --- urbackupserver/server_archive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/server_archive.cpp b/urbackupserver/server_archive.cpp index 31cb8955b..3b7aeec2d 100644 --- a/urbackupserver/server_archive.cpp +++ b/urbackupserver/server_archive.cpp @@ -157,7 +157,7 @@ void ServerAutomaticArchive::archiveBackups(void) else { Tokenize(letter_str, letters, ",;"); - for (size_t k = 0; k < res.size();) + for (size_t k = 0; k < letters.size();) { if (letters[k].empty()) { From 99c105863b2eceaebbf7cc70c9da1a67ab57490a Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 22 Jul 2024 00:22:22 +0200 Subject: [PATCH 326/469] Fix handling of relative symlinks to non-existent targets --- urbackupclient/client.cpp | 2 +- urbackupcommon/os_functions.h | 2 ++ urbackupcommon/os_functions_lin.cpp | 46 +++++++++++++++++++++++++++- urbackupcommon/os_functions_win.cpp | 47 +++++++++++++++++++++++++++++ 4 files changed, 95 insertions(+), 2 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index e933fdbb8..71dd63f86 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -8480,7 +8480,7 @@ bool IndexThread::getAbsSymlinkTarget( const std::string& symlink, const std::st target = orig_path + os_file_sep() + target; } - target = os_get_final_path(target); + target = os_get_final_path_str(target); } std::string lower_target; diff --git a/urbackupcommon/os_functions.h b/urbackupcommon/os_functions.h index 1f81c7023..664aead16 100644 --- a/urbackupcommon/os_functions.h +++ b/urbackupcommon/os_functions.h @@ -108,6 +108,8 @@ bool os_create_dir_recursive(std::string fn); std::string os_get_final_path(std::string path); +std::string os_get_final_path_str(const std::string& path); + bool os_rename_file(std::string src, std::string dst, void* transaction=NULL); void* os_start_transaction(); diff --git a/urbackupcommon/os_functions_lin.cpp b/urbackupcommon/os_functions_lin.cpp index 9484ab57a..8ba36ef65 100644 --- a/urbackupcommon/os_functions_lin.cpp +++ b/urbackupcommon/os_functions_lin.cpp @@ -1072,6 +1072,50 @@ std::string os_get_final_path(std::string path) return ret; } +std::string os_get_final_path_str(const std::string& path) +{ + if (path.empty()) + return path; + + if (path[0] != '/') + return path; + + std::vector toks; + Tokenize(path.substr(1), toks, os_file_sep()); + + std::vector finalToks; + for (size_t i = 0; i < toks.size(); ++i) + { + std::string& pathComponent = toks[i]; + + if (pathComponent.empty() || pathComponent == ".") + { + continue; + } + else if (pathComponent == "..") + { + if (finalToks.empty()) + return path; + + finalToks.pop_back(); + continue; + } + + finalToks.push_back(&pathComponent); + } + + if (toks.size() == finalToks.size()) + return path; + + std::string ret; + for (size_t i = 0; i < finalToks.size(); ++i) + { + ret += os_file_sep() + *finalToks[i]; + } + + return ret; +} + bool os_path_absolute(const std::string& path) { if(!path.empty() && path[0]=='/') @@ -1554,4 +1598,4 @@ int os_system(const std::string& cmd) #else return system(cmd.c_str()); #endif -} +} \ No newline at end of file diff --git a/urbackupcommon/os_functions_win.cpp b/urbackupcommon/os_functions_win.cpp index 0985ca4e7..2cea60b7b 100644 --- a/urbackupcommon/os_functions_win.cpp +++ b/urbackupcommon/os_functions_win.cpp @@ -1393,6 +1393,53 @@ std::string os_get_final_path(std::string path) #endif } +std::string os_get_final_path_str(const std::string& path) +{ + if (path.empty()) + return path; + + std::vector toks; + Tokenize(path, toks, os_file_sep()); + + std::vector finalToks; + for (size_t i = 0; i < toks.size(); ++i) + { + std::string& pathComponent = toks[i]; + + if (pathComponent.empty() || pathComponent == ".") + { + continue; + } + else if (pathComponent == "..") + { + if (finalToks.empty()) + return path; + + finalToks.pop_back(); + continue; + } + + finalToks.push_back(&pathComponent); + } + + if (toks.size() == finalToks.size()) + return path; + + std::string ret; + + if (!finalToks.empty()) + { + ret += *finalToks[0]; + } + + for (size_t i = 1; i < finalToks.size(); ++i) + { + ret += os_file_sep() + *finalToks[i]; + } + + return ret; +} + bool os_rename_file(std::string src, std::string dst, void* transaction) { BOOL rc; From 06bed3955e8257151a93abb248cafd3f10f09f1d Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 26 Oct 2024 14:51:05 +0200 Subject: [PATCH 327/469] Add ARM64 build --- .gitignore | 12 ++ CompiledServer.vcxproj | 143 +++++++++++++++++- UrBackupBackend.sln | 79 +++++++++- blockalign_src/blockalign.vcxproj | 69 +++++++++ blockalign_src/crc.cpp | 2 +- build_client.bat | 4 - build_client_backend.bat | 6 + clientctl/clientctl.vcxproj | 71 +++++++++ cryptoplugin/cryptoplugin.vcxproj | 85 +++++++++++ fileservplugin/fileservplugin.vcxproj | 86 +++++++++++ fsimageplugin/fsimageplugin.vcxproj | 88 +++++++++++ httpserver/httpserver.vcxproj | 78 ++++++++++ luaplugin/luaplugin.vcxproj | 69 +++++++++ .../sysvol_test/sysvol_test.vcxproj | 71 +++++++++ urbackupclient/urbackupclient.vcxproj | 89 +++++++++++ urbackupserver/urbackupserver.vcxproj | 89 +++++++++++ urlplugin/urlplugin.vcxproj | 76 ++++++++++ 17 files changed, 1107 insertions(+), 10 deletions(-) diff --git a/.gitignore b/.gitignore index d8078a62d..bf133e3a3 100644 --- a/.gitignore +++ b/.gitignore @@ -314,3 +314,15 @@ cryptoplugin/src/m4/libtool.m4 /btrfs/btrfsplugin/x64 /btrfs/fuse/oslib/x64 /btrfs/fuse/x64 +/ARM64/* +/blockalign_src/ARM64 +/clientctl/ARM64 +/cryptoplugin/ARM64 +/fileservplugin/ARM64 +/fsimageplugin/ARM64 +/httpserver/ARM64 +/luaplugin/ARM64 +/urbackupclient/ARM64 +/urbackupclient/sysvol_test/ARM64 +/urbackupserver/ARM64 +/urlplugin/ARM64 diff --git a/CompiledServer.vcxproj b/CompiledServer.vcxproj index c4a839fd8..b65ea1a57 100644 --- a/CompiledServer.vcxproj +++ b/CompiledServer.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release Service + ARM64 + Release Service Win32 @@ -17,6 +25,10 @@ Release Service x64 + + Release + ARM64 + Release Win32 @@ -57,17 +69,34 @@ true v143 + + Application + Unicode + true + v143 + Application Unicode true v143 + + Application + Unicode + true + v143 + Application Unicode v143 + + Application + Unicode + v143 + @@ -83,12 +112,21 @@ + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 @@ -96,38 +134,56 @@ $(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true + true $(SolutionDir)$(Configuration)\ $(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false + false $(SolutionDir)$(Configuration)\ $(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -135,15 +191,24 @@ x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -193,13 +258,38 @@ %(AdditionalLibraryDirectories) true Console - false + true MachineX64 Dbghelp.lib;%(AdditionalDependencies) + + + + Disabled + ./libfastcgi;%(AdditionalIncludeDirectories) + DO_NOT_USE_CRYPTOPP_MD5;WIN32;_DEBUG;_CONSOLE;SQLITE_ENABLE_UNLOCK_NOTIFY;SQLITE_ENABLE_DBPAGE_VTAB;THREAD_BOOST;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + 4005;%(DisableSpecificWarnings) + + + %(AdditionalLibraryDirectories) + true + Console + true + + + Dbghelp.lib;%(AdditionalDependencies) + + ./libfastcgi_win;%(AdditionalIncludeDirectories) @@ -241,7 +331,7 @@ Console true true - false + true MachineX64 @@ -250,6 +340,30 @@ Dbghelp.lib;%(AdditionalDependencies) + + + + ./libfastcgi_win;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;SQLITE_ENABLE_UNLOCK_NOTIFY;SQLITE_ENABLE_DBPAGE_VTAB;THREAD_BOOST;DO_NOT_USE_CRYPTOPP_MD5;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + true + Console + true + true + true + + + + + Dbghelp.lib;%(AdditionalDependencies) + + ./libfastcgi_win;%(AdditionalIncludeDirectories) @@ -292,13 +406,36 @@ Console true true - false + true MachineX64 Dbghelp.lib;%(AdditionalDependencies) + + + + ./libfastcgi_win;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_CONSOLE;AS_SERVICE;SQLITE_ENABLE_UNLOCK_NOTIFY;SQLITE_ENABLE_DBPAGE_VTAB;DO_NOT_USE_CRYPTOPP_MD5;%(PreprocessorDefinitions) + MultiThreadedDLL + + + Level3 + ProgramDatabase + + + libx64/;D:\Developement\urbackup_libs\libx64;%(AdditionalLibraryDirectories) + true + Console + true + true + true + + + Dbghelp.lib;%(AdditionalDependencies) + + diff --git a/UrBackupBackend.sln b/UrBackupBackend.sln index ed795108d..2ace2fcfb 100644 --- a/UrBackupBackend.sln +++ b/UrBackupBackend.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30114.105 +# Visual Studio Version 17 +VisualStudioVersion = 17.11.35327.3 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "fileservplugin", "fileservplugin\fileservplugin.vcxproj", "{B1F1AF2E-E544-45F7-864A-883461A4B574}" EndProject @@ -31,145 +31,220 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "luaplugin", "luaplugin\luap EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM64 = Debug|ARM64 Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 + Release Service|ARM64 = Release Service|ARM64 Release Service|Win32 = Release Service|Win32 Release Service|x64 = Release Service|x64 + Release|ARM64 = Release|ARM64 Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B1F1AF2E-E544-45F7-864A-883461A4B574}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {B1F1AF2E-E544-45F7-864A-883461A4B574}.Debug|ARM64.Build.0 = Debug|ARM64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Debug|Win32.ActiveCfg = Debug|Win32 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Debug|Win32.Build.0 = Debug|Win32 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Debug|x64.ActiveCfg = Debug|x64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Debug|x64.Build.0 = Debug|x64 + {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release Service|ARM64.Build.0 = Release|ARM64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release Service|Win32.ActiveCfg = Release|x64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release Service|x64.ActiveCfg = Release|x64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release Service|x64.Build.0 = Release|x64 + {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release|ARM64.ActiveCfg = Release|ARM64 + {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release|ARM64.Build.0 = Release|ARM64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release|Win32.ActiveCfg = Release|Win32 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release|Win32.Build.0 = Release|Win32 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release|x64.ActiveCfg = Release|x64 {B1F1AF2E-E544-45F7-864A-883461A4B574}.Release|x64.Build.0 = Release|x64 + {20375DC0-38DA-4254-B479-EFA8028C29B1}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {20375DC0-38DA-4254-B479-EFA8028C29B1}.Debug|ARM64.Build.0 = Debug|ARM64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Debug|Win32.ActiveCfg = Debug|Win32 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Debug|Win32.Build.0 = Debug|Win32 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Debug|x64.ActiveCfg = Debug|x64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Debug|x64.Build.0 = Debug|x64 + {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release Service|ARM64.Build.0 = Release|ARM64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release Service|Win32.ActiveCfg = Release|x64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release Service|x64.ActiveCfg = Release|x64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release Service|x64.Build.0 = Release|x64 + {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release|ARM64.ActiveCfg = Release|ARM64 + {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release|ARM64.Build.0 = Release|ARM64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release|Win32.ActiveCfg = Release|Win32 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release|Win32.Build.0 = Release|Win32 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release|x64.ActiveCfg = Release|x64 {20375DC0-38DA-4254-B479-EFA8028C29B1}.Release|x64.Build.0 = Release|x64 + {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Debug|ARM64.Build.0 = Debug|ARM64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Debug|Win32.ActiveCfg = Debug|Win32 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Debug|Win32.Build.0 = Debug|Win32 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Debug|x64.ActiveCfg = Debug|x64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Debug|x64.Build.0 = Debug|x64 + {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release Service|ARM64.Build.0 = Release|ARM64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release Service|Win32.ActiveCfg = Release|x64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release Service|x64.ActiveCfg = Release|x64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release Service|x64.Build.0 = Release|x64 + {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release|ARM64.ActiveCfg = Release|ARM64 + {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release|ARM64.Build.0 = Release|ARM64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release|Win32.ActiveCfg = Release|Win32 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release|Win32.Build.0 = Release|Win32 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release|x64.ActiveCfg = Release|x64 {3E6BBB51-77D4-4DC7-BF09-FC6F21CA04D5}.Release|x64.Build.0 = Release|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|ARM64.Build.0 = Debug|ARM64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|Win32.ActiveCfg = Debug|Win32 {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|Win32.Build.0 = Debug|Win32 {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|x64.ActiveCfg = Debug|x64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Debug|x64.Build.0 = Debug|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|ARM64.Build.0 = Release|ARM64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|Win32.ActiveCfg = Release|x64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|x64.ActiveCfg = Release|x64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release Service|x64.Build.0 = Release|x64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|ARM64.ActiveCfg = Release|ARM64 + {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|ARM64.Build.0 = Release|ARM64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|Win32.ActiveCfg = Release|Win32 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|Win32.Build.0 = Release|Win32 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|x64.ActiveCfg = Release|x64 {A4E2527B-4886-4163-9411-10BF66A931BE}.Release|x64.Build.0 = Release|x64 + {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|ARM64.Build.0 = Debug|ARM64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|Win32.ActiveCfg = Debug|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|Win32.Build.0 = Debug|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|x64.ActiveCfg = Debug|x64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Debug|x64.Build.0 = Debug|x64 + {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release Service|ARM64.Build.0 = Release|ARM64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release Service|Win32.ActiveCfg = Release|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release Service|Win32.Build.0 = Release|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release Service|x64.ActiveCfg = Release|x64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release Service|x64.Build.0 = Release|x64 + {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release|ARM64.ActiveCfg = Release|ARM64 + {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release|ARM64.Build.0 = Release|ARM64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release|Win32.ActiveCfg = Release|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release|Win32.Build.0 = Release|Win32 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release|x64.ActiveCfg = Release|x64 {28D66E10-BF1E-45E3-B4E0-77920126531B}.Release|x64.Build.0 = Release|x64 + {8546D6E2-1872-418B-9766-E40F33689BE4}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {8546D6E2-1872-418B-9766-E40F33689BE4}.Debug|ARM64.Build.0 = Debug|ARM64 {8546D6E2-1872-418B-9766-E40F33689BE4}.Debug|Win32.ActiveCfg = Debug|Win32 {8546D6E2-1872-418B-9766-E40F33689BE4}.Debug|Win32.Build.0 = Debug|Win32 {8546D6E2-1872-418B-9766-E40F33689BE4}.Debug|x64.ActiveCfg = Debug|x64 {8546D6E2-1872-418B-9766-E40F33689BE4}.Debug|x64.Build.0 = Debug|x64 + {8546D6E2-1872-418B-9766-E40F33689BE4}.Release Service|ARM64.ActiveCfg = Release Service|ARM64 + {8546D6E2-1872-418B-9766-E40F33689BE4}.Release Service|ARM64.Build.0 = Release Service|ARM64 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release Service|Win32.ActiveCfg = Release Service|Win32 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release Service|Win32.Build.0 = Release Service|Win32 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release Service|x64.ActiveCfg = Release Service|x64 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release Service|x64.Build.0 = Release Service|x64 + {8546D6E2-1872-418B-9766-E40F33689BE4}.Release|ARM64.ActiveCfg = Release|ARM64 + {8546D6E2-1872-418B-9766-E40F33689BE4}.Release|ARM64.Build.0 = Release|ARM64 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release|Win32.ActiveCfg = Release|Win32 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release|Win32.Build.0 = Release|Win32 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release|x64.ActiveCfg = Release|x64 {8546D6E2-1872-418B-9766-E40F33689BE4}.Release|x64.Build.0 = Release|x64 + {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Debug|ARM64.Build.0 = Debug|ARM64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Debug|Win32.ActiveCfg = Debug|Win32 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Debug|Win32.Build.0 = Debug|Win32 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Debug|x64.ActiveCfg = Debug|x64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Debug|x64.Build.0 = Debug|x64 + {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release Service|ARM64.Build.0 = Release|ARM64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release Service|Win32.ActiveCfg = Release|x64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release Service|x64.ActiveCfg = Release|x64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release Service|x64.Build.0 = Release|x64 + {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release|ARM64.ActiveCfg = Release|ARM64 + {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release|ARM64.Build.0 = Release|ARM64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release|Win32.ActiveCfg = Release|Win32 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release|Win32.Build.0 = Release|Win32 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release|x64.ActiveCfg = Release|x64 {A9B12FBF-84D8-4BB6-B4A3-DD57F06637B0}.Release|x64.Build.0 = Release|x64 + {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Debug|ARM64.Build.0 = Debug|ARM64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Debug|Win32.ActiveCfg = Debug|Win32 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Debug|Win32.Build.0 = Debug|Win32 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Debug|x64.ActiveCfg = Debug|x64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Debug|x64.Build.0 = Debug|x64 + {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release Service|ARM64.Build.0 = Release|ARM64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release Service|Win32.ActiveCfg = Release|x64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release Service|x64.ActiveCfg = Release|x64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release Service|x64.Build.0 = Release|x64 + {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release|ARM64.ActiveCfg = Release|ARM64 + {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release|ARM64.Build.0 = Release|ARM64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release|Win32.ActiveCfg = Release|Win32 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release|Win32.Build.0 = Release|Win32 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release|x64.ActiveCfg = Release|x64 {D1BF1BE4-1F36-4E19-8631-CB4C93B77E9B}.Release|x64.Build.0 = Release|x64 + {09263E7C-F43C-4925-B672-22C1818D8CCD}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {09263E7C-F43C-4925-B672-22C1818D8CCD}.Debug|ARM64.Build.0 = Debug|ARM64 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Debug|Win32.ActiveCfg = Debug|Win32 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Debug|Win32.Build.0 = Debug|Win32 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Debug|x64.ActiveCfg = Debug|x64 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Debug|x64.Build.0 = Debug|x64 + {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release Service|ARM64.Build.0 = Release|ARM64 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release Service|Win32.ActiveCfg = Release|Win32 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release Service|Win32.Build.0 = Release|Win32 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release Service|x64.ActiveCfg = Release|x64 + {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release|ARM64.ActiveCfg = Release|ARM64 + {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release|ARM64.Build.0 = Release|ARM64 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release|Win32.ActiveCfg = Release|Win32 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release|Win32.Build.0 = Release|Win32 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release|x64.ActiveCfg = Release|x64 {09263E7C-F43C-4925-B672-22C1818D8CCD}.Release|x64.Build.0 = Release|x64 + {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Debug|ARM64.Build.0 = Debug|ARM64 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Debug|Win32.ActiveCfg = Debug|Win32 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Debug|x64.ActiveCfg = Debug|x64 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Debug|x64.Build.0 = Debug|x64 + {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release Service|ARM64.Build.0 = Release|ARM64 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release Service|Win32.ActiveCfg = Release|Win32 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release Service|Win32.Build.0 = Release|Win32 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release Service|x64.ActiveCfg = Release|x64 + {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release|ARM64.ActiveCfg = Release|ARM64 + {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release|ARM64.Build.0 = Release|ARM64 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release|Win32.ActiveCfg = Release|Win32 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release|Win32.Build.0 = Release|Win32 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release|x64.ActiveCfg = Release|x64 {DC9628DB-0FBF-4E1C-944C-6F877E185FA7}.Release|x64.Build.0 = Release|x64 + {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Debug|ARM64.Build.0 = Debug|ARM64 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Debug|Win32.ActiveCfg = Debug|Win32 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Debug|Win32.Build.0 = Debug|Win32 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Debug|x64.ActiveCfg = Debug|x64 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Debug|x64.Build.0 = Debug|x64 + {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release Service|ARM64.Build.0 = Release|ARM64 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release Service|Win32.ActiveCfg = Release|Win32 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release Service|Win32.Build.0 = Release|Win32 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release Service|x64.ActiveCfg = Release|x64 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release Service|x64.Build.0 = Release|x64 + {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release|ARM64.ActiveCfg = Release|ARM64 + {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release|ARM64.Build.0 = Release|ARM64 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release|Win32.ActiveCfg = Release|Win32 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release|Win32.Build.0 = Release|Win32 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release|x64.ActiveCfg = Release|x64 {C2F8110F-6103-4669-9CA5-C332DC5FE228}.Release|x64.Build.0 = Release|x64 + {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Debug|ARM64.ActiveCfg = Debug|ARM64 + {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Debug|ARM64.Build.0 = Debug|ARM64 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Debug|Win32.ActiveCfg = Debug|Win32 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Debug|Win32.Build.0 = Debug|Win32 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Debug|x64.ActiveCfg = Debug|x64 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Debug|x64.Build.0 = Debug|x64 + {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release Service|ARM64.ActiveCfg = Release|ARM64 + {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release Service|ARM64.Build.0 = Release|ARM64 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release Service|Win32.ActiveCfg = Release|Win32 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release Service|Win32.Build.0 = Release|Win32 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release Service|x64.ActiveCfg = Release|x64 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release Service|x64.Build.0 = Release|x64 + {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release|ARM64.ActiveCfg = Release|ARM64 + {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release|ARM64.Build.0 = Release|ARM64 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release|Win32.ActiveCfg = Release|Win32 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release|Win32.Build.0 = Release|Win32 {249B45EB-BDD2-4F47-B2F8-3F4E3B805ACC}.Release|x64.ActiveCfg = Release|x64 diff --git a/blockalign_src/blockalign.vcxproj b/blockalign_src/blockalign.vcxproj index 263030e91..be8ef78b4 100644 --- a/blockalign_src/blockalign.vcxproj +++ b/blockalign_src/blockalign.vcxproj @@ -1,10 +1,18 @@  + + Debug + ARM64 + Debug Win32 + + Release + ARM64 + Release Win32 @@ -53,6 +61,12 @@ v143 Unicode + + Application + true + v143 + Unicode + Application false @@ -60,6 +74,13 @@ true Unicode + + Application + false + v143 + true + Unicode + @@ -74,9 +95,15 @@ + + + + + + true @@ -84,21 +111,33 @@ true + + true + false false + + false + true x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -128,6 +167,19 @@ true + + + + + Level3 + Disabled + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + Level3 @@ -162,6 +214,23 @@ true + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + true + true + + diff --git a/blockalign_src/crc.cpp b/blockalign_src/crc.cpp index 188ef65eb..83b8f3f88 100644 --- a/blockalign_src/crc.cpp +++ b/blockalign_src/crc.cpp @@ -25,7 +25,7 @@ // Intrinsics availible in GCC 4.3 (http://gcc.gnu.org/gcc-4.3/changes.html) and // MSVC 2008 (http://msdn.microsoft.com/en-us/library/bb892950%28v=vs.90%29.aspx) // SunCC could generate SSE4 at 12.1, but the intrinsics are missing until 12.4. -#if !defined(CRYPTOPP_DISABLE_ASM) && !defined(CRYPTOPP_DISABLE_SSE4) && !defined(_M_ARM) && ((_MSC_VER >= 1500) || (defined(__SSE4_1__) && defined(__SSE4_2__))) +#if !defined(CRYPTOPP_DISABLE_ASM) && !defined(CRYPTOPP_DISABLE_SSE4) && !defined(_M_ARM) && !defined(_M_ARM64) && ((_MSC_VER >= 1500) || (defined(__SSE4_1__) && defined(__SSE4_2__))) #define CRYPTOPP_BOOL_SSE4_INTRINSICS_AVAILABLE 1 #else #define CRYPTOPP_BOOL_SSE4_INTRINSICS_AVAILABLE 0 diff --git a/build_client.bat b/build_client.bat index 1ecd9f7ff..68eac1e64 100644 --- a/build_client.bat +++ b/build_client.bat @@ -21,14 +21,10 @@ call build_revision.bat cd "%~dp0" -copy /Y "Release Server 2003\urbackupclient_server03.dll" "Release Server 2003\urbackup_server03.dll" -copy /Y "x64\Release Server 2003\urbackupclient_server03.dll" "x64\Release Server 2003\urbackup_server03.dll" copy /Y "Release\urbackupclient.dll" "Release\urbackup.dll" copy /Y "x64\Release\urbackupclient.dll" "x64\Release\urbackup.dll" -copy /Y "Release WinXP\urbackupclient_xp.dll" "Release Server 2003\urbackup_xp.dll" - FOR /F "tokens=*" %%G IN (pdb_dirs_client.txt) DO symstore add /compress /r /f "%~dp0%%G" /s "C:\symstore" /t "UrBackup Client /v "%build_revision%" /c "Release" diff --git a/build_client_backend.bat b/build_client_backend.bat index f18d5fab4..3bead212c 100644 --- a/build_client_backend.bat +++ b/build_client_backend.bat @@ -6,10 +6,16 @@ if %errorlevel% neq 0 exit /b %errorlevel% msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% +msbuild UrBackupBackend.sln /p:Configuration=Release /p:Platform="ARM64" /p:vcpkgTriplet="arm64-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% + msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="x64" /p:vcpkgTriplet="x64-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="win32" /p:vcpkgTriplet="x86-windows-static-md" if %errorlevel% neq 0 exit /b %errorlevel% +msbuild CompiledServer.vcxproj /p:Configuration="Release Service" /p:Platform="arm64" /p:vcpkgTriplet="arm64-windows-static-md" +if %errorlevel% neq 0 exit /b %errorlevel% + exit /b 0 \ No newline at end of file diff --git a/clientctl/clientctl.vcxproj b/clientctl/clientctl.vcxproj index d9efff268..1fad76d43 100644 --- a/clientctl/clientctl.vcxproj +++ b/clientctl/clientctl.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -37,6 +45,12 @@ Unicode v143 + + Application + true + Unicode + v143 + Application false @@ -51,6 +65,13 @@ Unicode v143 + + Application + false + true + Unicode + v143 + @@ -60,12 +81,18 @@ + + + + + + true @@ -73,21 +100,33 @@ true + + true + false false + + false + true x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md + + arm64-windows-static-md + @@ -116,6 +155,20 @@ ws2_32.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + OS_FUNC_NO_SERVER;WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + ws2_32.lib;%(AdditionalDependencies) + + Level3 @@ -152,6 +205,24 @@ ws2_32.lib;%(AdditionalDependencies) + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;OS_FUNC_NO_SERVER;%(PreprocessorDefinitions) + + + Console + true + true + true + ws2_32.lib;%(AdditionalDependencies) + + diff --git a/cryptoplugin/cryptoplugin.vcxproj b/cryptoplugin/cryptoplugin.vcxproj index 92365b96f..bab5bf4d7 100644 --- a/cryptoplugin/cryptoplugin.vcxproj +++ b/cryptoplugin/cryptoplugin.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -37,6 +45,12 @@ true v143 + + DynamicLibrary + Unicode + true + v143 + DynamicLibrary Unicode @@ -47,6 +61,11 @@ Unicode v143 + + DynamicLibrary + Unicode + v143 + @@ -56,39 +75,57 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 $(SolutionDir)$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true true + true $(SolutionDir)$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -97,9 +134,16 @@ x64-windows-static-md Debug + + arm64-windows-static-md + Debug + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -144,6 +188,25 @@ Windows + + + Disabled + %(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;CRYPTOPLUGIN_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + %(AdditionalDependencies) + $(CryptoppLibDir);$(SolutionDir)/deps/libs;%(AdditionalLibraryDirectories) + true + Windows + + MaxSpeed @@ -189,6 +252,28 @@ %(AdditionalDependencies) + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CRYPTOPLUGIN_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + $(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp; + + + true + Windows + true + true + %(AdditionalLibraryDirectories) + %(AdditionalDependencies) + + diff --git a/fileservplugin/fileservplugin.vcxproj b/fileservplugin/fileservplugin.vcxproj index d29d8b525..df468fd31 100644 --- a/fileservplugin/fileservplugin.vcxproj +++ b/fileservplugin/fileservplugin.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -42,11 +50,22 @@ true v143 + + DynamicLibrary + Unicode + true + v143 + DynamicLibrary Unicode v143 + + DynamicLibrary + Unicode + v143 + @@ -59,9 +78,15 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 @@ -72,23 +97,35 @@ $(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true + true $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -96,9 +133,15 @@ x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md + + arm64-windows-static-md + Disabled @@ -164,6 +207,25 @@ MachineX64 + + + + Disabled + WIN32;_DEBUG;_CONSOLE;DO_NOT_USE_CRYPTOPP_MD5;DO_NOT_USE_CRYPTOPP_SHA;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + ws2_32.lib;%(AdditionalDependencies) + true + Console + + X64 @@ -191,6 +253,30 @@ + + + + MaxSpeed + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + $(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp; + + + ws2_32.lib;%(AdditionalDependencies) + true + Console + true + true + + + + diff --git a/fsimageplugin/fsimageplugin.vcxproj b/fsimageplugin/fsimageplugin.vcxproj index 64a6932d4..b30d29405 100644 --- a/fsimageplugin/fsimageplugin.vcxproj +++ b/fsimageplugin/fsimageplugin.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -42,11 +50,22 @@ true v143 + + DynamicLibrary + Unicode + true + v143 + DynamicLibrary Unicode v143 + + DynamicLibrary + Unicode + v143 + @@ -59,9 +78,15 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 @@ -72,23 +97,35 @@ $(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true + true $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -96,9 +133,15 @@ x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -170,6 +213,27 @@ $(SolutionDir)/deps/libs;$(ZstdLibDir) + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;FSIMAGEPLUGIN_EXPORTS;DO_NOT_USE_CRYPTOPP_MD5;DO_NOT_USE_CRYPTOPP_SHA;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + $(SolutionDir)/deps/include/imdisk;$(ImdiskIncludeDir) + + + true + Windows + ws2_32.lib;%(AdditionalDependencies) + $(SolutionDir)/deps/libs;$(ZstdLibDir) + + X64 @@ -197,6 +261,30 @@ + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;FSIMAGEPLUGIN_EXPORTS;DO_NOT_USE_CRYPTOPP_MD5;DO_NOT_USE_CRYPTOPP_SHA;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + $(SolutionDir)/deps/include/imdisk;$(ImdiskIncludeDir);$(ZstdIncludeDir);$(SolutionDir)/deps/include/zstd + + + true + Windows + true + true + ws2_32.lib;%(AdditionalDependencies) + + + + diff --git a/httpserver/httpserver.vcxproj b/httpserver/httpserver.vcxproj index 89ea023e0..4799634df 100644 --- a/httpserver/httpserver.vcxproj +++ b/httpserver/httpserver.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -37,6 +45,12 @@ true v143 + + DynamicLibrary + Unicode + true + v143 + DynamicLibrary Unicode @@ -47,6 +61,11 @@ Unicode v143 + + DynamicLibrary + Unicode + v143 + @@ -56,39 +75,57 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 $(SolutionDir)$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true true + true $(SolutionDir)$(Configuration)\ $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -96,9 +133,15 @@ x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -136,6 +179,22 @@ Windows + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;HTTPSERVER_EXPORTS;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + true + Windows + + MaxSpeed @@ -175,6 +234,25 @@ true + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;HTTPSERVER_EXPORTS;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + true + Windows + true + true + + diff --git a/luaplugin/luaplugin.vcxproj b/luaplugin/luaplugin.vcxproj index db1859033..85f4abf67 100644 --- a/luaplugin/luaplugin.vcxproj +++ b/luaplugin/luaplugin.vcxproj @@ -1,10 +1,18 @@  + + Debug + ARM64 + Debug Win32 + + Release + ARM64 + Release Win32 @@ -44,6 +52,12 @@ v143 Unicode + + DynamicLibrary + true + v143 + Unicode + DynamicLibrary false @@ -51,6 +65,13 @@ true Unicode + + DynamicLibrary + false + v143 + true + Unicode + @@ -65,9 +86,15 @@ + + + + + + true @@ -75,21 +102,33 @@ true + + true + false false + + false + true x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -119,6 +158,19 @@ true + + + + + Level3 + Disabled + _DEBUG;_WINDOWS;_USRDLL;LUAPLUGIN_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + Level3 @@ -153,6 +205,23 @@ true + + + Level3 + + + MaxSpeed + true + true + NDEBUG;_WINDOWS;_USRDLL;LUAPLUGIN_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + diff --git a/urbackupclient/sysvol_test/sysvol_test.vcxproj b/urbackupclient/sysvol_test/sysvol_test.vcxproj index 32afab4a3..d821c07e0 100644 --- a/urbackupclient/sysvol_test/sysvol_test.vcxproj +++ b/urbackupclient/sysvol_test/sysvol_test.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -37,6 +45,12 @@ Unicode v143 + + Application + true + Unicode + v143 + Application false @@ -51,6 +65,13 @@ Unicode v143 + + Application + false + true + Unicode + v143 + @@ -60,12 +81,18 @@ + + + + + + true @@ -73,21 +100,33 @@ true + + true + false false + + false + true x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -121,6 +160,21 @@ ../../libx86 + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;NO_SERVER;%(PreprocessorDefinitions) + D:\boost + + + Console + true + ../../libx86 + + Level3 @@ -155,6 +209,23 @@ true + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;NO_SERVER;%(PreprocessorDefinitions) + + + Console + true + true + true + + diff --git a/urbackupclient/urbackupclient.vcxproj b/urbackupclient/urbackupclient.vcxproj index 84b96483e..91dfc8c9d 100644 --- a/urbackupclient/urbackupclient.vcxproj +++ b/urbackupclient/urbackupclient.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -156,11 +164,22 @@ true v143 + + DynamicLibrary + Unicode + true + v143 + DynamicLibrary Unicode v143 + + DynamicLibrary + Unicode + v143 + @@ -173,9 +192,15 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 @@ -183,26 +208,38 @@ $(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true + true $(SolutionDir)$(Configuration)\ $(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -210,9 +247,15 @@ x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -262,6 +305,28 @@ + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;DO_NOT_USE_CRYPTOPP_SHA;DO_NOT_USE_CRYPTOPP_MD5;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(ZstdIncludeDir);$(SolutionDir)/deps/include/zstd + + + ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) + true + Windows + + + + MaxSpeed @@ -313,6 +378,30 @@ + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;CLIENT_ONLY;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + $(ZstdIncludeDir);$(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;$(SolutionDir)/deps/include/zstd + + + ws2_32.lib;VssApi.Lib;%(AdditionalDependencies) + true + Console + true + true + + + + diff --git a/urbackupserver/urbackupserver.vcxproj b/urbackupserver/urbackupserver.vcxproj index 23bfe5c9a..1c606e591 100644 --- a/urbackupserver/urbackupserver.vcxproj +++ b/urbackupserver/urbackupserver.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -42,11 +50,22 @@ true v143 + + DynamicLibrary + Unicode + true + v143 + DynamicLibrary Unicode v143 + + DynamicLibrary + Unicode + v143 + @@ -59,9 +78,15 @@ + + + + + + <_ProjectFileVersion>10.0.30319.1 @@ -69,26 +94,38 @@ $(Configuration)\ true $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ true + true $(SolutionDir)$(Configuration)\ $(Configuration)\ false $(SolutionDir)$(Platform)\$(Configuration)\ + $(SolutionDir)$(Platform)\$(Configuration)\ $(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ false + false AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + AllRules.ruleset AllRules.ruleset + AllRules.ruleset + + true @@ -96,9 +133,15 @@ x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -148,6 +191,28 @@ + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;USE_NTFS_TXF;DO_NOT_USE_CRYPTOPP_SHA;DO_NOT_USE_CRYPTOPP_MD5;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(SolutionDir)/deps/include/imdisk;$(ImdiskIncludeDir);$(SolutionDir)/deps/include/zstd;$(ZstdIncludeDir) + + + ws2_32.lib;VssApi.Lib;Ktmw32.lib;%(AdditionalDependencies) + true + Windows + + + + MaxSpeed @@ -199,6 +264,30 @@ + + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;URBACKUP_EXPORTS;SERVER_ONLY;USE_NTFS_TXF;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + $(ZlibIncludeDir);$(SolutionDir)/deps/include/zlib;$(SolutionDir)/../deps/include/zlib;$(CryptoppIncludeDir);$(SolutionDir)/deps/include/cryptopp;$(SolutionDir)/../deps/include/cryptopp;$(SolutionDir)/deps/include/imdisk;$(SolutionDir)/../deps/include/imdisk;$(ImdiskIncludeDir);$(SolutionDir)/deps/include/zstd;$(ZstdIncludeDir);$(SolutionDir)/../deps/include/zstd + + + ws2_32.lib;Ktmw32.lib;%(AdditionalDependencies) + true + Console + true + true + + + + diff --git a/urlplugin/urlplugin.vcxproj b/urlplugin/urlplugin.vcxproj index 8c3d44301..d0b650964 100644 --- a/urlplugin/urlplugin.vcxproj +++ b/urlplugin/urlplugin.vcxproj @@ -1,6 +1,10 @@  + + Debug + ARM64 + Debug Win32 @@ -9,6 +13,10 @@ Debug x64 + + Release + ARM64 + Release Win32 @@ -37,6 +45,12 @@ Unicode v143 + + DynamicLibrary + true + Unicode + v143 + DynamicLibrary false @@ -51,6 +65,13 @@ Unicode v143 + + DynamicLibrary + false + true + Unicode + v143 + @@ -60,12 +81,18 @@ + + + + + + true @@ -73,21 +100,33 @@ true + + true + false false + + false + true x64-windows-static-md + + arm64-windows-static-md + x64-windows-static-md + + x64-windows-static-md + x86-windows-static-md @@ -125,6 +164,23 @@ Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + + + + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;CURL_STATICLIB;%(PreprocessorDefinitions) + $(CurlIncludeDir);$(SolutionDir)/deps/include + + + Console + true + + + Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + Level3 @@ -168,6 +224,26 @@ Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + + Level3 + + + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;CURL_STATICLIB;%(PreprocessorDefinitions) + $(CurlIncludeDir);$(SolutionDir)/deps/include + + + Console + true + true + true + $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs + Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + + From 6b319f70ce2f03963ab8099f5f154bb7b1ab4c71 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 27 Oct 2024 20:07:49 +0100 Subject: [PATCH 328/469] Add setting to disallow setting max number of backups from client (not allowed by default) --- urbackupcommon/capa_bits.h | 3 ++- urbackupcommon/settingslist.cpp | 23 +++++++++++-------- urbackupserver/ClientMain.cpp | 8 +++---- urbackupserver/dllmain.cpp | 2 +- urbackupserver/server_channel.cpp | 2 ++ urbackupserver/server_settings.cpp | 8 ++++++- urbackupserver/server_settings.h | 1 + urbackupserver/www/js/urbackup.js | 3 ++- .../www/templates/settings_inv_row.htm | 9 +++++++- 9 files changed, 40 insertions(+), 19 deletions(-) diff --git a/urbackupcommon/capa_bits.h b/urbackupcommon/capa_bits.h index 5c4106970..239b1de65 100644 --- a/urbackupcommon/capa_bits.h +++ b/urbackupcommon/capa_bits.h @@ -15,4 +15,5 @@ const int DONT_ALLOW_FILE_RESTORE = 1 << 13; const int DONT_ALLOW_COMPONENT_RESTORE = 1 << 14; const int DONT_ALLOW_COMPONENT_CONFIG = 1 << 15; const int STATUS_NO_COMPONENTS = 1 << 16; -const int DONT_ALLOW_STARTING_INCR_IMAGE_BACKUPS = 1 << 17; \ No newline at end of file +const int DONT_ALLOW_STARTING_INCR_IMAGE_BACKUPS = 1 << 17; +const int DONT_ALLOW_CONFIG_MAX_BACKUPS = 1 << 18; \ No newline at end of file diff --git a/urbackupcommon/settingslist.cpp b/urbackupcommon/settingslist.cpp index e454b4b6e..5ff3cfa5f 100644 --- a/urbackupcommon/settingslist.cpp +++ b/urbackupcommon/settingslist.cpp @@ -52,7 +52,6 @@ std::vector getSettingsList(void) ret.push_back("allow_starting_incr_image_backups"); ret.push_back("allow_pause"); ret.push_back("allow_log_view"); - ret.push_back("allow_overwrite"); ret.push_back("allow_tray_exit"); ret.push_back("image_letters"); ret.push_back("internet_server"); @@ -110,24 +109,28 @@ std::vector getSettingsList(void) ret.push_back("hash_threads"); ret.push_back("client_hash_threads"); ret.push_back("image_compress_threads"); + ret.push_back("allow_config_max_backups"); return ret; } -std::vector getClientConfigurableSettingsList() +std::vector getClientConfigurableSettingsList(const bool with_max_backups_config) { std::vector ret; ret.push_back("update_freq_incr"); ret.push_back("update_freq_full"); ret.push_back("update_freq_image_incr"); ret.push_back("update_freq_image_full"); - ret.push_back("max_file_incr"); - ret.push_back("min_file_incr"); - ret.push_back("max_file_full"); - ret.push_back("min_file_full"); - ret.push_back("min_image_incr"); - ret.push_back("max_image_incr"); - ret.push_back("min_image_full"); - ret.push_back("max_image_full"); + if (with_max_backups_config) + { + ret.push_back("max_file_incr"); + ret.push_back("min_file_incr"); + ret.push_back("max_file_full"); + ret.push_back("min_file_full"); + ret.push_back("min_image_incr"); + ret.push_back("max_image_incr"); + ret.push_back("min_image_full"); + ret.push_back("max_image_full"); + } ret.push_back("startup_backup_delay"); ret.push_back("computername"); ret.push_back("virtual_clients"); diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index 894d221ae..f84318542 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -2040,13 +2040,13 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) std::auto_ptr sr(Server->createFileSettingsReader(tmp_fn)); - std::vector setting_names=getClientConfigurableSettingsList(); + std::vector setting_names=getClientConfigurableSettingsList(server_settings->getSettings()->allow_config_max_backups); std::vector merge_settings = getClientMergableSettingsList(); bool mod=false; bool has_use = false; - bool allow_overwrite = server_settings->getSettings()->allow_overwrite; + const bool allow_overwrite = server_settings->getSettings()->allow_overwrite; for (size_t i = 0; i < setting_names.size(); ++i) { @@ -2109,7 +2109,7 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) continue; } - bool b = updateClientSetting(key, value, use, use_lm, curr_allow_overwrite); + const bool b = updateClientSetting(key, value, use, use_lm, curr_allow_overwrite); if (b) mod = true; } @@ -2122,7 +2122,7 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) continue; } - bool b = updateClientSetting(key, value, def_use, 0, curr_allow_overwrite); + const bool b = updateClientSetting(key, value, def_use, 0, curr_allow_overwrite); if (b) mod = true; } diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index befb485d2..a69f0b2a8 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2152,7 +2152,7 @@ bool upgrade59_60() q_get->Reset(); if (!res.empty() && res[0]["value"] == "true") { - std::vector settings = getClientConfigurableSettingsList(); + std::vector settings = getClientConfigurableSettingsList(true); for (size_t j = 0; j < settings.size(); ++j) { q_update_use_key->Bind(c_use_value_client); diff --git a/urbackupserver/server_channel.cpp b/urbackupserver/server_channel.cpp index 4d8a779c2..4411fd87c 100644 --- a/urbackupserver/server_channel.cpp +++ b/urbackupserver/server_channel.cpp @@ -642,6 +642,8 @@ int ServerChannelThread::constructCapabilities(void) capa |= DONT_ALLOW_COMPONENT_RESTORE; if (!cs->allow_component_config) capa |= DONT_ALLOW_COMPONENT_CONFIG; + if (!cs->allow_config_max_backups) + capa |= DONT_ALLOW_CONFIG_MAX_BACKUPS; return capa; } diff --git a/urbackupserver/server_settings.cpp b/urbackupserver/server_settings.cpp index 66351de0c..217f84380 100644 --- a/urbackupserver/server_settings.cpp +++ b/urbackupserver/server_settings.cpp @@ -431,6 +431,7 @@ void ServerSettings::readSettingsDefault(ISettingsReader* settings_default, settings->hash_threads = 1; settings->client_hash_threads = 1; settings->image_compress_threads = 0; + settings->allow_config_max_backups = false; } readStringClientSetting(q_get_client_setting, "update_freq_incr", std::string(), &settings->update_freq_incr, false); @@ -594,6 +595,8 @@ void ServerSettings::readSettingsDefault(ISettingsReader* settings_default, readIntClientSetting(q_get_client_setting, "client_hash_threads", &settings->client_hash_threads, false); readIntClientSetting(q_get_client_setting, "image_compress_threads", &settings->image_compress_threads, false); + + readBoolClientSetting(q_get_client_setting, "allow_config_max_backups", &settings->allow_config_max_backups, false); } void ServerSettings::readSettingsClient(ISettingsReader* settings_client, IQuery* q_get_client_setting) @@ -702,7 +705,7 @@ void ServerSettings::readSettingsClient(ISettingsReader* settings_client, IQuery readBoolClientSetting(q_get_client_setting, "allow_file_restore", &settings->allow_file_restore); readBoolClientSetting(q_get_client_setting, "allow_component_config", &settings->allow_component_config); readBoolClientSetting(q_get_client_setting, "allow_component_restore", &settings->allow_component_restore); - + readStringClientSetting(q_get_client_setting, "image_snapshot_groups", std::string(), &settings->image_snapshot_groups, false); readStringClientSetting(q_get_client_setting, "file_snapshot_groups", std::string(), &settings->file_snapshot_groups, false); @@ -719,6 +722,8 @@ void ServerSettings::readSettingsClient(ISettingsReader* settings_client, IQuery readIntClientSetting(q_get_client_setting, "hash_threads", &settings->hash_threads, false); readIntClientSetting(q_get_client_setting, "client_hash_threads", &settings->client_hash_threads, false); readIntClientSetting(q_get_client_setting, "image_compress_threads", &settings->image_compress_threads, false); + + readBoolClientSetting(q_get_client_setting, "allow_config_max_backups", &settings->allow_config_max_backups); } void ServerSettings::readStringClientSetting(IQuery * q_get_client_setting, int clientid, const std::string & name, const std::string & merge_sep, std::string * output, bool allow_client_value) @@ -1544,6 +1549,7 @@ std::map ServerSettings::getClientS SET_SETTING_INT(hash_threads); SET_SETTING_INT(client_hash_threads); SET_SETTING_INT(image_compress_threads); + SET_SETTING_BOOL(allow_config_max_backups); #undef SET_SETTING return ret; } diff --git a/urbackupserver/server_settings.h b/urbackupserver/server_settings.h index 879cceea1..5bd5eabf3 100644 --- a/urbackupserver/server_settings.h +++ b/urbackupserver/server_settings.h @@ -153,6 +153,7 @@ struct SSettings int hash_threads; int client_hash_threads; int image_compress_threads; + bool allow_config_max_backups; }; struct SLDAPSettings diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index e0a8e5bc6..3d11ffa46 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -4307,7 +4307,8 @@ g.settings_list=[ "download_threads", "hash_threads", "client_hash_threads", -"image_compress_threads" +"image_compress_threads", +"allow_config_max_backups" ]; g.general_settings_list=[ "backupfolder", diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index a6d154f7d..ab567b316 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -237,12 +237,19 @@
    - +
    +
    + +
    + +
    +
    +
    From 99e0afdcf57cde7c1f206cb83dfc7b26eeb846d1 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 27 Oct 2024 20:18:28 +0100 Subject: [PATCH 329/469] Fix build --- urbackupserver/serverinterface/settings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index 0be1de6c3..14ec9bc06 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -511,7 +511,7 @@ void updateClientSettings(int t_clientid, str_map &POST, IDatabase *db) std::vector sset_client_merge = getClientMergableSettingsList(); std::sort(sset_client_merge.begin(), sset_client_merge.end()); - std::vector sset_client_use = getClientConfigurableSettingsList(); + std::vector sset_client_use = getClientConfigurableSettingsList(true); std::sort(sset_client_use.begin(), sset_client_use.end()); std::vector sset_localized = getLocalizedSettingsList(); std::sort(sset_localized.begin(), sset_localized.end()); From aee715b95677f3a5e15498a5d991615fd07d740b Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 27 Oct 2024 20:19:34 +0100 Subject: [PATCH 330/469] Fix build --- urbackupcommon/settingslist.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupcommon/settingslist.h b/urbackupcommon/settingslist.h index 1420e02d5..acec7fdf1 100644 --- a/urbackupcommon/settingslist.h +++ b/urbackupcommon/settingslist.h @@ -2,7 +2,7 @@ #include std::vector getSettingsList(void); -std::vector getClientConfigurableSettingsList(); +std::vector getClientConfigurableSettingsList(const bool with_max_backups_config); std::vector getClientMergableSettingsList(); std::vector getOnlyServerClientSettingsList(void); std::vector getGlobalizedSettingsList(void); From c7069e6396824f856ff47aca9761f0cf53bf7207 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 29 Oct 2024 22:04:22 +0100 Subject: [PATCH 331/469] Revert "Fix build"This reverts commit aee715b95677f3a5e15498a5d991615fd07d740b.Revert "Fix build"This reverts commit 99e0afdcf57cde7c1f206cb83dfc7b26eeb846d1. Revert "Add setting to disallow setting max number of backups from client" This reverts commit 6b319f70ce2f03963ab8099f5f154bb7b1ab4c71. --- urbackupcommon/capa_bits.h | 3 +-- urbackupcommon/settingslist.cpp | 23 ++++++++----------- urbackupcommon/settingslist.h | 2 +- urbackupserver/ClientMain.cpp | 8 +++---- urbackupserver/dllmain.cpp | 2 +- urbackupserver/server_channel.cpp | 2 -- urbackupserver/server_settings.cpp | 8 +------ urbackupserver/server_settings.h | 1 - urbackupserver/serverinterface/settings.cpp | 2 +- urbackupserver/www/js/urbackup.js | 3 +-- .../www/templates/settings_inv_row.htm | 9 +------- 11 files changed, 21 insertions(+), 42 deletions(-) diff --git a/urbackupcommon/capa_bits.h b/urbackupcommon/capa_bits.h index 239b1de65..5c4106970 100644 --- a/urbackupcommon/capa_bits.h +++ b/urbackupcommon/capa_bits.h @@ -15,5 +15,4 @@ const int DONT_ALLOW_FILE_RESTORE = 1 << 13; const int DONT_ALLOW_COMPONENT_RESTORE = 1 << 14; const int DONT_ALLOW_COMPONENT_CONFIG = 1 << 15; const int STATUS_NO_COMPONENTS = 1 << 16; -const int DONT_ALLOW_STARTING_INCR_IMAGE_BACKUPS = 1 << 17; -const int DONT_ALLOW_CONFIG_MAX_BACKUPS = 1 << 18; \ No newline at end of file +const int DONT_ALLOW_STARTING_INCR_IMAGE_BACKUPS = 1 << 17; \ No newline at end of file diff --git a/urbackupcommon/settingslist.cpp b/urbackupcommon/settingslist.cpp index 5ff3cfa5f..e454b4b6e 100644 --- a/urbackupcommon/settingslist.cpp +++ b/urbackupcommon/settingslist.cpp @@ -52,6 +52,7 @@ std::vector getSettingsList(void) ret.push_back("allow_starting_incr_image_backups"); ret.push_back("allow_pause"); ret.push_back("allow_log_view"); + ret.push_back("allow_overwrite"); ret.push_back("allow_tray_exit"); ret.push_back("image_letters"); ret.push_back("internet_server"); @@ -109,28 +110,24 @@ std::vector getSettingsList(void) ret.push_back("hash_threads"); ret.push_back("client_hash_threads"); ret.push_back("image_compress_threads"); - ret.push_back("allow_config_max_backups"); return ret; } -std::vector getClientConfigurableSettingsList(const bool with_max_backups_config) +std::vector getClientConfigurableSettingsList() { std::vector ret; ret.push_back("update_freq_incr"); ret.push_back("update_freq_full"); ret.push_back("update_freq_image_incr"); ret.push_back("update_freq_image_full"); - if (with_max_backups_config) - { - ret.push_back("max_file_incr"); - ret.push_back("min_file_incr"); - ret.push_back("max_file_full"); - ret.push_back("min_file_full"); - ret.push_back("min_image_incr"); - ret.push_back("max_image_incr"); - ret.push_back("min_image_full"); - ret.push_back("max_image_full"); - } + ret.push_back("max_file_incr"); + ret.push_back("min_file_incr"); + ret.push_back("max_file_full"); + ret.push_back("min_file_full"); + ret.push_back("min_image_incr"); + ret.push_back("max_image_incr"); + ret.push_back("min_image_full"); + ret.push_back("max_image_full"); ret.push_back("startup_backup_delay"); ret.push_back("computername"); ret.push_back("virtual_clients"); diff --git a/urbackupcommon/settingslist.h b/urbackupcommon/settingslist.h index acec7fdf1..1420e02d5 100644 --- a/urbackupcommon/settingslist.h +++ b/urbackupcommon/settingslist.h @@ -2,7 +2,7 @@ #include std::vector getSettingsList(void); -std::vector getClientConfigurableSettingsList(const bool with_max_backups_config); +std::vector getClientConfigurableSettingsList(); std::vector getClientMergableSettingsList(); std::vector getOnlyServerClientSettingsList(void); std::vector getGlobalizedSettingsList(void); diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index f84318542..894d221ae 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -2040,13 +2040,13 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) std::auto_ptr sr(Server->createFileSettingsReader(tmp_fn)); - std::vector setting_names=getClientConfigurableSettingsList(server_settings->getSettings()->allow_config_max_backups); + std::vector setting_names=getClientConfigurableSettingsList(); std::vector merge_settings = getClientMergableSettingsList(); bool mod=false; bool has_use = false; - const bool allow_overwrite = server_settings->getSettings()->allow_overwrite; + bool allow_overwrite = server_settings->getSettings()->allow_overwrite; for (size_t i = 0; i < setting_names.size(); ++i) { @@ -2109,7 +2109,7 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) continue; } - const bool b = updateClientSetting(key, value, use, use_lm, curr_allow_overwrite); + bool b = updateClientSetting(key, value, use, use_lm, curr_allow_overwrite); if (b) mod = true; } @@ -2122,7 +2122,7 @@ bool ClientMain::getClientSettings(bool& doesnt_exist) continue; } - const bool b = updateClientSetting(key, value, def_use, 0, curr_allow_overwrite); + bool b = updateClientSetting(key, value, def_use, 0, curr_allow_overwrite); if (b) mod = true; } diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index a69f0b2a8..befb485d2 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -2152,7 +2152,7 @@ bool upgrade59_60() q_get->Reset(); if (!res.empty() && res[0]["value"] == "true") { - std::vector settings = getClientConfigurableSettingsList(true); + std::vector settings = getClientConfigurableSettingsList(); for (size_t j = 0; j < settings.size(); ++j) { q_update_use_key->Bind(c_use_value_client); diff --git a/urbackupserver/server_channel.cpp b/urbackupserver/server_channel.cpp index 4411fd87c..4d8a779c2 100644 --- a/urbackupserver/server_channel.cpp +++ b/urbackupserver/server_channel.cpp @@ -642,8 +642,6 @@ int ServerChannelThread::constructCapabilities(void) capa |= DONT_ALLOW_COMPONENT_RESTORE; if (!cs->allow_component_config) capa |= DONT_ALLOW_COMPONENT_CONFIG; - if (!cs->allow_config_max_backups) - capa |= DONT_ALLOW_CONFIG_MAX_BACKUPS; return capa; } diff --git a/urbackupserver/server_settings.cpp b/urbackupserver/server_settings.cpp index 217f84380..66351de0c 100644 --- a/urbackupserver/server_settings.cpp +++ b/urbackupserver/server_settings.cpp @@ -431,7 +431,6 @@ void ServerSettings::readSettingsDefault(ISettingsReader* settings_default, settings->hash_threads = 1; settings->client_hash_threads = 1; settings->image_compress_threads = 0; - settings->allow_config_max_backups = false; } readStringClientSetting(q_get_client_setting, "update_freq_incr", std::string(), &settings->update_freq_incr, false); @@ -595,8 +594,6 @@ void ServerSettings::readSettingsDefault(ISettingsReader* settings_default, readIntClientSetting(q_get_client_setting, "client_hash_threads", &settings->client_hash_threads, false); readIntClientSetting(q_get_client_setting, "image_compress_threads", &settings->image_compress_threads, false); - - readBoolClientSetting(q_get_client_setting, "allow_config_max_backups", &settings->allow_config_max_backups, false); } void ServerSettings::readSettingsClient(ISettingsReader* settings_client, IQuery* q_get_client_setting) @@ -705,7 +702,7 @@ void ServerSettings::readSettingsClient(ISettingsReader* settings_client, IQuery readBoolClientSetting(q_get_client_setting, "allow_file_restore", &settings->allow_file_restore); readBoolClientSetting(q_get_client_setting, "allow_component_config", &settings->allow_component_config); readBoolClientSetting(q_get_client_setting, "allow_component_restore", &settings->allow_component_restore); - + readStringClientSetting(q_get_client_setting, "image_snapshot_groups", std::string(), &settings->image_snapshot_groups, false); readStringClientSetting(q_get_client_setting, "file_snapshot_groups", std::string(), &settings->file_snapshot_groups, false); @@ -722,8 +719,6 @@ void ServerSettings::readSettingsClient(ISettingsReader* settings_client, IQuery readIntClientSetting(q_get_client_setting, "hash_threads", &settings->hash_threads, false); readIntClientSetting(q_get_client_setting, "client_hash_threads", &settings->client_hash_threads, false); readIntClientSetting(q_get_client_setting, "image_compress_threads", &settings->image_compress_threads, false); - - readBoolClientSetting(q_get_client_setting, "allow_config_max_backups", &settings->allow_config_max_backups); } void ServerSettings::readStringClientSetting(IQuery * q_get_client_setting, int clientid, const std::string & name, const std::string & merge_sep, std::string * output, bool allow_client_value) @@ -1549,7 +1544,6 @@ std::map ServerSettings::getClientS SET_SETTING_INT(hash_threads); SET_SETTING_INT(client_hash_threads); SET_SETTING_INT(image_compress_threads); - SET_SETTING_BOOL(allow_config_max_backups); #undef SET_SETTING return ret; } diff --git a/urbackupserver/server_settings.h b/urbackupserver/server_settings.h index 5bd5eabf3..879cceea1 100644 --- a/urbackupserver/server_settings.h +++ b/urbackupserver/server_settings.h @@ -153,7 +153,6 @@ struct SSettings int hash_threads; int client_hash_threads; int image_compress_threads; - bool allow_config_max_backups; }; struct SLDAPSettings diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index 14ec9bc06..0be1de6c3 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -511,7 +511,7 @@ void updateClientSettings(int t_clientid, str_map &POST, IDatabase *db) std::vector sset_client_merge = getClientMergableSettingsList(); std::sort(sset_client_merge.begin(), sset_client_merge.end()); - std::vector sset_client_use = getClientConfigurableSettingsList(true); + std::vector sset_client_use = getClientConfigurableSettingsList(); std::sort(sset_client_use.begin(), sset_client_use.end()); std::vector sset_localized = getLocalizedSettingsList(); std::sort(sset_localized.begin(), sset_localized.end()); diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 3d11ffa46..e0a8e5bc6 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -4307,8 +4307,7 @@ g.settings_list=[ "download_threads", "hash_threads", "client_hash_threads", -"image_compress_threads", -"allow_config_max_backups" +"image_compress_threads" ]; g.general_settings_list=[ "backupfolder", diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index ab567b316..a6d154f7d 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -237,19 +237,12 @@
    - +
    -
    - -
    - -
    -
    -
    From 77056f5beffac7cf8d6f56a0ef8fe332ca932f5d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 29 Oct 2024 22:05:49 +0100 Subject: [PATCH 332/469] Ignore symlink loop in zip file download --- urbackupserver/serverinterface/create_zip.cpp | 8 ++++++++ urbackupserver/www/js/translation.js | 2 ++ 2 files changed, 10 insertions(+) diff --git a/urbackupserver/serverinterface/create_zip.cpp b/urbackupserver/serverinterface/create_zip.cpp index 79351c9e8..3f0a80d74 100644 --- a/urbackupserver/serverinterface/create_zip.cpp +++ b/urbackupserver/serverinterface/create_zip.cpp @@ -282,6 +282,14 @@ bool add_dir(mz_zip_archive& zip_archive, const std::string& archivefoldername, std::auto_ptr add_file(Server->openFile(os_file_prefix(filename), MODE_READ_SEQUENTIAL)); if (add_file.get() == NULL) { +#ifndef _WIN32 + if(errno==ELOOP) + { + Server->Log("Error opening file \"" + filename + "\" for ZIP file download. Symlink loop. Ignoring file. " + os_last_error_str(), LL_INFO); + continue; + } +#endif + Server->Log("Error opening file \"" + filename + "\" for ZIP file download. " + os_last_error_str(), LL_ERROR); return false; } diff --git a/urbackupserver/www/js/translation.js b/urbackupserver/www/js/translation.js index 845f520ca..97f390749 100644 --- a/urbackupserver/www/js/translation.js +++ b/urbackupserver/www/js/translation.js @@ -1426,6 +1426,8 @@ translations.en = { "tSend reports to": "Send reports to", "tSend": "Send", "tBackup time": "Backup time", +"tBackup ID": "Backup ID", +"tErrors": "Errors", "tStorage usage": "Storage usage", "tAll": "All", "tFilter": "Filter", From 5639234d25d9f621ac231d887c1c92b1fdd35548 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 2 Nov 2024 16:11:53 +0100 Subject: [PATCH 333/469] Merge pull request #109 from ori-sky/patch-1 EscapeHTML: Fix potentially vulnerable missing semicolon (cherry picked from commit cfa2748ece7d167ab193186baa32d02e1839573a) --- stringtools.cpp | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/stringtools.cpp b/stringtools.cpp index b56e3360f..ad49e0866 100644 --- a/stringtools.cpp +++ b/stringtools.cpp @@ -218,20 +218,20 @@ std::string getFile(std::string filename) return ret; } -std::string getStreamFile(const std::string& fn) -{ - std::fstream fin(fn.c_str(), std::ios::binary | std::ios::in); - if (!fin.is_open()) - return std::string(); - - std::string ret; - while (!fin.eof()) - { - char buf[512]; - fin.read(buf, sizeof(buf)); - ret.insert(ret.end(), buf, buf + fin.gcount()); - } - return ret; +std::string getStreamFile(const std::string& fn) +{ + std::fstream fin(fn.c_str(), std::ios::binary | std::ios::in); + if (!fin.is_open()) + return std::string(); + + std::string ret; + while (!fin.eof()) + { + char buf[512]; + fin.read(buf, sizeof(buf)); + ret.insert(ret.end(), buf, buf + fin.gcount()); + } + return ret; } void strupper_utf8(std::string *pStr) @@ -1256,7 +1256,7 @@ std::string EscapeHTML(const std::string & html) else if (html[i] == '&') ret += "&"; else if (html[i] == '\"') ret += """; else if (html[i] == '\'') ret += "'"; - else if (html[i] == '/') ret += "/"; + else if (html[i] == '/') ret += "/"; else ret += html[i]; } return ret; From 4cb1dc936a4f9347b00106de5bfe99738a9e47f0 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 2 Nov 2024 16:17:26 +0100 Subject: [PATCH 334/469] Fix UnescapeHTML as well (cherry picked from commit aedd60a2131ddcf04c417d40c0887010f5348309) --- stringtools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stringtools.cpp b/stringtools.cpp index ad49e0866..ec47466a8 100644 --- a/stringtools.cpp +++ b/stringtools.cpp @@ -1241,7 +1241,7 @@ std::string UnescapeHTML(const std::string &html) ret=greplace(">", ">", ret); ret=greplace(""", "\"", ret); ret=greplace("'", "'", ret); - ret = greplace("/", "/", ret); + ret = greplace("/", "/", ret); return ret; } From e0a90bdb11ac0c570052d6dbdcd5d2e5b93accdb Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 5 Jan 2025 15:36:22 +0100 Subject: [PATCH 335/469] Always go through all volumes and uninstall cbt if necessary (cherry picked from commit 99d14c13ffb3db5816d3ac670f7aca06da86dc01) --- urbackupclient/client.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 71dd63f86..15015afc3 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -8361,25 +8361,29 @@ void IndexThread::updateCbt() } } - if ((curr_settings.get() == NULL || volumes.empty()) - && backup_dirs.empty()) + const bool enable_all = (curr_settings.get() == NULL || volumes.empty()) + && backup_dirs.empty(); + + volumes = get_all_volumes_list(true, volumes_cache); + + std::vector ret; + Tokenize(volumes, ret, ";,"); + for (size_t i = 0; i ret; - Tokenize(volumes, ret, ";,"); - for (size_t i = 0; i Date: Tue, 14 Jan 2025 00:04:35 +0100 Subject: [PATCH 336/469] Call urbctctl setup function on startup (cherry picked from commit 5ab92eae915bd86d2f97297a3c815c6cd1a54efc) (cherry picked from commit 0f9529153677906b2cde7ed042695e0d5fa7fcc7) --- urbackupclient/client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 15015afc3..4d79affa8 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -709,6 +709,8 @@ void IndexThread::operator()(void) if (os_get_file_type("urbctctl.exe") != 0) { add_cbt_path(Server->getServerWorkingDir() + os_file_sep() + "urbackup"); + + system("urbctctl.exe setup"); } #endif From 1609696198095b71ac18a6d0cdc2186fa6cef3f4 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 16 May 2025 21:58:35 +0200 Subject: [PATCH 337/469] Output error code when unable to get socket error --- Server.cpp | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Server.cpp b/Server.cpp index 78a058eb1..be0e46604 100644 --- a/Server.cpp +++ b/Server.cpp @@ -1198,8 +1198,12 @@ IPipe* CServer::ConnectStream(const std::string& connect_str, const SLookupBlock rc=getsockopt(s, SOL_SOCKET, SO_ERROR, (char*)&err, &len); if(rc<0) { - closesocket(s); - Server->Log("Error getting socket status.", LL_ERROR); +#ifdef _WIN32 + Server->Log("Error getting socket status: " + convert(WSAGetLastError()), LL_ERROR); +#else + Server->Log("Error getting socket status: " + convert(errno), LL_ERROR); +#endif + closesocket(s); return NULL; } if(err) From 991eb2ec385d78b95fe2bd753c3767055a691122 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 17 May 2025 12:08:08 +0200 Subject: [PATCH 338/469] Buffer partial patch Make sure that we only write complete chunks into the patch file. Otherwise when a download is interrupted the last small chunk might not have a hash in the hash file causing the backup to fail with a fatal error --- urbackupcommon/fileclient/FileClientChunked.cpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/urbackupcommon/fileclient/FileClientChunked.cpp b/urbackupcommon/fileclient/FileClientChunked.cpp index 8c2a0979a..dc73e6afc 100644 --- a/urbackupcommon/fileclient/FileClientChunked.cpp +++ b/urbackupcommon/fileclient/FileClientChunked.cpp @@ -1599,7 +1599,21 @@ void FileClientChunked::writePatch(_i64 pos, unsigned int length, char *buf, boo } else { - writePatchInt(pos, length, buf); + if (!last && length % c_chunk_size != 0) + { + const unsigned int wchunks = length / c_chunk_size; + const unsigned int towrite = wchunks * c_chunk_size; + writePatchInt(pos, towrite, buf); + + const unsigned int wleft = length - towrite; + memcpy(&patch_buf[patch_buf_pos], buf + towrite, wleft); + patch_buf_start = pos; + patch_buf_pos += wleft; + } + else + { + writePatchInt(pos, length, buf); + } } } } From 1b2b37c010cdf70f9da0276addbb74a69bb9e9da Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 25 Jun 2025 23:36:55 +0200 Subject: [PATCH 339/469] Keep client status if there are still running backups --- urbackupserver/server_status.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/urbackupserver/server_status.cpp b/urbackupserver/server_status.cpp index 15b115618..17920bcfb 100644 --- a/urbackupserver/server_status.cpp +++ b/urbackupserver/server_status.cpp @@ -386,8 +386,19 @@ bool ServerStatus::removeStatus( const std::string &clientname ) if(it!=status.end()) { - status.erase(it); - return true; + if (it->second.running_jobs > 0) + { + Server->Log("Client " + clientname + " has running jobs when removing status. Just resetting", LL_WARNING); + const int running_jobs = it->second.running_jobs; + it->second = SStatus(); + it->second.running_jobs = running_jobs; + return false; + } + else + { + status.erase(it); + return true; + } } else { @@ -513,6 +524,7 @@ void ServerStatus::subRunningJob( const std::string &clientname ) IScopedLock lock(mutex); SStatus *s=&status[clientname]; + assert(s->running_jobs > 0); s->running_jobs-=1; } @@ -521,7 +533,7 @@ int ServerStatus::numRunningJobs( const std::string &clientname ) assert(!clientname.empty()); IScopedLock lock(mutex); - SStatus *s=&status[clientname]; + const SStatus *s=&status[clientname]; return s->running_jobs; } From 35d6b372a5a8af6cdd430ec5b31e153b0079283b Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 30 Jun 2025 22:38:42 +0200 Subject: [PATCH 340/469] Only use & as param str separator --- stringtools.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stringtools.cpp b/stringtools.cpp index ec47466a8..c52f116fa 100644 --- a/stringtools.cpp +++ b/stringtools.cpp @@ -774,7 +774,7 @@ void ParseParamStrHttp(const std::string &pStr, std::map Date: Wed, 16 Jul 2025 01:21:16 +0200 Subject: [PATCH 341/469] Fix patch_buf_start when buffering partial chunk (cherry picked from commit 3860366c0e14ade39e4c85ca17b3f031fa6e4fe0) --- urbackupcommon/fileclient/FileClientChunked.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupcommon/fileclient/FileClientChunked.cpp b/urbackupcommon/fileclient/FileClientChunked.cpp index dc73e6afc..b39d2df34 100644 --- a/urbackupcommon/fileclient/FileClientChunked.cpp +++ b/urbackupcommon/fileclient/FileClientChunked.cpp @@ -1607,7 +1607,7 @@ void FileClientChunked::writePatch(_i64 pos, unsigned int length, char *buf, boo const unsigned int wleft = length - towrite; memcpy(&patch_buf[patch_buf_pos], buf + towrite, wleft); - patch_buf_start = pos; + patch_buf_start = pos + towrite; patch_buf_pos += wleft; } else From 4a2dfe0ac1d5f72252ccd021d7fd7951f0d938c0 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 17 Jul 2025 22:16:58 +0200 Subject: [PATCH 342/469] Fix reading setting that enables local transfer encryption --- Interface/SettingsReader.h | 4 +++- SettingsReader.cpp | 29 +++++++++++++++++++++++++---- SettingsReader.h | 4 +++- 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/Interface/SettingsReader.h b/Interface/SettingsReader.h index 63b09ed7e..ef157fa4f 100644 --- a/Interface/SettingsReader.h +++ b/Interface/SettingsReader.h @@ -10,11 +10,13 @@ class ISettingsReader : public IObject public: virtual bool getValue(std::string key, std::string *value)=0; - virtual std::string getValue(std::string key, std::string def)=0; + virtual std::string getValue(const std::string& key, const char* def) = 0; + virtual std::string getValue(std::string key, const std::string& def)=0; virtual std::string getValue(std::string key)=0; virtual int getValue(std::string key, int def)=0; virtual float getValue(std::string key, float def)=0; virtual int64 getValue(std::string key, int64 def)=0; + virtual bool getValue(const std::string& key, const bool def) = 0; virtual std::vector getKeys() = 0; }; diff --git a/SettingsReader.cpp b/SettingsReader.cpp index e931f2f54..f6157c87d 100644 --- a/SettingsReader.cpp +++ b/SettingsReader.cpp @@ -22,11 +22,21 @@ #include #endif -std::string CSettingsReader::getValue(std::string key,std::string def) +std::string CSettingsReader::getValue(std::string key, const std::string& def) { std::string value; - bool b=getValue(key,&value); - if(b==false) + const bool b=getValue(key,&value); + if(!b) + return def; + else + return value; +} + +std::string CSettingsReader::getValue(const std::string& key, const char* def) +{ + std::string value; + const bool b = getValue(key, &value); + if (!b) return def; else return value; @@ -70,4 +80,15 @@ int64 CSettingsReader::getValue(std::string key, int64 def) return def; else return watoi64(value); -} \ No newline at end of file +} + +bool CSettingsReader::getValue(const std::string& key, const bool def) +{ + std::string value; + const bool b = getValue(key, &value); + if (!b) + return def; + + const std::string tkey = trim(value); + return tkey == "true" || tkey == "1" || tkey == "yes"; +} diff --git a/SettingsReader.h b/SettingsReader.h index a6dff1446..0de970058 100644 --- a/SettingsReader.h +++ b/SettingsReader.h @@ -9,11 +9,13 @@ class CSettingsReader : public ISettingsReader virtual bool getValue(std::string key, std::string *value)=0; - std::string getValue(std::string key,std::string def); + std::string getValue(const std::string& key, const char* def); + std::string getValue(std::string key, const std::string& def); std::string getValue(std::string key); int getValue(std::string key, int def); float getValue(std::string key, float def); int64 getValue(std::string key, int64 def); + bool getValue(const std::string& key, const bool def); }; #endif //CSETTINGSREADER_H From ae7a027e1311d1888a2b7dbc0c146f148aef25e9 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 17 Jul 2025 22:17:28 +0200 Subject: [PATCH 343/469] Fail with error if writing to file list file fails --- urbackupclient/client.cpp | 13 +++++++++++++ urbackupclient/client.h | 3 ++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 4d79affa8..9aac98ca5 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -1915,6 +1915,11 @@ IndexThread::IndexErrorInfo IndexThread::indexDirs(bool full_backup, bool simult SCDirs *scd=getSCDir(backup_dirs[k].tname, index_clientsubname, false); release_shadowcopy(scd); } + + if (outfile.bad() || outfile.fail()) + { + VSSLog("Error writing to file list at " + filelist_fn, LL_ERROR); + } outfile.close(); removeFile((filelist_fn)); @@ -1945,6 +1950,14 @@ IndexThread::IndexErrorInfo IndexThread::indexDirs(bool full_backup, bool simult addBackupScripts(outfile); } + if (outfile.bad() || outfile.fail()) + { + VSSLog("Error writing to file list at " + filelist_fn, LL_ERROR); + outfile.close(); + removeFile(filelist_fn); + return IndexErrorInfo_FilelistWriteError; + } + std::streampos pos=outfile.tellp(); outfile.seekg(0, std::ios::end); if(pos!=outfile.tellg()) diff --git a/urbackupclient/client.h b/urbackupclient/client.h index 985cd3d63..9e27a27b6 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -450,7 +450,8 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public { IndexErrorInfo_Ok = 0, IndexErrorInfo_Error = 1, - IndexErrorInfo_NoBackupPaths = 2 + IndexErrorInfo_NoBackupPaths = 2, + IndexErrorInfo_FilelistWriteError = 3 }; IndexErrorInfo indexDirs(bool full_backup, bool simultaneous_other); From 4e1308b322d72e4ce8e69cb24772a0ae060f2630 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 17 Jul 2025 22:17:53 +0200 Subject: [PATCH 344/469] Fix preventing sleep on Windows 11 --- urbackupclient/ClientServiceCMD.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index d9d52c172..d5e215b67 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1325,7 +1325,7 @@ void ClientConnector::CMD_PING_RUNNING2(const std::string &cmd) proc->done_bytes = watoi64(params["done_bytes"]); #ifdef _WIN32 - SetThreadExecutionState(ES_SYSTEM_REQUIRED); + preventSleep(); #endif } From b3c3714968ec7bffbee07a91a15aed07097ec41f Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 17 Jul 2025 22:18:54 +0200 Subject: [PATCH 345/469] Handle filesrv returning ERR as first message --- urbackupcommon/fileclient/FileClient.cpp | 6 ++++++ urbackupcommon/fileclient/FileClientChunked.cpp | 7 +++++++ urbackupcommon/fileclient/packet_ids.h | 2 ++ 3 files changed, 15 insertions(+) diff --git a/urbackupcommon/fileclient/FileClient.cpp b/urbackupcommon/fileclient/FileClient.cpp index cf37dcdf9..b924f602c 100644 --- a/urbackupcommon/fileclient/FileClient.cpp +++ b/urbackupcommon/fileclient/FileClient.cpp @@ -1133,6 +1133,12 @@ bool FileClient::Reconnect(void) { rc += dl_off; } + + if (firstpacket && dl_off + rc > 0 && dl_off + rc < 10 && buf[0] == ID_ERR) + { + Server->Log("Received ID_ERR from server rc=" + convert(rc)+". Reconnecting...", LL_WARNING); + rc = 0; + } } else { diff --git a/urbackupcommon/fileclient/FileClientChunked.cpp b/urbackupcommon/fileclient/FileClientChunked.cpp index b39d2df34..774a7e46f 100644 --- a/urbackupcommon/fileclient/FileClientChunked.cpp +++ b/urbackupcommon/fileclient/FileClientChunked.cpp @@ -608,6 +608,13 @@ _u32 FileClientChunked::GetFile(std::string remotefn, _i64& filesize_out, int64 { buf = stack_buf; rc = getPipe()->Read(buf, BUFFERSIZE, 0); + + if (initial_read && rc > 0 && rc < 10 && buf[0] == ID_ERR) + { + Server->Log("Received ID_ERR from server fc_chunked rc=" + convert(rc) + ". Reconnecting...", LL_WARNING); + rc = 0; + flush_rc = ERR_ERROR; + } } initial_read = false; diff --git a/urbackupcommon/fileclient/packet_ids.h b/urbackupcommon/fileclient/packet_ids.h index e56fdd78f..b47c902e5 100644 --- a/urbackupcommon/fileclient/packet_ids.h +++ b/urbackupcommon/fileclient/packet_ids.h @@ -35,6 +35,8 @@ const uchar ID_FLUSH_SOCKET=13; const uchar ID_SCRIPT_FINISH = 14; const uchar ID_FREE_SERVER_FILE=18; const uchar ID_STOP_PHASH = 19; +// Server returns ERR to FILESRV request +const uchar ID_ERR = 3; //errors const unsigned int ERR_SEEKING_FAILED = 0; From 93fc70e1d91f7738e7968c506067d352846a5766 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 27 Aug 2025 21:07:57 +0200 Subject: [PATCH 346/469] Disable following symlinks by default when restoring via command line --- clientctl/main.cpp | 3168 ++++++++++++++++++++++---------------------- 1 file changed, 1584 insertions(+), 1584 deletions(-) diff --git a/clientctl/main.cpp b/clientctl/main.cpp index d171498ab..79f998885 100644 --- a/clientctl/main.cpp +++ b/clientctl/main.cpp @@ -1,53 +1,53 @@ -/************************************************************************* -* 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 . -**************************************************************************/ - -#include -#include -#include -#include -#include -#include "Connector.h" -#include "../stringtools.h" -#include "../tclap/CmdLine.h" -#include "json/json.h" -#include "../urbackupcommon/os_functions.h" - -#ifndef _WIN32 -#include -#include -#include "../config.h" -#include -#include -#define PWFILE VARDIR "/urbackup/pw.txt" -#define PWFILE_CHANGE VARDIR "/urbackup/pw_change.txt" -#else -#include +/************************************************************************* +* 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 . +**************************************************************************/ + +#include +#include +#include +#include +#include +#include "Connector.h" +#include "../stringtools.h" +#include "../tclap/CmdLine.h" +#include "json/json.h" +#include "../urbackupcommon/os_functions.h" + +#ifndef _WIN32 +#include +#include +#include "../config.h" +#include +#include +#define PWFILE VARDIR "/urbackup/pw.txt" +#define PWFILE_CHANGE VARDIR "/urbackup/pw_change.txt" +#else +#include #define PACKAGE_VERSION "$version_full_numeric$" -#define VARDIR "" -#define PWFILE "pw.txt" -#define PWFILE_CHANGE "pw_change.txt" -#endif - +#define VARDIR "" +#define PWFILE "pw.txt" +#define PWFILE_CHANGE "pw_change.txt" +#endif + #ifdef __MACH__ #include #include -#endif - +#endif + void wait(unsigned int ms) { #ifdef _WIN32 @@ -55,8 +55,8 @@ void wait(unsigned int ms) #else usleep(ms * 1000); #endif -} - +} + int64 getTimeMS() { #ifdef _WIN32 @@ -96,76 +96,76 @@ int64 getTimeMS() return static_cast(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] []" << std::endl; - std::cout << std::endl; - std::cout << "Get specific command help with " << cmd << " --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 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] []" << std::endl; + std::cout << std::endl; + std::cout << "Get specific command help with " << cmd << " --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 +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 -} - + 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; @@ -237,1503 +237,1503 @@ void draw_progress(int pc_done, double speed_bpms, int64 done_bytes, int64 total std::cout << toc; std::cout.flush(); -} - -typedef int(*action_fun)(std::vector 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 pw_file_arg; - TCLAP::ValueArg client_arg; -}; - -std::vector get_current_processes() -{ - SStatusDetails sd = Connector::getStatusDetails(); - - std::vector 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& 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 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 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 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 args) -{ - TCLAP::CmdLine cmd("Get current backup status", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, false); - - TCLAP::ValueArg 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 args) -{ - TCLAP::CmdLine cmd("Browse backups and files/folders in backups", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, false); - - TCLAP::ValueArg backupid_arg("b", "backupid", - "Backupid of backup in which to browse files/folders or \"last\" for last complete backup", - false, "", "id", cmd); - - TCLAP::ValueArg path_arg("d", "path", - "Path of folder/file to which to browse", - false, "", "path", cmd); - - TCLAP::ValueArg 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 = NULL; - 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 args) -{ - TCLAP::CmdLine cmd("Restore files/folders from backup", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, false); - - TCLAP::ValueArg backupid_arg("b", "backupid", - "Backupid of backup from which to restore files/folders or \"last\" for last complete backup", - true, "", "id", cmd); - - TCLAP::ValueArg path_arg("d", "path", - "Path of folder/file to restore", - false, "", "path", cmd); - - TCLAP::MultiArg map_from_arg("m", "map-from", - "Map from local output path of folders/files to a different local path", - false, "path", cmd); - - TCLAP::MultiArg 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 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 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 args) -{ - TCLAP::CmdLine cmd("Set backup settings", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, true); - - TCLAP::MultiArg key_arg("k", "key", - "Key of the setting to set", - false, "setting key", cmd); - - TCLAP::MultiArg value_arg("v", "value", - "New value to set the setting to", - false, "setting value", cmd); - - TCLAP::SwitchArg no_merge_arg("n", "no-merge", - "Don't merge server and client settings if possible", cmd); - - TCLAP::ValueArg server_url_arg("", "server-url", - "URL of server to connect to", - false, "", "url", cmd); - - TCLAP::ValueArg name_arg("", "name", - "Client name", - false, "", "string", cmd); - - TCLAP::ValueArg authkey_arg("", "authkey", - "Server authentication key for client", - false, "", "string", cmd); - - TCLAP::ValueArg proxy_arg("", "proxy", - "HTTP CONNECT proxy to use to connect to server", - false, "", "url", 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; - } - - str_map arg_settings; - - if (server_url_arg.isSet()) - { - std::vector server_urls; - Tokenize(server_url_arg.getValue(), server_urls, ";"); - std::string internet_server; - std::string internet_server_port; - for (size_t i = 0; i < server_urls.size(); ++i) - { - std::string server_url = server_urls[i]; - std::string server_port = "55415"; - - if (server_url.find("urbackup://") != 0 && - server_url.find("wss://") != 0 && - server_url.find("ws://") != 0) - { - std::cerr << "Server URL must start with urbackup://, wss:// or ws://" << std::endl; - return 4; - } - - if (server_url.find("urbackup://") == 0) +} + +typedef int(*action_fun)(std::vector 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())) { - std::string hostname = server_url.substr(11); - if (hostname.find(":") != std::string::npos) + 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 pw_file_arg; + TCLAP::ValueArg client_arg; +}; + +std::vector get_current_processes() +{ + SStatusDetails sd = Connector::getStatusDetails(); + + std::vector 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& 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()) { - server_port = getafter(":", server_url); + if (i > 0) + { + std::cout << "\r" << message << "done" << std::endl; + } + return sd.running_processes[j].process_id; } - server_url = hostname; - } - - if (!internet_server.empty()) - internet_server += ";"; - if (!internet_server_port.empty()) - internet_server_port += ";"; - - internet_server += server_url; - internet_server_port += server_port; - } - - arg_settings["internet_server_port"] = internet_server_port; - arg_settings["internet_server"] = internet_server; - arg_settings["internet_mode_enabled"] = "true"; - } - - if (authkey_arg.isSet()) - { - arg_settings["internet_authkey"] = authkey_arg.getValue(); - arg_settings["internet_mode_enabled"] = "true"; - } - - if (name_arg.isSet()) - { - arg_settings["computername"] = name_arg.getValue(); - } - - if (proxy_arg.isSet()) - { - arg_settings["internet_server_proxy"] = proxy_arg.getValue(); - arg_settings["internet_mode_enabled"] = "true"; - } - - std::string s_settings; - for (size_t i = 0; i < key_arg.getValue().size(); ++i) - { - std::string key = key_arg.getValue()[i]; - if(arg_settings.find(key)==arg_settings.end()) - s_settings += key + "=" + value_arg.getValue()[i] + "\n"; - } - - for (str_map::const_iterator it = arg_settings.begin(); - it != arg_settings.end(); ++it) - { - s_settings += it->first + "=" + it->second + "\n"; - } - - s_settings += "set_client_settings=1\n"; - - if (!no_merge_arg.getValue()) - { - s_settings += "merge_client_settings=0\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 args) -{ - TCLAP::CmdLine cmd("Reset keeping files during incremental backups", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, true); - - TCLAP::ValueArg virtual_client_arg("v", "virtual-client", - "Virtual client name", - false, "", "client name", cmd); - - TCLAP::ValueArg backup_folder_arg("b", "backup-folder", - "Backup folder name", - false, "", "folder name", cmd); - - TCLAP::ValueArg 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) + } + } + + 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) { - char illegalchars[] = { '*', ':', '/' , '\\' }; - std::string ret; - for (size_t i = 0; i0) + { + --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 j = 0; j0) + { + 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) { - ret += in[i]; + 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); } - return ret; -} - -bool findPathName(const std::vector& dirs, const std::string &pn) +} + +int action_start(std::vector args) { - for (size_t i = 0; i 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()) { - if (dirs[i].name == pn) + return 3; + } + + std::vector 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()) { - return true; + 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); } } - return false; -} - -std::string getDefaultDirname(const std::vector& dirs, const std::string &path) + 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 args) { - std::string dirname = removeChars(ExtractFileName(path)); + TCLAP::CmdLine cmd("Get current backup status", ' ', cmdline_version); - if (dirname.empty()) - dirname = "rootfs"; + PwClientCmd pw_client_cmd(cmd, false); - if (findPathName(dirs, dirname)) + TCLAP::ValueArg follow_arg("f", "follow", + "Follow proccess status", + false, 0, "process id", cmd); + + cmd.parse(args); + + if (!pw_client_cmd.set()) { - for (int k = 0; k<100; ++k) + return 3; + } + + if (follow_arg.getValue() == 0) + { + std::string status = Connector::getStatusDetailsRaw(); + if (!status.empty()) { - if (!findPathName(dirs, dirname + "_" + convert(k))) + 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 args) +{ + TCLAP::CmdLine cmd("Browse backups and files/folders in backups", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, false); + + TCLAP::ValueArg backupid_arg("b", "backupid", + "Backupid of backup in which to browse files/folders or \"last\" for last complete backup", + false, "", "id", cmd); + + TCLAP::ValueArg path_arg("d", "path", + "Path of folder/file to which to browse", + false, "", "path", cmd); + + TCLAP::ValueArg 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) { - dirname = dirname + "_" + convert(k); - break; + 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 = NULL; + 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; } } } +} - return dirname; -} - -int action_add_backupdir(std::vector args) -{ - TCLAP::CmdLine cmd("Add new directory to backup set", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, true); - - TCLAP::ValueArg virtual_client_arg("v", "virtual-client", - "Virtual client name", - false, "", "client name", cmd); - - TCLAP::ValueArg name_arg("n", "name", - "Backup directory name", - false, "", "name", cmd); - - TCLAP::ValueArg path_arg("d", "path", - "Backup path", - true, "", "path", cmd); - - TCLAP::ValueArg 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 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 (!os_path_absolute(new_dir.path)) - { - new_dir.path = os_get_final_path(new_dir.path); - } - - if (new_dir.path.size()>1 && new_dir.path[new_dir.path.size() - 1] == os_file_sep()[0]) - { - new_dir.path.erase(new_dir.path.size() - 1, 1); - } - - 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 >& rows) -{ - if (rows.empty()) - { - return; - } - - const size_t val_gap = 1; - - std::vector 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 args) -{ - TCLAP::CmdLine cmd("List directories that are being backed up", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, false); - - TCLAP::ValueArg 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 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 > tab; - - std::vector 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 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 args) -{ - TCLAP::CmdLine cmd("Remove directory from backup set", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, true); - - TCLAP::ValueArg name_arg("n", "name", - "Backup directory name", - false, "", "name"); - - TCLAP::ValueArg 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 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 = false; - - 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; - ++i; - } - 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; - ++i; - } - 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 args) -{ - TCLAP::CmdLine cmd("Wait for backend to become available", ' ', cmdline_version); - - PwClientCmd pw_client_cmd(cmd, false); - - TCLAP::ValueArg 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) +int wait_for_restore(std::string restore_info) +{ + Json::Value root; + Json::Reader reader; + + if (!reader.parse(restore_info, root, false)) { - WCHAR path[MAX_PATH]; - if (GetModuleFileNameW(hModule, path, MAX_PATH) != 0) - { - SetCurrentDirectoryW(path); - } - } -#endif - - std::vector actions; - std::vector 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 args; - args.push_back(argv[0]); - for(int i=1;i 1 + && path[path.size() - 1] == os_file_sep()[0]) + { + return path.substr(0, path.size() - 1); + } + + return path; +} + +int action_start_restore(std::vector args) +{ + TCLAP::CmdLine cmd("Restore files/folders from backup", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, false); + + TCLAP::ValueArg backupid_arg("b", "backupid", + "Backupid of backup from which to restore files/folders or \"last\" for last complete backup", + true, "", "id", cmd); + + TCLAP::ValueArg path_arg("d", "path", + "Path of folder/file to restore", + false, "", "path", cmd); + + TCLAP::MultiArg map_from_arg("m", "map-from", + "Map from local output path of folders/files to a different local path", + false, "path", cmd); + + TCLAP::MultiArg 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 follow_symlinks("f", "follow-symlinks", + "Follow symlinks outside of restored path during restore", cmd); + + TCLAP::ValueArg 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 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(), + 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 args) +{ + TCLAP::CmdLine cmd("Set backup settings", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, true); + + TCLAP::MultiArg key_arg("k", "key", + "Key of the setting to set", + false, "setting key", cmd); + + TCLAP::MultiArg value_arg("v", "value", + "New value to set the setting to", + false, "setting value", cmd); + + TCLAP::SwitchArg no_merge_arg("n", "no-merge", + "Don't merge server and client settings if possible", cmd); + + TCLAP::ValueArg server_url_arg("", "server-url", + "URL of server to connect to", + false, "", "url", cmd); + + TCLAP::ValueArg name_arg("", "name", + "Client name", + false, "", "string", cmd); + + TCLAP::ValueArg authkey_arg("", "authkey", + "Server authentication key for client", + false, "", "string", cmd); + + TCLAP::ValueArg proxy_arg("", "proxy", + "HTTP CONNECT proxy to use to connect to server", + false, "", "url", 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; + } + + str_map arg_settings; + + if (server_url_arg.isSet()) + { + std::vector server_urls; + Tokenize(server_url_arg.getValue(), server_urls, ";"); + std::string internet_server; + std::string internet_server_port; + for (size_t i = 0; i < server_urls.size(); ++i) + { + std::string server_url = server_urls[i]; + std::string server_port = "55415"; + + if (server_url.find("urbackup://") != 0 && + server_url.find("wss://") != 0 && + server_url.find("ws://") != 0) + { + std::cerr << "Server URL must start with urbackup://, wss:// or ws://" << std::endl; + return 4; + } + + if (server_url.find("urbackup://") == 0) + { + std::string hostname = server_url.substr(11); + if (hostname.find(":") != std::string::npos) + { + server_port = getafter(":", server_url); + } + server_url = hostname; + } + + if (!internet_server.empty()) + internet_server += ";"; + if (!internet_server_port.empty()) + internet_server_port += ";"; + + internet_server += server_url; + internet_server_port += server_port; + } + + arg_settings["internet_server_port"] = internet_server_port; + arg_settings["internet_server"] = internet_server; + arg_settings["internet_mode_enabled"] = "true"; + } + + if (authkey_arg.isSet()) + { + arg_settings["internet_authkey"] = authkey_arg.getValue(); + arg_settings["internet_mode_enabled"] = "true"; + } + + if (name_arg.isSet()) + { + arg_settings["computername"] = name_arg.getValue(); + } + + if (proxy_arg.isSet()) + { + arg_settings["internet_server_proxy"] = proxy_arg.getValue(); + arg_settings["internet_mode_enabled"] = "true"; + } + + std::string s_settings; + for (size_t i = 0; i < key_arg.getValue().size(); ++i) + { + std::string key = key_arg.getValue()[i]; + if(arg_settings.find(key)==arg_settings.end()) + s_settings += key + "=" + value_arg.getValue()[i] + "\n"; + } + + for (str_map::const_iterator it = arg_settings.begin(); + it != arg_settings.end(); ++it) + { + s_settings += it->first + "=" + it->second + "\n"; + } + + s_settings += "set_client_settings=1\n"; + + if (!no_merge_arg.getValue()) + { + s_settings += "merge_client_settings=0\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 args) +{ + TCLAP::CmdLine cmd("Reset keeping files during incremental backups", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, true); + + TCLAP::ValueArg virtual_client_arg("v", "virtual-client", + "Virtual client name", + false, "", "client name", cmd); + + TCLAP::ValueArg backup_folder_arg("b", "backup-folder", + "Backup folder name", + false, "", "folder name", cmd); + + TCLAP::ValueArg 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& dirs, const std::string &pn) +{ + for (size_t i = 0; i& 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 args) +{ + TCLAP::CmdLine cmd("Add new directory to backup set", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, true); + + TCLAP::ValueArg virtual_client_arg("v", "virtual-client", + "Virtual client name", + false, "", "client name", cmd); + + TCLAP::ValueArg name_arg("n", "name", + "Backup directory name", + false, "", "name", cmd); + + TCLAP::ValueArg path_arg("d", "path", + "Backup path", + true, "", "path", cmd); + + TCLAP::ValueArg 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 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 (!os_path_absolute(new_dir.path)) + { + new_dir.path = os_get_final_path(new_dir.path); + } + + if (new_dir.path.size()>1 && new_dir.path[new_dir.path.size() - 1] == os_file_sep()[0]) + { + new_dir.path.erase(new_dir.path.size() - 1, 1); + } + + 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 >& rows) +{ + if (rows.empty()) + { + return; + } + + const size_t val_gap = 1; + + std::vector 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 args) +{ + TCLAP::CmdLine cmd("List directories that are being backed up", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, false); + + TCLAP::ValueArg 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 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 > tab; + + std::vector 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 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 args) +{ + TCLAP::CmdLine cmd("Remove directory from backup set", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, true); + + TCLAP::ValueArg name_arg("n", "name", + "Backup directory name", + false, "", "name"); + + TCLAP::ValueArg 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 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 = false; + + 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; + ++i; + } + 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; + ++i; + } + 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 args) +{ + TCLAP::CmdLine cmd("Wait for backend to become available", ' ', cmdline_version); + + PwClientCmd pw_client_cmd(cmd, false); + + TCLAP::ValueArg 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 actions; + std::vector 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 args; + args.push_back(argv[0]); + for(int i=1;i Date: Wed, 27 Aug 2025 21:10:56 +0200 Subject: [PATCH 347/469] Disable following symlinks when starting restore via web ui --- urbackupserver/serverinterface/backups.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/serverinterface/backups.cpp b/urbackupserver/serverinterface/backups.cpp index 44fcdbf41..b23ac1c0f 100644 --- a/urbackupserver/serverinterface/backups.cpp +++ b/urbackupserver/serverinterface/backups.cpp @@ -1813,7 +1813,7 @@ ACTION_IMPL(backups) if(!create_clientdl_thread(clientname, t_clientid, t_clientid, path_info.full_path, path_info.full_metadata_path, CURRP["filter"], path_info.rel_path.empty(), path_info.rel_path, restore_id, status_id, log_id, std::string(), - std::vector< std::pair >(), true, true, greplace(os_file_sep(), "/", path_info.rel_path), true, + std::vector< std::pair >(), true, true, greplace(os_file_sep(), "/", path_info.rel_path), false, restore_flags, ticket, tokens, path_info.backup_tokens, false)) { ret.set("err", "internal_error"); From 0c7da6fb718bc8296a9383cb1429112939463aa5 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 13 Sep 2025 16:52:31 +0200 Subject: [PATCH 348/469] Fix dead-lock when falling back to old indexing method --- urbackupclient/ChangeJournalWatcher.cpp | 10 +++++----- urbackupclient/ChangeJournalWatcher.h | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/urbackupclient/ChangeJournalWatcher.cpp b/urbackupclient/ChangeJournalWatcher.cpp index ee417ce9a..49e28c6dd 100644 --- a/urbackupclient/ChangeJournalWatcher.cpp +++ b/urbackupclient/ChangeJournalWatcher.cpp @@ -573,7 +573,7 @@ void ChangeJournalWatcher::indexRootDirs(_i64 rid, const std::string &root, uint { if(Server->getTimeMS()-last_index_update>10000) { - update(); + update(std::string(), false); last_index_update=Server->getTimeMS(); } } @@ -789,7 +789,7 @@ std::string ChangeJournalWatcher::getFilename(const SChangeJournal &cj, uint128 const int BUF_LEN=4096; -void ChangeJournalWatcher::update(std::string vol_str) +void ChangeJournalWatcher::update(std::string vol_str, const bool allow_trans_start) { char buffer[BUF_LEN]; @@ -868,7 +868,7 @@ void ChangeJournalWatcher::update(std::string vol_str) usn_record.Filename!="backup_client.db-wal" && usn_record.Filename != "backup_client.db-shm") { - if(!started_transaction) + if(!started_transaction && allow_trans_start) { started_transaction=true; db->BeginWriteTransaction(); @@ -882,7 +882,7 @@ void ChangeJournalWatcher::update(std::string vol_str) usn_record.Filename!="backup_client.db-wal" && usn_record.Filename != "backup_client.db-shm" ) { - if(!started_transaction) + if(!started_transaction && allow_trans_start) { started_transaction=true; db->BeginWriteTransaction(); @@ -979,7 +979,7 @@ void ChangeJournalWatcher::update(std::string vol_str) if((startUsn!=it->second.last_record && started_transaction) || !vol_str.empty()) { - if(!started_transaction) + if(!started_transaction && allow_trans_start) { started_transaction=true; db->BeginWriteTransaction(); diff --git a/urbackupclient/ChangeJournalWatcher.h b/urbackupclient/ChangeJournalWatcher.h index 83a959e8a..4a49ade43 100644 --- a/urbackupclient/ChangeJournalWatcher.h +++ b/urbackupclient/ChangeJournalWatcher.h @@ -109,7 +109,7 @@ class ChangeJournalWatcher void watchDir(const std::string &dir); - void update(std::string vol_str=""); + void update(std::string vol_str="", const bool allow_trans_start=true); void update_longliving(void); void set_freeze_open_write_files(bool b); From 7576fe2ce5e84649b583b6d9fd35539333115f38 Mon Sep 17 00:00:00 2001 From: Martin Date: Mon, 6 Oct 2025 23:11:44 +0200 Subject: [PATCH 349/469] Trim image_snapshot_groups for comparison with all (cherry picked from commit a7904ac56b063b0a3ed2686ae940bfe150e3673e) --- urbackupserver/ClientMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index 894d221ae..dec67f84b 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -3703,7 +3703,7 @@ bool ClientMain::isImageGroupQueued(const std::string & letter, bool full) std::vector groups; Tokenize(image_snapshot_groups, groups, "|"); - image_snapshot_groups = strlower(image_snapshot_groups); + image_snapshot_groups = strlower(trim(image_snapshot_groups)); std::string vol = normalizeVolumeUpper(letter); From cbf246bceb91955b57fe94a9514f5fa3b8483984 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 23 Oct 2025 22:16:46 +0200 Subject: [PATCH 350/469] Don't erroneously delete incr file backups as full backups We don't want to delete the only full file backups if incremental file backups are enabled and we don't have any, because then we would not have a full backup to base the first incremental on. This check erroneously caused incremental backups to be deleted. --- urbackupserver/server_cleanup.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/server_cleanup.cpp b/urbackupserver/server_cleanup.cpp index 2f5e2c87b..9bbd9e45a 100644 --- a/urbackupserver/server_cleanup.cpp +++ b/urbackupserver/server_cleanup.cpp @@ -1304,6 +1304,7 @@ bool ServerCleanupThread::cleanup_one_filebackup_client(int clientid, int64 mins } int backupid; + int backupid_incr; int full_file_num=(int)getFilesFullNum(clientid, backupid); ServerLogger::Log(logid, "Client with id="+convert(clientid)+" has "+convert(full_file_num)+" full file backups "+ full_val_info +"="+convert(max_file_full), LL_DEBUG); while(full_file_num>max_file_full @@ -1311,7 +1312,7 @@ bool ServerCleanupThread::cleanup_one_filebackup_client(int clientid, int64 mins && !(full_file_num==1 && settings.getSettings()->max_file_incr>0 && settings.getUpdateFreqFileIncr()>=0 - && getFilesIncrNum(clientid, backupid)==0) ) + && getFilesIncrNum(clientid, backupid_incr)==0) ) { ServerCleanupDao::SFileBackupInfo res_info=cleanupdao->getFileBackupInfo(backupid); ServerCleanupDao::CondString clientname=cleanupdao->getClientName(clientid); From c799063453a781827796a9dae8b03bce10d1ac36 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 2 Nov 2025 17:36:59 +0100 Subject: [PATCH 351/469] Update translations --- urbackupserver/www/js/translation.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/urbackupserver/www/js/translation.js b/urbackupserver/www/js/translation.js index 97f390749..a2c430b27 100644 --- a/urbackupserver/www/js/translation.js +++ b/urbackupserver/www/js/translation.js @@ -4868,6 +4868,7 @@ translations.hr = { "hours": "sati", "days": "dani", "nav_item_6": "Status", +"tErrors": "GreÅ¡ke", "tStatus": "Status", "tCancel": "PoniÅ¡tavanje", "tName:": "Naziv:", @@ -4973,6 +4974,7 @@ translations.hu_HU = { "tSend reports to": "Jelentések küldése ide:", "tSend": "Küldés", "tBackup time": "Mentés ideje", +"tErrors": "Hibák", "tStorage usage": "Tárhely használat", "tAll": "Összes", "tFilter": "SzűrÅ‘", @@ -6087,6 +6089,7 @@ translations.lt = { "hours": "val.", "days": "dienų", "nav_item_6": "Statusas", +"tErrors": "Klaidos", "tStatus": "Statusas", "interval_hours": "val.", "interval_days": "dienų" @@ -12699,6 +12702,7 @@ translations.vi = { "hours": "giá»", "days": "ngày", "nav_item_6": "Trạng thái", +"tErrors": "Lá»—i", "tStatus": "Trạng thái", "tCancel": "Ngưng", "tName:": "Tên:", From 1b4169db1131139fac461000ff4d89fd9081d302 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 2 Nov 2025 17:39:38 +0100 Subject: [PATCH 352/469] Use newer visual studio runtime --- .../urbackup_server.nsi | 42 ++++++------------- .../urbackup_server.wxs | 2 +- 2 files changed, 14 insertions(+), 30 deletions(-) diff --git a/urbackupserver_installer_win/urbackup_server.nsi b/urbackupserver_installer_win/urbackup_server.nsi index f489a8635..5f88b0871 100644 --- a/urbackupserver_installer_win/urbackup_server.nsi +++ b/urbackupserver_installer_win/urbackup_server.nsi @@ -63,63 +63,47 @@ Section "install" SetOutPath "$TEMP" ${If} ${RunningX64} - ; Push $R0 - ; ClearErrors - ; ReadRegDword $R0 HKLM "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\{DA5E371C-6333-3D8A-93A4-6FD5B20BCC6E}" "Version" - ; IfErrors 0 VSRedistInstalled64 - ; inetc::get "http://www.urserver.de/vc10/vcredist_x64.exe" $TEMP\vcredist_x64.exe - ; Pop $0 - ; ExecWait '"$TEMP\vcredist_x64.exe" /q' - ; Delete '$TEMP\vcredist_x64.exe' -; VSRedistInstalled64: - File "..\deps\redist\vc_redist_2019.x64.exe" - ExecWait '"$TEMP\vc_redist_2019.x64.exe" /q /norestart' $0 + File "..\deps\redist\vc_redist_2022.x64.exe" + ExecWait '"$TEMP\vc_redist_2022.x64.exe" /q /norestart' $0 ${If} $0 != '0' ${If} $0 != '3010' ${If} $0 != '1638' ${If} $0 != '8192' ${If} $0 != '1641' ${If} $0 != '1046' - ExecWait '"$TEMP\vc_redist_2019.x64.exe" /passive /norestart' $0 + ExecWait '"$TEMP\vc_redist_2022.x64.exe" /passive /norestart' $0 ${If} $0 != '0' ${If} $0 != '3010' - MessageBox MB_OK "Unable to install Visual Studio 2019 runtime. UrBackup needs that runtime." + ${If} $0 != '1638' + MessageBox MB_OK "Unable to install Visual Studio 2022 runtime. UrBackup needs that runtime." Quit ${EndIf} ${EndIf} + ${EndIf} ${EndIf} ${EndIf} ${EndIf} ${EndIf} ${EndIf} - ${EndIf} - + ${EndIf} ${Else} - ; ReadRegStr $0 HKLM "SOFTWARE\Microsoft\VisualStudio\10.0\VC\Runtimes\x86" 'Installed' - ; ${If} $0 != '1' - ; ReadRegStr $0 HKLM "SOFTWARE\Microsoft\VisualStudio\10.0\VC\VCRedist\x86" 'Installed' - ; ${If} $0 != '1' - ; inetc::get "http://www.urserver.de/vc10/vcredist_x86.exe" $TEMP\vcredist_x86.exe - ; Pop $0 - ; ExecWait '"$TEMP\vcredist_x86.exe" /q' - ; Delete '$TEMP\vcredist_x86.exe' - ; ${EndIf} - ; ${EndIf} - File "..\deps\redist\vc_redist_2019.x86.exe" - ExecWait '"$TEMP\vc_redist_2019.x86.exe" /q /norestart' $0 + File "..\deps\redist\vc_redist_2022.x86.exe" + ExecWait '"$TEMP\vc_redist_2022.x86.exe" /q /norestart' $0 ${If} $0 != '0' ${If} $0 != '3010' ${If} $0 != '1638' ${If} $0 != '8192' ${If} $0 != '1641' ${If} $0 != '1046' - ExecWait '"$TEMP\vc_redist_2019.x86.exe" /passive /norestart' $0 + ExecWait '"$TEMP\vc_redist_2022.x86.exe" /passive /norestart' $0 ${If} $0 != '0' ${If} $0 != '3010' - MessageBox MB_OK "Unable to install Visual Studio 2019 runtime. UrBackup needs that runtime." + ${If} $0 != '1638' + MessageBox MB_OK "Unable to install Visual Studio 2022 runtime. UrBackup needs that runtime." Quit ${EndIf} ${EndIf} + ${EndIf} ${EndIf} ${EndIf} ${EndIf} diff --git a/urbackupserver_installer_win/urbackup_server.wxs b/urbackupserver_installer_win/urbackup_server.wxs index 0c163e93a..8cd19615e 100644 --- a/urbackupserver_installer_win/urbackup_server.wxs +++ b/urbackupserver_installer_win/urbackup_server.wxs @@ -23,7 +23,7 @@ - + From edf80c37b2f12ffe85cbab8c4e1bb782e7f1fdf1 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 2 Nov 2025 17:43:03 +0100 Subject: [PATCH 353/469] Increment version --- configure.ac_server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac_server b/configure.ac_server index 8d3cb8a13..9dcbe031c 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.33.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.34.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) From a12a6d59563d1016f8d86e80619679644ff5b8cd Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 2 Nov 2025 17:43:51 +0100 Subject: [PATCH 354/469] Increment version --- urbackupserver/www/js/urbackup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index e0a8e5bc6..c18b8f3e0 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5,7 +5,7 @@ g.startup=true; g.no_tab_mouse_click=false; g.tabberidx=-1; g.progress_stop_id=-1; -g.current_version=2005003300; +g.current_version=2005003400; g.status_show_all=false; g.ldap_login=false; g.datatable_default_config={}; From bb27135adafde5bd8167cf87d217e086eaca378a Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 2 Nov 2025 22:55:50 +0100 Subject: [PATCH 355/469] Add missing include for Linux --- urbackupserver/serverinterface/create_zip.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/urbackupserver/serverinterface/create_zip.cpp b/urbackupserver/serverinterface/create_zip.cpp index 3f0a80d74..cc21fd0f1 100644 --- a/urbackupserver/serverinterface/create_zip.cpp +++ b/urbackupserver/serverinterface/create_zip.cpp @@ -31,6 +31,7 @@ #define _fdopen fdopen #define _close close #include +#include #else #include #include From 355f196cd61315cea62bcaba06eeb7a3c91adf93 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 8 Nov 2025 16:53:40 +0100 Subject: [PATCH 356/469] Fix sending GPT footer with large volumes Volumes with size greater than 2 TiB have GPT header and footer stored for convenience. We need to substract the GPT footer when restoring otherwise the volume might not fit on the partition. Get the size of the volume without GPT footer from the GPT partition data and restore the correctly sized volume. --- urbackupserver/server_channel.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/urbackupserver/server_channel.cpp b/urbackupserver/server_channel.cpp index 4d8a779c2..6b6cc0467 100644 --- a/urbackupserver/server_channel.cpp +++ b/urbackupserver/server_channel.cpp @@ -1215,10 +1215,25 @@ void ServerChannelThread::DOWNLOAD_IMAGE(str_map& params) && file_extension!="raw") skip=512*512; - if (is_disk_mbr(res[0]["path"] + ".mbr")) + const bool whole_disk = is_disk_mbr(res[0]["path"] + ".mbr"); + if (whole_disk) skip = 0; _i64 imgsize = (_i64)vhdfile->getSize() - skip; + + if (!whole_disk && imgsize + skip > 2LL * 1024 * 1024 * 1024 * 1024) + { + // Get size of volume without GPT footer for images > 2TiB + bool gpt_style = false; + const std::vector parts = image_fak->readPartitions(vhdfile, 0, gpt_style); + if (gpt_style && parts.size() == 1 + && parts[0].length < imgsize && parts[0].length >= imgsize - 1*1024*1024 - 2*512) + { + Server->Log("Using volume size " + convert(parts[0].length) + " from GPT (image file size " + convert(imgsize) + ")", LL_INFO); + imgsize = parts[0].length; + } + } + _i64 r=little_endian(imgsize); if (!input->Write((char*)&r, sizeof(_i64), img_send_timeout)) { From dea400410e4a763c1e71064ab25c3e378857b26f Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:14:30 +0100 Subject: [PATCH 357/469] Fix server settings usage from wrong thread --- urbackupserver/ClientMain.cpp | 14 ++++++++++---- urbackupserver/ClientMain.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index dec67f84b..66cbd81eb 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -2898,7 +2898,7 @@ _u32 ClientMain::getClientFilesrvConnection(FileClient *fc, ServerSettings* serv _u32 ret; if (protocol_versions.filesrvtunnel > 0) { - IPipe* pipe = new_fileclient_connection(); + IPipe* pipe = new_fileclient_connection(server_settings); if (pipe == NULL) return ERR_ERROR; @@ -2961,7 +2961,7 @@ bool ClientMain::getClientChunkedFilesrvConnection(std::auto_ptr 0) { - pipe = new_fileclient_connection(); + pipe = new_fileclient_connection(server_settings); if (pipe == NULL) return false; } @@ -3071,7 +3071,13 @@ void ClientMain::destroyTemporaryFile(IFile *tmp) Server->deleteFile(fn); } -IPipe * ClientMain::new_fileclient_connection(void) +IPipe* ClientMain::new_fileclient_connection() +{ + ServerSettings server_settings(db, clientid); + return new_fileclient_connection(&server_settings); +} + +IPipe * ClientMain::new_fileclient_connection(ServerSettings* server_settings) { std::string curr_clientname = (clientname); if(!clientsubname.empty()) @@ -3088,7 +3094,7 @@ IPipe * ClientMain::new_fileclient_connection(void) { if (protocol_versions.filesrvtunnel > 0) { - rp = getClientCommandConnection(server_settings.get(), c_filesrv_connect_timeout); + rp = getClientCommandConnection(server_settings, c_filesrv_connect_timeout); if (rp == NULL) { return NULL; diff --git a/urbackupserver/ClientMain.h b/urbackupserver/ClientMain.h index 480654341..78d044877 100644 --- a/urbackupserver/ClientMain.h +++ b/urbackupserver/ClientMain.h @@ -179,6 +179,7 @@ class ClientMain : public IThread, public FileClientChunked::ReconnectionCallbac IPipe *getClientCommandConnection(ServerSettings* server_settings, int timeoutms=10000, std::string* clientaddr=NULL, bool do_encrypt=true, bool allow_reauth=false, bool* require_reauth = NULL); virtual IPipe * new_fileclient_connection(void); + virtual IPipe* new_fileclient_connection(ServerSettings* server_settings); virtual bool handle_not_enough_space(const std::string &path); From 07984dd00579b91cec8b2edfc783538682203ac8 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:15:10 +0100 Subject: [PATCH 358/469] Keep track of for which interface we already logged the broadcast error --- urbackupcommon/fileclient/FileClient.cpp | 42 ++++++++++++++---------- urbackupcommon/fileclient/FileClient.h | 8 ++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/urbackupcommon/fileclient/FileClient.cpp b/urbackupcommon/fileclient/FileClient.cpp index b924f602c..c86239a52 100644 --- a/urbackupcommon/fileclient/FileClient.cpp +++ b/urbackupcommon/fileclient/FileClient.cpp @@ -610,14 +610,17 @@ _u32 FileClient::GetServers(bool start, const std::vector &addr_hints int rc = sendto(udpsocks[i].udpsock, &ch, 1, 0, (sockaddr*)&addr_udp, sizeof(addr_udp)); if (rc == -1) { - if(!errors.broadcast_error_ipv4) - Server->Log("Sending broadcast failed!", LL_ERROR); - - errors.broadcast_error_ipv4 = true; + const std::string iface = ipToString(udpsocks[i]); + if (errors.broadcast_error_ipv4.find(iface) == errors.broadcast_error_ipv4.end()) + { + Server->Log("Sending broadcast failed! iface " + iface, LL_ERROR); + errors.broadcast_error_ipv4.insert(iface); + } } - else - { - errors.broadcast_error_ipv4 = false; + else if(!errors.broadcast_error_ipv4.empty()) + { + const std::string iface = ipToString(udpsocks[i]); + errors.broadcast_error_ipv4.erase(iface); } } else @@ -627,24 +630,29 @@ _u32 FileClient::GetServers(bool start, const std::vector &addr_hints addr_udp.sin6_port = htons(UDP_PORT); if (inet_pton(AF_INET6, multicast_group, &addr_udp.sin6_addr) != 1) { - if(!errors.broadcast_error_ipv6) - Server->Log("inet_pton failed", LL_ERROR); - - errors.broadcast_error_ipv6 = true; + const std::string iface = ipToString(udpsocks[i]); + if (errors.broadcast_error_ipv6.find(iface) == errors.broadcast_error_ipv6.end()) + { + Server->Log("inet_pton failed iface " + iface, LL_ERROR); + errors.broadcast_error_ipv6.insert(iface); + } } char ch = ID_PING; int rc = sendto(udpsocks[i].udpsock, &ch, 1, 0, (sockaddr*)&addr_udp, sizeof(addr_udp)); if (rc == -1) { - if (!errors.broadcast_error_ipv6) - Server->Log("Sending broadcast failed! (ipv6)", LL_ERROR); - - errors.broadcast_error_ipv6 = true; + const std::string iface = ipToString(udpsocks[i]); + if (errors.broadcast_error_ipv6.find(iface) == errors.broadcast_error_ipv6.end()) + { + Server->Log("Sending broadcast failed! (ipv6) iface " + ipToString(udpsocks[i]), LL_ERROR); + errors.broadcast_error_ipv6.insert(iface); + } } - else + else if(!errors.broadcast_error_ipv6.empty()) { - errors.broadcast_error_ipv6 = false; + std::string iface = ipToString(udpsocks[i]); + errors.broadcast_error_ipv6.erase(iface); } } } diff --git a/urbackupcommon/fileclient/FileClient.h b/urbackupcommon/fileclient/FileClient.h index cf520f3ff..43852be28 100644 --- a/urbackupcommon/fileclient/FileClient.h +++ b/urbackupcommon/fileclient/FileClient.h @@ -3,6 +3,7 @@ #include #include +#include #include "packet_ids.h" #include "../../urbackupcommon/fileclient/tcpstack.h" #include "socket_header.h" @@ -121,11 +122,8 @@ class FileClient struct GetServersErrors { - GetServersErrors() : - broadcast_error_ipv4(false), broadcast_error_ipv6(false) {} - - bool broadcast_error_ipv4; - bool broadcast_error_ipv6; + std::set broadcast_error_ipv4; + std::set broadcast_error_ipv6; }; _u32 GetServers(bool start, const std::vector &addr_hints, GetServersErrors& errors); From a9630799637ed5428bb61e8a890aabaed7ac6532 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:15:39 +0100 Subject: [PATCH 359/469] Add filelist listing debug function --- urbackupserver/dllmain.cpp | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index befb485d2..cc5ac3160 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -88,6 +88,7 @@ SStartupStatus startup_status; #include "Mailer.h" #include "../urbackupcommon/settingslist.h" #include "../urbackupcommon/settings.h" +#include "../urbackupcommon/filelist_utils.h" #include #include "../Interface/DatabaseCursor.h" @@ -678,6 +679,60 @@ DLLEXPORT void LoadActions(IServer* pServer) } } + std::string list_filelist = Server->getServerParameter("list_filelist"); + if (!list_filelist.empty()) + { + IFile* f = Server->openFile(list_filelist, MODE_READ); + + if (!f) + { + Server->Log("Error opening filelist at " + list_filelist); + exit(2); + } + + char buffer[4096]; + _u32 read; + + FileListParser list_parser; + + std::string path; + SFile cf; + + while ((read = f->Read(buffer, 4096)) > 0) + { + for (size_t i = 0; i < read; ++i) + { + bool b = list_parser.nextEntry(buffer[i], cf, NULL); + if (b) + { + if (cf.isdir) + { + if (cf.name == "..") + { + if (path.empty()) + { + Server->Log("Path is empty"); + exit(1); + } + path = ExtractFilePath(path); + } + else + { + path += os_file_sep() + cf.name; + Server->Log("Folder: " + path); + } + } + else + { + Server->Log("File: " + path + "/"+ cf.name+" Size: " + PrettyPrintBytes(cf.size)); + } + } + } + } + + exit(0); + } + Server->destroyAllDatabases(); startup_status.mutex=Server->createMutex(); From 22e7a99c67852500d4c5b09ba4e1921104ed9904 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:16:42 +0100 Subject: [PATCH 360/469] Add snapshot path to updatedb prune paths updatedb was preventing the snapshots from unmounting --- linux_snapshot/dattobd_create_snapshot | 2 ++ linux_snapshot/dm_create_snapshot | 2 ++ linux_snapshot/filesystem_snapshot_common | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/linux_snapshot/dattobd_create_snapshot b/linux_snapshot/dattobd_create_snapshot index 94e75a147..70c3fe55a 100755 --- a/linux_snapshot/dattobd_create_snapshot +++ b/linux_snapshot/dattobd_create_snapshot @@ -56,6 +56,8 @@ then exit 1 fi +add_to_updatedb_conf "/mnt/urbackup_snaps" + echo "Snapshotting device $DEVICE via dattobd..." NUM=0 diff --git a/linux_snapshot/dm_create_snapshot b/linux_snapshot/dm_create_snapshot index 5c8f2e91b..882237fcc 100755 --- a/linux_snapshot/dm_create_snapshot +++ b/linux_snapshot/dm_create_snapshot @@ -43,6 +43,8 @@ then exit 1 fi +add_to_updatedb_conf "/mnt/urbackup_snaps" + echo "Snapshotting device $DEVICE via dm..." if ! dmsetup table "$DEVICE" > /dev/null 2>&1 diff --git a/linux_snapshot/filesystem_snapshot_common b/linux_snapshot/filesystem_snapshot_common index 838f265aa..6fff3a4d7 100755 --- a/linux_snapshot/filesystem_snapshot_common +++ b/linux_snapshot/filesystem_snapshot_common @@ -21,3 +21,13 @@ set_filesystem_type() { TYPE=`df -T -P | egrep " ${1}\$" | head -n 1 | tr -s " " | cut -d" " -f2` export TYPE } + +add_to_updatedb_conf() { + if test -e /etc/updatedb.conf + then + if ! grep -q "$1" /etc/updatedb.conf + then + sed -i "s|^PRUNEPATHS=\"|PRUNEPATHS=\"$1 |" /etc/updatedb.conf + fi + fi +} \ No newline at end of file From feb5b9ebb6c7e3f0bef2901c75bac6577dd0141a Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:17:18 +0100 Subject: [PATCH 361/469] Release buffer when writing it failed --- fsimageplugin/CompressedFile.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 2feb2c40c..548d712a1 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -528,6 +528,8 @@ void CompressedFile::evictFromLruCache( const SCacheItem& item ) { error=true; Server->Log("Error while writing compressed data to file", LL_ERROR); + IScopedLock lock(mutex.get()); + returnCompressedBuffer(compBuffer, compBufferIdx); return; } From cb7ce4812508d4c6f4a4433d6365a3d80017fcd5 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:17:56 +0100 Subject: [PATCH 362/469] Set decompressed size we get from ZSTD This allows correct detection of insufficient amount of decompressed data --- fsimageplugin/CompressedFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 548d712a1..362218020 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -379,7 +379,6 @@ bool CompressedFile::fillCache( __int64 offset, bool errorMsg, bool *has_error) #ifndef NO_ZSTD_COMPRESSION else if (mode == mode_zstd) { - rdecomp = blocksize; const size_t rc = ZSTD_decompress(buf, blocksize, compressedBuffer.data(), compressedSize); @@ -388,6 +387,7 @@ bool CompressedFile::fillCache( __int64 offset, bool errorMsg, bool *has_error) Server->Log(std::string("Error while decompressing file (zstd). Error code ") + ZSTD_getErrorName(rc), LL_ERROR); return false; } + rdecomp = rc; } #endif else From 79e824ebe0a15ea38e53dccfee1c025d3f0afebd Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:18:26 +0100 Subject: [PATCH 363/469] Allow writing to null file with vhdcopy --- fsimageplugin/dllmain.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/fsimageplugin/dllmain.cpp b/fsimageplugin/dllmain.cpp index 3c396af0f..5c03f54b9 100644 --- a/fsimageplugin/dllmain.cpp +++ b/fsimageplugin/dllmain.cpp @@ -876,7 +876,7 @@ DLLEXPORT void LoadActions(IServer* pServer) else { IFile *out=Server->openFile(vhdcopy_out, MODE_RW); - if(out==NULL) + if(out==NULL && vhdcopy_out!="null") { Server->Log("Couldn't open output file", LL_ERROR); exit(6); @@ -903,11 +903,13 @@ DLLEXPORT void LoadActions(IServer* pServer) uint64 currpos=skip; bool is_ok=true; - out->Seek(0); + if(out) + out->Seek(0); + while(currpos%vhd_blocksize!=0) { is_ok=in->Read(buffer, 512, read); - if(read>0) + if(read>0 && out) { _u32 rc=out->Write(buffer, (_u32)read); if(rc!=read) @@ -929,7 +931,7 @@ DLLEXPORT void LoadActions(IServer* pServer) if(in->has_sector()) { is_ok=in->Read(buffer, 4096, read); - if(read>0) + if(read>0 && out) { _u32 rc=out->Write(buffer, (_u32)read); if(rc!=read) @@ -938,6 +940,10 @@ DLLEXPORT void LoadActions(IServer* pServer) exit(7); } } + if (!is_ok) + { + Server->Log("Error reading from input file. " + os_last_error_str(), LL_ERROR); + } currpos+=read; } else @@ -945,7 +951,8 @@ DLLEXPORT void LoadActions(IServer* pServer) read=4096; currpos+=read; in->Seek(currpos); - out->Seek(currpos-skip); + if(out) + out->Seek(currpos-skip); } ++p_skip; From ad9cb819d124a01cfe97e965731195fe3b10dd99 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:19:06 +0100 Subject: [PATCH 364/469] Disable assert for now finish() is called from vhdxfile::Sync and later again --- fsimageplugin/CompressedFile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 362218020..9501252ea 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -636,7 +636,8 @@ void CompressedFile::returnCompressedBuffer(char* buf, size_t compressed_buffer_ bool CompressedFile::finish() { - assert(!finished); + // TODO: Fix + // assert(!finished); if(hotCache.get()) { From 12e2a3fe7a1cce4631aa77178d12ba9b1014e099 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 3 Dec 2025 21:26:02 +0100 Subject: [PATCH 365/469] Change install command to set proper file permissions --- install_client_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install_client_linux.sh b/install_client_linux.sh index 200474a70..142358607 100755 --- a/install_client_linux.sh +++ b/install_client_linux.sh @@ -334,7 +334,7 @@ then fi fi - install -c urbackupclientbackend.service $SYSTEMD_DIR + install -m 644 -c urbackupclientbackend.service $SYSTEMD_DIR systemctl enable urbackupclientbackend.service SYSTEMD_DBUS=yes From 5823a119b2b293586cce5856003b5af28722afd8 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:17:09 +0100 Subject: [PATCH 366/469] Debug assertions to check bat data for consistency --- fsimageplugin/vhdxfile.cpp | 18 ++++++++++++++++++ fsimageplugin/vhdxfile.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index 1c8111072..e63f7e5bb 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1693,6 +1693,8 @@ bool VHDXFile::syncInt(bool full) } } + check_bat_buf(); + int64 b_idx = -1; for (std::set::iterator it = pending_bat_entries.begin(); it != pending_bat_entries.end();) { @@ -1731,6 +1733,8 @@ bool VHDXFile::syncInt(bool full) if(stop_idx==-1) pending_bat_entries.clear(); + check_bat_buf(); + if (fast_mode) { if (!file->Sync()) @@ -2168,6 +2172,8 @@ bool VHDXFile::readBat() } } + check_bat_buf(); + return true; } @@ -2926,3 +2932,15 @@ bool VHDXFile::has_block(bool use_parent) return true; } + +void VHDXFile::check_bat_buf() +{ +#ifndef NDEBUG + for (size_t i = 0; i < bat_buf.size(); i += sizeof(VhdxBatEntry)) + { + const VhdxBatEntry* entry = reinterpret_cast(bat_buf.data() + i); + assert(entry->State != 5); + assert(entry->State != 4); + } +#endif +} diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h index 663ba7092..db088e664 100644 --- a/fsimageplugin/vhdxfile.h +++ b/fsimageplugin/vhdxfile.h @@ -142,6 +142,8 @@ class VHDXFile : public IVHDFile, public IFile bool has_block(bool use_parent); + void check_bat_buf(); + VhdxHeader curr_header; int64 curr_header_pos; From 26d02d4f382e428abfb390f2033f62eb6bb8f375 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:19:08 +0100 Subject: [PATCH 367/469] Add VHDX read error logging --- fsimageplugin/vhdxfile.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index e63f7e5bb..ea795ad27 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1252,7 +1252,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) { if (spos> dst_size) { - if (has_error != NULL) + Server->Log("Error reading from VHDX file. Trying to read beyond file size at " + convert(spos) + " size=" + convert(dst_size)); + if (has_error != NULL) *has_error = true; return 0; @@ -1267,13 +1268,14 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) { _u32 block = getBatEntry(spos, block_size, sector_size); - VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + const VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; if (bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) { - _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); + const _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); - _u32 rc = file->Read(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + const int64 fpos = bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size; + const _u32 rc = file->Read(fpos, buffer + read, toread); read += rc; @@ -1281,6 +1283,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) if (rc < toread) { + Server->Log("Error reading " + convert(toread) + " bytes from vhdx file at pos " + convert(fpos) + + " read " + convert(rc) + " toread " + convert(toread) + " error: " + os_last_error_str()); if (has_error != NULL) *has_error = true; @@ -1296,7 +1300,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { - if (has_error != NULL) + Server->Log("VHDX parent partially present though there is no parent pos=" + convert(spos), LL_WARNING); + if (has_error != NULL) *has_error = true; return read; @@ -1314,6 +1319,7 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) } else { + Server->Log("Unknown VHDX bat state " + convert(bat_entry->State) + " pos=" + convert(spos), LL_WARNING); if (has_error != NULL) *has_error = true; @@ -1337,7 +1343,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) bool set; if (!isSectorSet(spos, set)) { - if (has_error != NULL) + Server->Log("Sector of partially present VHDX block not set pos=" + convert(spos), LL_WARNING); + if (has_error != NULL) *has_error = true; return read; @@ -1359,7 +1366,11 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) if (rc < toread) { - if (has_error != NULL) + Server->Log("Error reading " + convert(toread) + " bytes from vhdx file at pos " + + convert(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size) + " spos " + convert(spos) + " set " + convert(set) + + " read " + convert(rc) + " toread " + convert(toread) + " error: " + os_last_error_str()); + + if (has_error != NULL) *has_error = true; return read; @@ -1375,7 +1386,7 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) } else if (bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) { - _u32 rc = parent->Read(spos, buffer + read, toread); + const _u32 rc = parent->Read(spos, buffer + read, toread, has_error); read += rc; spos += rc; @@ -1390,6 +1401,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) } else { + Server->Log("Unknown VHDX bat state (with parent) " + convert(bat_entry->State) + " pos=" + convert(spos), LL_WARNING); + if (has_error != NULL) *has_error = true; From 43b8e5acc84a3c2088f840c3e29434b4234f9444 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:19:34 +0100 Subject: [PATCH 368/469] Use 64-bit int for skip value --- fsimageplugin/dllmain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fsimageplugin/dllmain.cpp b/fsimageplugin/dllmain.cpp index 5c03f54b9..82b192477 100644 --- a/fsimageplugin/dllmain.cpp +++ b/fsimageplugin/dllmain.cpp @@ -884,10 +884,10 @@ DLLEXPORT void LoadActions(IServer* pServer) else { std::string skip_s=Server->getServerParameter("skip"); - int skip=1024*512; + int64 skip=1024*512; if(!skip_s.empty()) { - skip=atoi(skip_s.c_str()); + skip=watoi64(skip_s); } else if (is_disk_mbr(vhdcopy_in + ".mbr")) { @@ -940,7 +940,7 @@ DLLEXPORT void LoadActions(IServer* pServer) exit(7); } } - if (!is_ok) + if (!is_ok && currpos + read < in->getSize()) { Server->Log("Error reading from input file. " + os_last_error_str(), LL_ERROR); } From f2201e1ddf7444c7d038ff87217c43329f40ae49 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:19:58 +0100 Subject: [PATCH 369/469] Add Resize functionality to CompressedFile --- fsimageplugin/CompressedFile.cpp | 52 ++++++++++++++++++++++++++++++++ fsimageplugin/CompressedFile.h | 10 +++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 9501252ea..67325ffd3 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -718,6 +718,58 @@ _u32 CompressedFile::writeToFile(int64 offset, const char* buffer, _u32 bsize) return written; } +void CompressedFile::resetSparseExtentIter() +{ +} + +IFsFile::SSparseExtent CompressedFile::nextSparseExtent() +{ + return SSparseExtent(); +} + +bool CompressedFile::Resize(int64 new_size, bool set_sparse) +{ + IScopedLock lock(mutex.get()); + if (new_size > filesize) + { + const size_t blockIdx = static_cast(new_size / blocksize); + const size_t currNumBlockOffsets = blockOffsets.size(); + if (blockOffsets.size() <= blockIdx) + { + const size_t new_size = (blockIdx + 1) * 2; + blockOffsets.resize(new_size); + for (size_t i = currNumBlockOffsets; i < new_size; ++i) + { + blockOffsets[i] = -1; + } + } + filesize = new_size; + numBlockOffsets = (std::max)(numBlockOffsets, blockIdx + 1); + } + return true; +} + +std::vector CompressedFile::getFileExtents(int64 starting_offset, int64 block_size, bool& more_data) +{ + more_data = false; + return std::vector(); +} + +IVdlVolCache* CompressedFile::createVdlVolCache() +{ + return nullptr; +} + +int64 CompressedFile::getValidDataLength(IVdlVolCache* vol_cache) +{ + return int64(); +} + +IFsFile::os_file_handle CompressedFile::getOsHandle(bool release_handle) +{ + return os_file_handle(); +} + bool CompressedFile::hasNoMagic() { return noMagic; diff --git a/fsimageplugin/CompressedFile.h b/fsimageplugin/CompressedFile.h index bafcaf349..bc52f13b5 100644 --- a/fsimageplugin/CompressedFile.h +++ b/fsimageplugin/CompressedFile.h @@ -27,7 +27,7 @@ class ICacheEvictionCallback friend class LRUMemCache; }; -class CompressedFile : public IFile, public ICacheEvictionCallback +class CompressedFile : public IFsFile, public ICacheEvictionCallback { public: CompressedFile(std::string pFilename, int pMode, size_t n_threads); @@ -57,6 +57,14 @@ class CompressedFile : public IFile, public ICacheEvictionCallback bool hasNoMagic(); + void resetSparseExtentIter(); + SSparseExtent nextSparseExtent(); + bool Resize(int64 new_size, bool set_sparse); + std::vector getFileExtents(int64 starting_offset, int64 block_size, bool& more_data); + IVdlVolCache* createVdlVolCache(); + int64 getValidDataLength(IVdlVolCache* vol_cache); + os_file_handle getOsHandle(bool release_handle); + private: void readHeader(bool *has_error); void readIndex(bool *has_error); From 892de87352964687d2228bb0f2135e9709cdd12e Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:20:28 +0100 Subject: [PATCH 370/469] Resize compressed file when used by vhdx --- fsimageplugin/vhdxfile.cpp | 20 ++++++-------------- fsimageplugin/vhdxfile.h | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index ea795ad27..9649e7f48 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1204,19 +1204,14 @@ bool VHDXFile::setUnused(_i64 unused_start, _i64 unused_end) bool VHDXFile::setBackingFileSize(_i64 fsize) { - if (file != backing_file) - { - return false; - } - fsize += 1 * 1024 * 1024; fsize += bat_region.Length; fsize += curr_header.LogLength; fsize += meta_table_region.Length; - if (fsize > backing_file->Size()) + if (fsize > file->Size()) { - return backing_file->Resize(fsize, false); + return file->Resize(fsize, false); } return false; @@ -1910,8 +1905,7 @@ bool VHDXFile::createNew() return false; } - if (file == backing_file && - !backing_file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) + if (!file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) { Server->Log("Error writing new bat region. " + os_last_error_str(), LL_WARNING); return false; @@ -2023,10 +2017,9 @@ bool VHDXFile::replayLog() } int64 new_fsize = -1; - if (file->Size() < head_entry.new_fsize && - file == backing_file) + if (file->Size() < head_entry.new_fsize) { - if (backing_file->Resize(head_entry.new_fsize, false)) + if (file->Resize(head_entry.new_fsize, false)) new_fsize = head_entry.new_fsize; } @@ -2470,8 +2463,7 @@ bool VHDXFile::allocateBatBlockFull(int64 block) { allocated_size = new_pos + block_size + allocate_size_add_size; - if (file == backing_file && - !backing_file->Resize(allocated_size, false)) + if (!file->Resize(allocated_size, false)) { Server->Log("Error resizing backing file to new allocated size " + convert(allocated_size) + ". " + os_last_error_str(), diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h index db088e664..9ff69d796 100644 --- a/fsimageplugin/vhdxfile.h +++ b/fsimageplugin/vhdxfile.h @@ -157,7 +157,7 @@ class VHDXFile : public IVHDFile, public IFile IFsFile* backing_file; std::auto_ptr backing_file_holder; - IFile* file; + IFsFile* file; std::auto_ptr compressed_file; int64 allocated_size; bool is_open; From ae2aecb826983ce738a9291f634e5fce2c989187 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:20:58 +0100 Subject: [PATCH 371/469] Properly zero new mem cache buffers --- fsimageplugin/LRUMemCache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fsimageplugin/LRUMemCache.cpp b/fsimageplugin/LRUMemCache.cpp index 47c868cd1..5bb30fb6d 100644 --- a/fsimageplugin/LRUMemCache.cpp +++ b/fsimageplugin/LRUMemCache.cpp @@ -77,6 +77,8 @@ bool LRUMemCache::put( __int64 offset, const char* buffer, size_t bsize ) SCacheItem newItem = createInt(offset); + memset(newItem.buffer, 0, buffersize); + size_t innerOffset = static_cast(offset-newItem.offset); if( buffersize - innerOffset < bsize) From bb9bcffe1b8459b4371a3abeaa5baf4389a2708f Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 4 Dec 2025 21:13:25 +0100 Subject: [PATCH 372/469] Fix function for mmcblk devices --- urbackupclient/ClientService.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 91cffd4ca..96a0db142 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -2353,7 +2353,7 @@ namespace } std::string dl_devnum; - const char* const devnames[] = { "sd", "xvd", "vd", "hd", "loop", "nvme", "nbd", NULL }; + const char* const devnames[] = { "sd", "xvd", "vd", "hd", "loop", "nvme", "nbd", "mmcblk", NULL}; for (const char* const * devname = devnames; *devname != NULL; ++devname) { @@ -2550,6 +2550,11 @@ void parse_devnum_test() assert(deviceNumber == 2); assert(partNumber == 3); assert(dev == "/dev/loop2"); + assert(parseDevicePartNumber("/dev/mmcblk0p45", dev, deviceNumber, partNumber)); + assert(deviceNumber == 0); + assert(partNumber == 45); + assert(dev == "/dev/mmcblk0"); + } bool ClientConnector::sendMBR(std::string dl, std::string &errmsg) From 07a2d13d025b021e24b44bf1e870b2166e79ae72 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:14:30 +0100 Subject: [PATCH 373/469] Fix server settings usage from wrong thread --- urbackupserver/ClientMain.cpp | 14 ++++++++++---- urbackupserver/ClientMain.h | 1 + 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/urbackupserver/ClientMain.cpp b/urbackupserver/ClientMain.cpp index dec67f84b..66cbd81eb 100644 --- a/urbackupserver/ClientMain.cpp +++ b/urbackupserver/ClientMain.cpp @@ -2898,7 +2898,7 @@ _u32 ClientMain::getClientFilesrvConnection(FileClient *fc, ServerSettings* serv _u32 ret; if (protocol_versions.filesrvtunnel > 0) { - IPipe* pipe = new_fileclient_connection(); + IPipe* pipe = new_fileclient_connection(server_settings); if (pipe == NULL) return ERR_ERROR; @@ -2961,7 +2961,7 @@ bool ClientMain::getClientChunkedFilesrvConnection(std::auto_ptr 0) { - pipe = new_fileclient_connection(); + pipe = new_fileclient_connection(server_settings); if (pipe == NULL) return false; } @@ -3071,7 +3071,13 @@ void ClientMain::destroyTemporaryFile(IFile *tmp) Server->deleteFile(fn); } -IPipe * ClientMain::new_fileclient_connection(void) +IPipe* ClientMain::new_fileclient_connection() +{ + ServerSettings server_settings(db, clientid); + return new_fileclient_connection(&server_settings); +} + +IPipe * ClientMain::new_fileclient_connection(ServerSettings* server_settings) { std::string curr_clientname = (clientname); if(!clientsubname.empty()) @@ -3088,7 +3094,7 @@ IPipe * ClientMain::new_fileclient_connection(void) { if (protocol_versions.filesrvtunnel > 0) { - rp = getClientCommandConnection(server_settings.get(), c_filesrv_connect_timeout); + rp = getClientCommandConnection(server_settings, c_filesrv_connect_timeout); if (rp == NULL) { return NULL; diff --git a/urbackupserver/ClientMain.h b/urbackupserver/ClientMain.h index 480654341..78d044877 100644 --- a/urbackupserver/ClientMain.h +++ b/urbackupserver/ClientMain.h @@ -179,6 +179,7 @@ class ClientMain : public IThread, public FileClientChunked::ReconnectionCallbac IPipe *getClientCommandConnection(ServerSettings* server_settings, int timeoutms=10000, std::string* clientaddr=NULL, bool do_encrypt=true, bool allow_reauth=false, bool* require_reauth = NULL); virtual IPipe * new_fileclient_connection(void); + virtual IPipe* new_fileclient_connection(ServerSettings* server_settings); virtual bool handle_not_enough_space(const std::string &path); From 96b6cba94e95ccba3f27ab22bcdf62b98039a2c5 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:15:10 +0100 Subject: [PATCH 374/469] Keep track of for which interface we already logged the broadcast error --- urbackupcommon/fileclient/FileClient.cpp | 42 ++++++++++++++---------- urbackupcommon/fileclient/FileClient.h | 8 ++--- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/urbackupcommon/fileclient/FileClient.cpp b/urbackupcommon/fileclient/FileClient.cpp index b924f602c..c86239a52 100644 --- a/urbackupcommon/fileclient/FileClient.cpp +++ b/urbackupcommon/fileclient/FileClient.cpp @@ -610,14 +610,17 @@ _u32 FileClient::GetServers(bool start, const std::vector &addr_hints int rc = sendto(udpsocks[i].udpsock, &ch, 1, 0, (sockaddr*)&addr_udp, sizeof(addr_udp)); if (rc == -1) { - if(!errors.broadcast_error_ipv4) - Server->Log("Sending broadcast failed!", LL_ERROR); - - errors.broadcast_error_ipv4 = true; + const std::string iface = ipToString(udpsocks[i]); + if (errors.broadcast_error_ipv4.find(iface) == errors.broadcast_error_ipv4.end()) + { + Server->Log("Sending broadcast failed! iface " + iface, LL_ERROR); + errors.broadcast_error_ipv4.insert(iface); + } } - else - { - errors.broadcast_error_ipv4 = false; + else if(!errors.broadcast_error_ipv4.empty()) + { + const std::string iface = ipToString(udpsocks[i]); + errors.broadcast_error_ipv4.erase(iface); } } else @@ -627,24 +630,29 @@ _u32 FileClient::GetServers(bool start, const std::vector &addr_hints addr_udp.sin6_port = htons(UDP_PORT); if (inet_pton(AF_INET6, multicast_group, &addr_udp.sin6_addr) != 1) { - if(!errors.broadcast_error_ipv6) - Server->Log("inet_pton failed", LL_ERROR); - - errors.broadcast_error_ipv6 = true; + const std::string iface = ipToString(udpsocks[i]); + if (errors.broadcast_error_ipv6.find(iface) == errors.broadcast_error_ipv6.end()) + { + Server->Log("inet_pton failed iface " + iface, LL_ERROR); + errors.broadcast_error_ipv6.insert(iface); + } } char ch = ID_PING; int rc = sendto(udpsocks[i].udpsock, &ch, 1, 0, (sockaddr*)&addr_udp, sizeof(addr_udp)); if (rc == -1) { - if (!errors.broadcast_error_ipv6) - Server->Log("Sending broadcast failed! (ipv6)", LL_ERROR); - - errors.broadcast_error_ipv6 = true; + const std::string iface = ipToString(udpsocks[i]); + if (errors.broadcast_error_ipv6.find(iface) == errors.broadcast_error_ipv6.end()) + { + Server->Log("Sending broadcast failed! (ipv6) iface " + ipToString(udpsocks[i]), LL_ERROR); + errors.broadcast_error_ipv6.insert(iface); + } } - else + else if(!errors.broadcast_error_ipv6.empty()) { - errors.broadcast_error_ipv6 = false; + std::string iface = ipToString(udpsocks[i]); + errors.broadcast_error_ipv6.erase(iface); } } } diff --git a/urbackupcommon/fileclient/FileClient.h b/urbackupcommon/fileclient/FileClient.h index cf520f3ff..43852be28 100644 --- a/urbackupcommon/fileclient/FileClient.h +++ b/urbackupcommon/fileclient/FileClient.h @@ -3,6 +3,7 @@ #include #include +#include #include "packet_ids.h" #include "../../urbackupcommon/fileclient/tcpstack.h" #include "socket_header.h" @@ -121,11 +122,8 @@ class FileClient struct GetServersErrors { - GetServersErrors() : - broadcast_error_ipv4(false), broadcast_error_ipv6(false) {} - - bool broadcast_error_ipv4; - bool broadcast_error_ipv6; + std::set broadcast_error_ipv4; + std::set broadcast_error_ipv6; }; _u32 GetServers(bool start, const std::vector &addr_hints, GetServersErrors& errors); From 731c69933e2c507e64761e059a1dbb8691a1f299 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:15:39 +0100 Subject: [PATCH 375/469] Add filelist listing debug function --- urbackupserver/dllmain.cpp | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index befb485d2..cc5ac3160 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -88,6 +88,7 @@ SStartupStatus startup_status; #include "Mailer.h" #include "../urbackupcommon/settingslist.h" #include "../urbackupcommon/settings.h" +#include "../urbackupcommon/filelist_utils.h" #include #include "../Interface/DatabaseCursor.h" @@ -678,6 +679,60 @@ DLLEXPORT void LoadActions(IServer* pServer) } } + std::string list_filelist = Server->getServerParameter("list_filelist"); + if (!list_filelist.empty()) + { + IFile* f = Server->openFile(list_filelist, MODE_READ); + + if (!f) + { + Server->Log("Error opening filelist at " + list_filelist); + exit(2); + } + + char buffer[4096]; + _u32 read; + + FileListParser list_parser; + + std::string path; + SFile cf; + + while ((read = f->Read(buffer, 4096)) > 0) + { + for (size_t i = 0; i < read; ++i) + { + bool b = list_parser.nextEntry(buffer[i], cf, NULL); + if (b) + { + if (cf.isdir) + { + if (cf.name == "..") + { + if (path.empty()) + { + Server->Log("Path is empty"); + exit(1); + } + path = ExtractFilePath(path); + } + else + { + path += os_file_sep() + cf.name; + Server->Log("Folder: " + path); + } + } + else + { + Server->Log("File: " + path + "/"+ cf.name+" Size: " + PrettyPrintBytes(cf.size)); + } + } + } + } + + exit(0); + } + Server->destroyAllDatabases(); startup_status.mutex=Server->createMutex(); From 7ad7d3848a23af4805cbee98700608057e7de0ba Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:16:42 +0100 Subject: [PATCH 376/469] Add snapshot path to updatedb prune paths updatedb was preventing the snapshots from unmounting --- linux_snapshot/dattobd_create_snapshot | 2 ++ linux_snapshot/dm_create_snapshot | 2 ++ linux_snapshot/filesystem_snapshot_common | 10 ++++++++++ 3 files changed, 14 insertions(+) diff --git a/linux_snapshot/dattobd_create_snapshot b/linux_snapshot/dattobd_create_snapshot index 94e75a147..70c3fe55a 100755 --- a/linux_snapshot/dattobd_create_snapshot +++ b/linux_snapshot/dattobd_create_snapshot @@ -56,6 +56,8 @@ then exit 1 fi +add_to_updatedb_conf "/mnt/urbackup_snaps" + echo "Snapshotting device $DEVICE via dattobd..." NUM=0 diff --git a/linux_snapshot/dm_create_snapshot b/linux_snapshot/dm_create_snapshot index 5c8f2e91b..882237fcc 100755 --- a/linux_snapshot/dm_create_snapshot +++ b/linux_snapshot/dm_create_snapshot @@ -43,6 +43,8 @@ then exit 1 fi +add_to_updatedb_conf "/mnt/urbackup_snaps" + echo "Snapshotting device $DEVICE via dm..." if ! dmsetup table "$DEVICE" > /dev/null 2>&1 diff --git a/linux_snapshot/filesystem_snapshot_common b/linux_snapshot/filesystem_snapshot_common index 838f265aa..6fff3a4d7 100755 --- a/linux_snapshot/filesystem_snapshot_common +++ b/linux_snapshot/filesystem_snapshot_common @@ -21,3 +21,13 @@ set_filesystem_type() { TYPE=`df -T -P | egrep " ${1}\$" | head -n 1 | tr -s " " | cut -d" " -f2` export TYPE } + +add_to_updatedb_conf() { + if test -e /etc/updatedb.conf + then + if ! grep -q "$1" /etc/updatedb.conf + then + sed -i "s|^PRUNEPATHS=\"|PRUNEPATHS=\"$1 |" /etc/updatedb.conf + fi + fi +} \ No newline at end of file From bdf2dc3695ec50aac168a25d2e70d0ffbd41b23e Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:17:18 +0100 Subject: [PATCH 377/469] Release buffer when writing it failed --- fsimageplugin/CompressedFile.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 2feb2c40c..548d712a1 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -528,6 +528,8 @@ void CompressedFile::evictFromLruCache( const SCacheItem& item ) { error=true; Server->Log("Error while writing compressed data to file", LL_ERROR); + IScopedLock lock(mutex.get()); + returnCompressedBuffer(compBuffer, compBufferIdx); return; } From 78a207a085fb52699e6e47c02294681831fe5c23 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:17:56 +0100 Subject: [PATCH 378/469] Set decompressed size we get from ZSTD This allows correct detection of insufficient amount of decompressed data --- fsimageplugin/CompressedFile.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 548d712a1..362218020 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -379,7 +379,6 @@ bool CompressedFile::fillCache( __int64 offset, bool errorMsg, bool *has_error) #ifndef NO_ZSTD_COMPRESSION else if (mode == mode_zstd) { - rdecomp = blocksize; const size_t rc = ZSTD_decompress(buf, blocksize, compressedBuffer.data(), compressedSize); @@ -388,6 +387,7 @@ bool CompressedFile::fillCache( __int64 offset, bool errorMsg, bool *has_error) Server->Log(std::string("Error while decompressing file (zstd). Error code ") + ZSTD_getErrorName(rc), LL_ERROR); return false; } + rdecomp = rc; } #endif else From 45c41533f941fa869d31282d12cc7ec5b8ff571d Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:18:26 +0100 Subject: [PATCH 379/469] Allow writing to null file with vhdcopy --- fsimageplugin/dllmain.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/fsimageplugin/dllmain.cpp b/fsimageplugin/dllmain.cpp index 3c396af0f..5c03f54b9 100644 --- a/fsimageplugin/dllmain.cpp +++ b/fsimageplugin/dllmain.cpp @@ -876,7 +876,7 @@ DLLEXPORT void LoadActions(IServer* pServer) else { IFile *out=Server->openFile(vhdcopy_out, MODE_RW); - if(out==NULL) + if(out==NULL && vhdcopy_out!="null") { Server->Log("Couldn't open output file", LL_ERROR); exit(6); @@ -903,11 +903,13 @@ DLLEXPORT void LoadActions(IServer* pServer) uint64 currpos=skip; bool is_ok=true; - out->Seek(0); + if(out) + out->Seek(0); + while(currpos%vhd_blocksize!=0) { is_ok=in->Read(buffer, 512, read); - if(read>0) + if(read>0 && out) { _u32 rc=out->Write(buffer, (_u32)read); if(rc!=read) @@ -929,7 +931,7 @@ DLLEXPORT void LoadActions(IServer* pServer) if(in->has_sector()) { is_ok=in->Read(buffer, 4096, read); - if(read>0) + if(read>0 && out) { _u32 rc=out->Write(buffer, (_u32)read); if(rc!=read) @@ -938,6 +940,10 @@ DLLEXPORT void LoadActions(IServer* pServer) exit(7); } } + if (!is_ok) + { + Server->Log("Error reading from input file. " + os_last_error_str(), LL_ERROR); + } currpos+=read; } else @@ -945,7 +951,8 @@ DLLEXPORT void LoadActions(IServer* pServer) read=4096; currpos+=read; in->Seek(currpos); - out->Seek(currpos-skip); + if(out) + out->Seek(currpos-skip); } ++p_skip; From 7cf85a41285b6db1c66091bc517ea16f535a581e Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 2 Dec 2025 23:19:06 +0100 Subject: [PATCH 380/469] Disable assert for now finish() is called from vhdxfile::Sync and later again --- fsimageplugin/CompressedFile.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 362218020..9501252ea 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -636,7 +636,8 @@ void CompressedFile::returnCompressedBuffer(char* buf, size_t compressed_buffer_ bool CompressedFile::finish() { - assert(!finished); + // TODO: Fix + // assert(!finished); if(hotCache.get()) { From 9ccb4cbb791f59932bfd7f46650a44e2d60bbf1a Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:17:09 +0100 Subject: [PATCH 381/469] Debug assertions to check bat data for consistency --- fsimageplugin/vhdxfile.cpp | 18 ++++++++++++++++++ fsimageplugin/vhdxfile.h | 2 ++ 2 files changed, 20 insertions(+) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index 1c8111072..e63f7e5bb 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1693,6 +1693,8 @@ bool VHDXFile::syncInt(bool full) } } + check_bat_buf(); + int64 b_idx = -1; for (std::set::iterator it = pending_bat_entries.begin(); it != pending_bat_entries.end();) { @@ -1731,6 +1733,8 @@ bool VHDXFile::syncInt(bool full) if(stop_idx==-1) pending_bat_entries.clear(); + check_bat_buf(); + if (fast_mode) { if (!file->Sync()) @@ -2168,6 +2172,8 @@ bool VHDXFile::readBat() } } + check_bat_buf(); + return true; } @@ -2926,3 +2932,15 @@ bool VHDXFile::has_block(bool use_parent) return true; } + +void VHDXFile::check_bat_buf() +{ +#ifndef NDEBUG + for (size_t i = 0; i < bat_buf.size(); i += sizeof(VhdxBatEntry)) + { + const VhdxBatEntry* entry = reinterpret_cast(bat_buf.data() + i); + assert(entry->State != 5); + assert(entry->State != 4); + } +#endif +} diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h index 663ba7092..db088e664 100644 --- a/fsimageplugin/vhdxfile.h +++ b/fsimageplugin/vhdxfile.h @@ -142,6 +142,8 @@ class VHDXFile : public IVHDFile, public IFile bool has_block(bool use_parent); + void check_bat_buf(); + VhdxHeader curr_header; int64 curr_header_pos; From 06511e8f3549317f7f1131ec347d17678d100a7c Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:19:08 +0100 Subject: [PATCH 382/469] Add VHDX read error logging --- fsimageplugin/vhdxfile.cpp | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index e63f7e5bb..ea795ad27 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1252,7 +1252,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) { if (spos> dst_size) { - if (has_error != NULL) + Server->Log("Error reading from VHDX file. Trying to read beyond file size at " + convert(spos) + " size=" + convert(dst_size)); + if (has_error != NULL) *has_error = true; return 0; @@ -1267,13 +1268,14 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) { _u32 block = getBatEntry(spos, block_size, sector_size); - VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; + const VhdxBatEntry* bat_entry = reinterpret_cast(bat_buf.data()) + block; if (bat_entry->State == PAYLOAD_BLOCK_FULLY_PRESENT) { - _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); + const _u32 toread = (std::min)(block_size - static_cast<_u32>(spos % block_size), bsize - read); - _u32 rc = file->Read(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size, + const int64 fpos = bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size; + const _u32 rc = file->Read(fpos, buffer + read, toread); read += rc; @@ -1281,6 +1283,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) if (rc < toread) { + Server->Log("Error reading " + convert(toread) + " bytes from vhdx file at pos " + convert(fpos) + + " read " + convert(rc) + " toread " + convert(toread) + " error: " + os_last_error_str()); if (has_error != NULL) *has_error = true; @@ -1296,7 +1300,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) if (bat_entry->State == PAYLOAD_BLOCK_PARTIALLY_PRESENT) { - if (has_error != NULL) + Server->Log("VHDX parent partially present though there is no parent pos=" + convert(spos), LL_WARNING); + if (has_error != NULL) *has_error = true; return read; @@ -1314,6 +1319,7 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) } else { + Server->Log("Unknown VHDX bat state " + convert(bat_entry->State) + " pos=" + convert(spos), LL_WARNING); if (has_error != NULL) *has_error = true; @@ -1337,7 +1343,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) bool set; if (!isSectorSet(spos, set)) { - if (has_error != NULL) + Server->Log("Sector of partially present VHDX block not set pos=" + convert(spos), LL_WARNING); + if (has_error != NULL) *has_error = true; return read; @@ -1359,7 +1366,11 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) if (rc < toread) { - if (has_error != NULL) + Server->Log("Error reading " + convert(toread) + " bytes from vhdx file at pos " + + convert(bat_entry->FileOffsetMB * 1024 * 1024 + spos % block_size) + " spos " + convert(spos) + " set " + convert(set) + + " read " + convert(rc) + " toread " + convert(toread) + " error: " + os_last_error_str()); + + if (has_error != NULL) *has_error = true; return read; @@ -1375,7 +1386,7 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) } else if (bat_entry->State == PAYLOAD_BLOCK_NOT_PRESENT) { - _u32 rc = parent->Read(spos, buffer + read, toread); + const _u32 rc = parent->Read(spos, buffer + read, toread, has_error); read += rc; spos += rc; @@ -1390,6 +1401,8 @@ _u32 VHDXFile::Read(int64 spos, char* buffer, _u32 bsize, bool* has_error) } else { + Server->Log("Unknown VHDX bat state (with parent) " + convert(bat_entry->State) + " pos=" + convert(spos), LL_WARNING); + if (has_error != NULL) *has_error = true; From fe6e285447ecf657c1bef5cdf33ad4d7fcdcb494 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:19:34 +0100 Subject: [PATCH 383/469] Use 64-bit int for skip value --- fsimageplugin/dllmain.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fsimageplugin/dllmain.cpp b/fsimageplugin/dllmain.cpp index 5c03f54b9..82b192477 100644 --- a/fsimageplugin/dllmain.cpp +++ b/fsimageplugin/dllmain.cpp @@ -884,10 +884,10 @@ DLLEXPORT void LoadActions(IServer* pServer) else { std::string skip_s=Server->getServerParameter("skip"); - int skip=1024*512; + int64 skip=1024*512; if(!skip_s.empty()) { - skip=atoi(skip_s.c_str()); + skip=watoi64(skip_s); } else if (is_disk_mbr(vhdcopy_in + ".mbr")) { @@ -940,7 +940,7 @@ DLLEXPORT void LoadActions(IServer* pServer) exit(7); } } - if (!is_ok) + if (!is_ok && currpos + read < in->getSize()) { Server->Log("Error reading from input file. " + os_last_error_str(), LL_ERROR); } From 2168249c252c7cb7a8adf8d3fe6eddc03b9bf2c2 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:19:58 +0100 Subject: [PATCH 384/469] Add Resize functionality to CompressedFile --- fsimageplugin/CompressedFile.cpp | 52 ++++++++++++++++++++++++++++++++ fsimageplugin/CompressedFile.h | 10 +++++- 2 files changed, 61 insertions(+), 1 deletion(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 9501252ea..67325ffd3 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -718,6 +718,58 @@ _u32 CompressedFile::writeToFile(int64 offset, const char* buffer, _u32 bsize) return written; } +void CompressedFile::resetSparseExtentIter() +{ +} + +IFsFile::SSparseExtent CompressedFile::nextSparseExtent() +{ + return SSparseExtent(); +} + +bool CompressedFile::Resize(int64 new_size, bool set_sparse) +{ + IScopedLock lock(mutex.get()); + if (new_size > filesize) + { + const size_t blockIdx = static_cast(new_size / blocksize); + const size_t currNumBlockOffsets = blockOffsets.size(); + if (blockOffsets.size() <= blockIdx) + { + const size_t new_size = (blockIdx + 1) * 2; + blockOffsets.resize(new_size); + for (size_t i = currNumBlockOffsets; i < new_size; ++i) + { + blockOffsets[i] = -1; + } + } + filesize = new_size; + numBlockOffsets = (std::max)(numBlockOffsets, blockIdx + 1); + } + return true; +} + +std::vector CompressedFile::getFileExtents(int64 starting_offset, int64 block_size, bool& more_data) +{ + more_data = false; + return std::vector(); +} + +IVdlVolCache* CompressedFile::createVdlVolCache() +{ + return nullptr; +} + +int64 CompressedFile::getValidDataLength(IVdlVolCache* vol_cache) +{ + return int64(); +} + +IFsFile::os_file_handle CompressedFile::getOsHandle(bool release_handle) +{ + return os_file_handle(); +} + bool CompressedFile::hasNoMagic() { return noMagic; diff --git a/fsimageplugin/CompressedFile.h b/fsimageplugin/CompressedFile.h index bafcaf349..bc52f13b5 100644 --- a/fsimageplugin/CompressedFile.h +++ b/fsimageplugin/CompressedFile.h @@ -27,7 +27,7 @@ class ICacheEvictionCallback friend class LRUMemCache; }; -class CompressedFile : public IFile, public ICacheEvictionCallback +class CompressedFile : public IFsFile, public ICacheEvictionCallback { public: CompressedFile(std::string pFilename, int pMode, size_t n_threads); @@ -57,6 +57,14 @@ class CompressedFile : public IFile, public ICacheEvictionCallback bool hasNoMagic(); + void resetSparseExtentIter(); + SSparseExtent nextSparseExtent(); + bool Resize(int64 new_size, bool set_sparse); + std::vector getFileExtents(int64 starting_offset, int64 block_size, bool& more_data); + IVdlVolCache* createVdlVolCache(); + int64 getValidDataLength(IVdlVolCache* vol_cache); + os_file_handle getOsHandle(bool release_handle); + private: void readHeader(bool *has_error); void readIndex(bool *has_error); From 6663c8e25e76cc8123f0fbd7a445c267c96e7eb7 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:20:28 +0100 Subject: [PATCH 385/469] Resize compressed file when used by vhdx --- fsimageplugin/vhdxfile.cpp | 20 ++++++-------------- fsimageplugin/vhdxfile.h | 2 +- 2 files changed, 7 insertions(+), 15 deletions(-) diff --git a/fsimageplugin/vhdxfile.cpp b/fsimageplugin/vhdxfile.cpp index ea795ad27..9649e7f48 100644 --- a/fsimageplugin/vhdxfile.cpp +++ b/fsimageplugin/vhdxfile.cpp @@ -1204,19 +1204,14 @@ bool VHDXFile::setUnused(_i64 unused_start, _i64 unused_end) bool VHDXFile::setBackingFileSize(_i64 fsize) { - if (file != backing_file) - { - return false; - } - fsize += 1 * 1024 * 1024; fsize += bat_region.Length; fsize += curr_header.LogLength; fsize += meta_table_region.Length; - if (fsize > backing_file->Size()) + if (fsize > file->Size()) { - return backing_file->Resize(fsize, false); + return file->Resize(fsize, false); } return false; @@ -1910,8 +1905,7 @@ bool VHDXFile::createNew() return false; } - if (file == backing_file && - !backing_file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) + if (!file->Resize(bat_region.FileOffset + bat_region.Length + allocate_size_add_size, false)) { Server->Log("Error writing new bat region. " + os_last_error_str(), LL_WARNING); return false; @@ -2023,10 +2017,9 @@ bool VHDXFile::replayLog() } int64 new_fsize = -1; - if (file->Size() < head_entry.new_fsize && - file == backing_file) + if (file->Size() < head_entry.new_fsize) { - if (backing_file->Resize(head_entry.new_fsize, false)) + if (file->Resize(head_entry.new_fsize, false)) new_fsize = head_entry.new_fsize; } @@ -2470,8 +2463,7 @@ bool VHDXFile::allocateBatBlockFull(int64 block) { allocated_size = new_pos + block_size + allocate_size_add_size; - if (file == backing_file && - !backing_file->Resize(allocated_size, false)) + if (!file->Resize(allocated_size, false)) { Server->Log("Error resizing backing file to new allocated size " + convert(allocated_size) + ". " + os_last_error_str(), diff --git a/fsimageplugin/vhdxfile.h b/fsimageplugin/vhdxfile.h index db088e664..9ff69d796 100644 --- a/fsimageplugin/vhdxfile.h +++ b/fsimageplugin/vhdxfile.h @@ -157,7 +157,7 @@ class VHDXFile : public IVHDFile, public IFile IFsFile* backing_file; std::auto_ptr backing_file_holder; - IFile* file; + IFsFile* file; std::auto_ptr compressed_file; int64 allocated_size; bool is_open; From 525107d822e3ae8f4a473ccb8b2aea0e9fa86f19 Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 3 Dec 2025 23:20:58 +0100 Subject: [PATCH 386/469] Properly zero new mem cache buffers --- fsimageplugin/LRUMemCache.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/fsimageplugin/LRUMemCache.cpp b/fsimageplugin/LRUMemCache.cpp index 47c868cd1..5bb30fb6d 100644 --- a/fsimageplugin/LRUMemCache.cpp +++ b/fsimageplugin/LRUMemCache.cpp @@ -77,6 +77,8 @@ bool LRUMemCache::put( __int64 offset, const char* buffer, size_t bsize ) SCacheItem newItem = createInt(offset); + memset(newItem.buffer, 0, buffersize); + size_t innerOffset = static_cast(offset-newItem.offset); if( buffersize - innerOffset < bsize) From 0b3d51b7df2e1145af202f1253cd081ae061dc25 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 4 Dec 2025 21:13:25 +0100 Subject: [PATCH 387/469] Fix function for mmcblk devices --- urbackupclient/ClientService.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 91cffd4ca..96a0db142 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -2353,7 +2353,7 @@ namespace } std::string dl_devnum; - const char* const devnames[] = { "sd", "xvd", "vd", "hd", "loop", "nvme", "nbd", NULL }; + const char* const devnames[] = { "sd", "xvd", "vd", "hd", "loop", "nvme", "nbd", "mmcblk", NULL}; for (const char* const * devname = devnames; *devname != NULL; ++devname) { @@ -2550,6 +2550,11 @@ void parse_devnum_test() assert(deviceNumber == 2); assert(partNumber == 3); assert(dev == "/dev/loop2"); + assert(parseDevicePartNumber("/dev/mmcblk0p45", dev, deviceNumber, partNumber)); + assert(deviceNumber == 0); + assert(partNumber == 45); + assert(dev == "/dev/mmcblk0"); + } bool ClientConnector::sendMBR(std::string dl, std::string &errmsg) From fd93946f97017809113d854fced12ef0329508f0 Mon Sep 17 00:00:00 2001 From: Martin Date: Sat, 6 Dec 2025 08:36:00 +0100 Subject: [PATCH 388/469] Handle different device mapper naming as well --- urbackupclient/ClientService.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index 96a0db142..c5b3648de 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -2295,7 +2295,8 @@ namespace bool parseDevicePartNumber(const std::string& volfn, std::string& dev, int& DeviceNumber, int& PartNumber) { if (next(volfn, 0, "/dev/mapper/") - || next(volfn, 0, "/dev/dm-") ) + || next(volfn, 0, "/dev/dm-") + || (next(volfn, 0, "/dev/") && volfn.find('/', 5) != std::string::npos) ) { std::string dm_table; //TODO: Use ioctl here From c5cf0f642808423bc8806ed7d002ed9448d48fb9 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 31 Dec 2025 16:27:30 +0100 Subject: [PATCH 389/469] Handle pkg-config failure gracefully for systemd dir --- install_client_linux.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install_client_linux.sh b/install_client_linux.sh index 142358607..2f8a3d718 100755 --- a/install_client_linux.sh +++ b/install_client_linux.sh @@ -315,7 +315,7 @@ then SYSTEMD_DIR="" if command -v pkg-config >/dev/null 2>&1 then - SYSTEMD_DIR=`pkg-config systemd --variable=systemdsystemunitdir` + SYSTEMD_DIR=`pkg-config systemd --variable=systemdsystemunitdir || true` fi if [ "x$SYSTEMD_DIR" = x ] From 63c23cb4568a0561935a804fb642a705577a4d49 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 6 Jan 2026 11:44:02 +0100 Subject: [PATCH 390/469] Make restore button display more specific --- urbackupserver/doc/admin_guide.tex | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/urbackupserver/doc/admin_guide.tex b/urbackupserver/doc/admin_guide.tex index e7e3f8e91..da58091f2 100644 --- a/urbackupserver/doc/admin_guide.tex +++ b/urbackupserver/doc/admin_guide.tex @@ -1138,7 +1138,7 @@ \subsection{Restoring file backups} Since UrBackup 2.0.x users can directly access the web interface from the client if a server URL is configured. Either they right-click on the UrBackup tray icon and then click ``Access/restore backups'' which opens the browser, or they can right click a file/directory in a backup path and then click on ``Access/restore backups'' to access all backups of a file/directory (only on Windows with Windows Explorer).\\ -When browsing backups the web interface will show a restore button if the client is online. The restore will ask for user confirmation. If the client includes a GUI component (tray icon), the user confirmation will popup for all active users on the client to be restored. If not acknowledged in time (timeout) or if declined the restore will fail. +When browsing backups the web interface will show a restore button if the client is online and the tray icon UI is running. The restore will ask for user confirmation. If the client includes a GUI component (tray icon), the user confirmation will popup for all active users on the client to be restored. If not acknowledged in time (timeout) or if declined the restore will fail. You can change this behaviour in \textsl{C:\textbackslash Program files \textbackslash UrBackup \textbackslash args.txt} by changing ``default'' to ``server-confirms'' on Windows, or by changing the restore setting in \textsl{/etc/default/urbackupclient} or \textsl{/etc/sysconfig/urbackupclient} on Linux. UrBackup is setup this way because a theoretical data loss scenario is an attacker taking control of your backup server, deleting all backups and then deleting all files on the clients via restores. @@ -1592,3 +1592,4 @@ \subsubsection{Btrfs} \end{document} + From 07ee5a7fcb26a4e145df3c9309bff5d4ca46d028 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 11 Jan 2026 13:04:51 +0100 Subject: [PATCH 391/469] Fix Windows 11 check --- urbackupclient/ClientServiceCMD.cpp | 51 +++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index d5e215b67..d947c157a 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1202,16 +1202,38 @@ namespace SetThreadExecutionState(ES_CONTINUOUS); } + typedef LONG(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW); + + bool IsWindows11() + { + HMODULE hMod = GetModuleHandleW(L"ntdll.dll"); + if (!hMod) + return false; + + RtlGetVersionPtr pRtlGetVersion = + reinterpret_cast(GetProcAddress(hMod, "RtlGetVersion")); + + if (!pRtlGetVersion) + return false; + + RTL_OSVERSIONINFOW info; + ZeroMemory(&info, sizeof(info)); + info.dwOSVersionInfoSize = sizeof(info); + + if (pRtlGetVersion(&info) != 0) + return false; + + /* Windows 11 = major version 10, build >= 22000 */ + return info.dwMajorVersion>10 || + (info.dwMajorVersion==10 && info.dwMinorVersion>0) || + (info.dwMajorVersion == 10 && info.dwBuildNumber >= 22000); + } + + void preventSleep() { - OSVERSIONINFO verinfo = {}; - verinfo.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); - //Check for Win11 - if (GetVersionExW(&verinfo) && - (verinfo.dwMajorVersion > 10 || - (verinfo.dwMajorVersion == 10 && verinfo.dwMinorVersion > 0) || - (verinfo.dwMajorVersion == 10 && verinfo.dwMinorVersion == 0 && - verinfo.dwBuildNumber >= 22000))) + static bool isWin11 = IsWindows11(); + if (isWin11) { std::lock_guard lock(prevent_sleep_mutex); @@ -1251,7 +1273,7 @@ void ClientConnector::CMD_PING_RUNNING(const std::string &cmd) return; } - int pcdone_old = proc->pcdone; + const int pcdone_old = proc->pcdone; if (pcdone_new.empty()) proc->pcdone = -1; @@ -1267,7 +1289,8 @@ void ClientConnector::CMD_PING_RUNNING(const std::string &cmd) proc->last_pingtime = Server->getTimeMS(); #ifdef _WIN32 - preventSleep(); + if (!IdleCheckerThread::getPause()) + preventSleep(); #endif } @@ -1277,9 +1300,10 @@ void ClientConnector::CMD_PING_RUNNING2(const std::string &cmd) str_map params; ParseParamStrHttp(params_str, ¶ms); str_map::iterator it_paused_fb = params.find("paused_fb"); + const bool paused = IdleCheckerThread::getPause(); if (it_paused_fb != params.end() && it_paused_fb->second == "1" - && IdleCheckerThread::getPause()) + && paused) { tcpstack.Send(pipe, "PAUSED"); } @@ -1305,7 +1329,7 @@ void ClientConnector::CMD_PING_RUNNING2(const std::string &cmd) std::string pcdone_new=params["pc_done"]; - int pcdone_old = proc->pcdone; + const int pcdone_old = proc->pcdone; if(pcdone_new.empty()) proc->pcdone =-1; @@ -1325,7 +1349,8 @@ void ClientConnector::CMD_PING_RUNNING2(const std::string &cmd) proc->done_bytes = watoi64(params["done_bytes"]); #ifdef _WIN32 - preventSleep(); + if(!paused) + preventSleep(); #endif } From 464104acb36f6b6d03932d020b5b4748b8b3aee6 Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 11 Jan 2026 19:53:41 +0100 Subject: [PATCH 392/469] Increment version --- configure.ac_server | 2 +- urbackupserver/www/js/urbackup.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/configure.ac_server b/configure.ac_server index 9dcbe031c..8337791ec 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.34.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.35.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index c18b8f3e0..4cdf6db5d 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5,7 +5,7 @@ g.startup=true; g.no_tab_mouse_click=false; g.tabberidx=-1; g.progress_stop_id=-1; -g.current_version=2005003400; +g.current_version=2005003500; g.status_show_all=false; g.ldap_login=false; g.datatable_default_config={}; From 0aeadc3c872024ea74084a23c74ba03062ecaade Mon Sep 17 00:00:00 2001 From: Martin Date: Sun, 11 Jan 2026 19:55:05 +0100 Subject: [PATCH 393/469] Increment version --- configure.ac_client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac_client b/configure.ac_client index 742781b8b..62399a02a 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.26.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.27.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM From 9e6845ac91d464d71eea0f06bc6101c854af51c1 Mon Sep 17 00:00:00 2001 From: Martin Date: Tue, 20 Jan 2026 10:34:13 +0100 Subject: [PATCH 394/469] Fix crash in Win 11 prevent sleep function --- configure.ac_client | 2 +- urbackupclient/ClientServiceCMD.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/configure.ac_client b/configure.ac_client index 62399a02a..af381ab2e 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.27.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.28.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index d947c157a..c7ad91d50 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1239,6 +1239,9 @@ namespace if (last_prevent_sleep_time == 0) { + if (prevent_sleep_thread.joinable()) + prevent_sleep_thread.join(); + prevent_sleep_thread = std::thread(prevent_thread_func); } From e897f6984617f8d1f41dbcd437ee88d94cb1afa3 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 27 Jan 2026 17:43:20 +0100 Subject: [PATCH 395/469] Fix script for newer homebrew --- switch_build_mac.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/switch_build_mac.sh b/switch_build_mac.sh index d1de6354e..5477c653d 100755 --- a/switch_build_mac.sh +++ b/switch_build_mac.sh @@ -1,5 +1,6 @@ #!/bin/sh -export PATH="/usr/local/opt/gnu-sed/libexec/gnubin:$PATH" +export PATH="$HOMEBREW_PREFIX/opt/gnu-sed/libexec/gnubin:$PATH" ./switch_build.sh $* + From e73288fca76ce98a8e4e5f011d2b1140364fa796 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 27 Jan 2026 18:19:38 +0100 Subject: [PATCH 396/469] Fix macOS build --- urbackupclient/FileMetadataDownloadThread.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/urbackupclient/FileMetadataDownloadThread.cpp b/urbackupclient/FileMetadataDownloadThread.cpp index c4aa5b401..a3d4069ef 100644 --- a/urbackupclient/FileMetadataDownloadThread.cpp +++ b/urbackupclient/FileMetadataDownloadThread.cpp @@ -40,6 +40,7 @@ #define llistxattr(path, list, size) listxattr(path, list, size, XATTR_NOFOLLOW) #define lremovexattr(path, name) removexattr(path, name, XATTR_NOFOLLOW) #define lsetxattr(path, name, value, size, flags) setxattr(path, name, value, size, 0, XATTR_NOFOLLOW|flags) +#define stat64 stat #elif __FreeBSD__ #define O_SYMLINK 0 #define stat64 stat From 2a6a7e60eb9d33272ea7a5db78ee088670dd0e58 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 28 Jan 2026 12:58:05 +0100 Subject: [PATCH 397/469] Improve macOS build - Fix build - Build fat binaries for x86_64 and arm64 --- create_osx_installer.sh | 96 ++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/create_osx_installer.sh b/create_osx_installer.sh index b8a4bc807..982993cb5 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -13,40 +13,72 @@ fi if !($development); then - if [ "x$AC_USERNAME" = x ]; then - echo "Notarization username not set (AC_USERNAME)" - exit 1 - fi - - if [ "x$AC_PASSWORD" = x ]; then - echo "Notarization account password not set (AC_PASSWORD)" - exit 1 - fi - git reset --hard cd client git reset --hard cd .. python3 build/replace_versions.py + echo foo fi rm -R osx-pkg || true rm -R osx-pkg2 || true +rm -R osx-pkg_x86 || true ./download_cryptopp.sh +function config() { + ARCH=$1 + echo "Configuring for arch $ARCH..." + HOMEBREW="/opt/homebrew" + WXWIDGETS="$HOME/wxWidgets/dest" + if [ $ARCH = "x86_64" ] + then + HOMEBREW="/usr/local" + WXWIDGETS="$HOME/wxWidgets_x86/dest" + fi + + if ! [ -e $WXWIDGETS ] + then + echo "wxWidgets not found at $WXWIDGETS" + exit 5 + fi + + if ! [ -e $HOMEBREW ] + then + echo "Homebrew not found at $HOMEBREW" + exit 5 + fi + + arch -$ARCH ./configure --enable-embedded-cryptopp --enable-embedded-zstd --enable-clientupdate --with-openssl=$HOMEBREW --with-wx-prefix=$WXWIDGETS CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE -arch $ARCH" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE -arch $ARCH" CPPFLAGS="-mmacosx-version-min=10.10 -I$HOMEBREW/include -arch $ARCH" LDFLAGS="-mmacosx-version-min=10.10 -L$HOMEBREW/lib -arch $ARCH" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10 -arch $ARCH" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" +} + mkdir -p osx-pkg/Library/LaunchDaemons cp osx_installer/daemon.plist osx-pkg/Library/LaunchDaemons/org.urbackup.client.plist mkdir -p osx-pkg/Library/LaunchAgents cp osx_installer/agent.plist osx-pkg/Library/LaunchAgents/org.urbackup.client.plist if !($development); then - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + config arm64 else - ./configure --enable-embedded-cryptopp --enable-clientupdate CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" LDFLAGS="-mmacosx-version-min=10.10" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + ./configure --enable-embedded-cryptopp --enable-embedded-zstd --enable-clientupdate --with-openssl=/opt/homebrew CXXFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CFLAGS="-mmacosx-version-min=10.10 -DDEBUG -DURB_WITH_CLIENTUPDATE -O0 -g" CPPFLAGS='-mmacosx-version-min=10.10 -I/opt/homebrew/include' LDFLAGS="-mmacosx-version-min=10.10 -L/opt/homebrew/lib" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" fi make clean -make -j5 +make -j10 make install DESTDIR=$PWD/osx-pkg2 + +if !($development); then + config x86_64 + make clean + make -j10 + make install DESTDIR=$PWD/osx-pkg_x86 +fi + +for i in $(ls "osx-pkg_x86/Applications/UrBackup Client.app/Contents/MacOS/bin/") +do + lipo -create "osx-pkg_x86/Applications/UrBackup Client.app/Contents/MacOS/bin/$i" "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/$i" -output "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/$i.new" + mv "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/$i.new" "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/$i" +done + mkdir -p "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin" mkdir -p "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS" mkdir -p "osx-pkg2/Applications/UrBackup Client.app/Contents/Resources" @@ -103,46 +135,14 @@ if ($development); then fi gsed -i 's/\$git_rev\$/'"$GIT_REV"'/g' "osx-pkg2/Applications/UrBackup Client.app/Contents/Info.plist" - -function notarization_info { - echo "$UPLOAD_INFO_PLIST" > tmp.plist - xcrun altool --notarization-info `/usr/libexec/PlistBuddy -c "Print :notarization-upload:RequestUUID" tmp.plist` -u "$AC_USERNAME" -p "@env:AC_PASSWORD" --output-format xml -} - -function wait_for_notarization { - echo "Waiting for notarization to finish..." - sleep 30 - while true; do - REQUEST_INFO_PLIST=$(notarization_info || true) - echo "$REQUEST_INFO_PLIST" > tmp.plist - if [ "x$(/usr/libexec/PlistBuddy -c 'Print :product-errors:0:code' tmp.plist)" = x1519 ]; then - sleep 30 - continue - fi - if [ "x$(/usr/libexec/PlistBuddy -c 'Print :notarization-info:Status' tmp.plist)" != "xin progress" ]; then - echo "Notarization finished" - break - fi - sleep 60 - done - -} - -function notarize_int { - xcrun altool --notarize-app --primary-bundle-id "org.urbackup.client.frontend" -u "$AC_USERNAME" -p "@env:AC_PASSWORD" -t osx -f "$1" --output-format xml -} - function notarize { echo "Sending $1 to notarization..." - UPLOAD_INFO_PLIST=$(notarize_int $1) - echo $UPLOAD_INFO_PLIST - wait_for_notarization + xcrun notarytool submit "$1" --keychain-profile "notary-profile" --wait } if !($development); then echo "Signing code..." - security unlock-keychain -p foobar /Users/martin/Library/Keychains/dev.keychain - codesign --deep --keychain dev.keychain --sign 3Y4WACCWC5 --timestamp --options runtime osx-pkg2/Applications/UrBackup\ Client.app + codesign --deep --sign 3Y4WACCWC5 --timestamp --options runtime osx-pkg2/Applications/UrBackup\ Client.app ditto -c -k --keepParent "osx-pkg2/Applications" "urbackup-client.zip" notarize "urbackup-client.zip" xcrun stapler staple "osx-pkg2/Applications/UrBackup Client.app" @@ -155,7 +155,7 @@ pkgbuild --root "osx-pkg2/Applications/UrBackup Client.app" --identifier "org.ur productbuild --distribution osx_installer/distribution.xml --resources osx_installer/resources --package-path pkg1 --version "$VERSION_SHORT_NUM" final.pkg if !($development); then - productsign --keychain /Users/martin/Library/Keychains/dev.keychain --sign 3Y4WACCWC5 final.pkg final-signed.pkg + productsign --sign 3Y4WACCWC5 final.pkg final-signed.pkg notarize final-signed.pkg xcrun stapler staple final-signed.pkg From fca650eaac42add620a8ff2fd7b72005849a29ce Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 28 Jan 2026 15:49:24 +0100 Subject: [PATCH 398/469] Add macOS snapshot support --- create_osx_installer.sh | 16 ++- linux_snapshot/apfs_create_snapshot | 40 ++++++++ linux_snapshot/apfs_remove_snapshot | 15 +++ urbackupclient/client.cpp | 149 ++++++++++++++++------------ 4 files changed, 150 insertions(+), 70 deletions(-) create mode 100755 linux_snapshot/apfs_create_snapshot create mode 100755 linux_snapshot/apfs_remove_snapshot diff --git a/create_osx_installer.sh b/create_osx_installer.sh index 982993cb5..2fbd84753 100755 --- a/create_osx_installer.sh +++ b/create_osx_installer.sh @@ -30,11 +30,11 @@ rm -R osx-pkg_x86 || true function config() { ARCH=$1 echo "Configuring for arch $ARCH..." - HOMEBREW="/opt/homebrew" + OPENSSL="$HOME/openssl" WXWIDGETS="$HOME/wxWidgets/dest" if [ $ARCH = "x86_64" ] then - HOMEBREW="/usr/local" + OPENSSL="$HOME/openssl_x86" WXWIDGETS="$HOME/wxWidgets_x86/dest" fi @@ -44,13 +44,13 @@ function config() { exit 5 fi - if ! [ -e $HOMEBREW ] + if ! [ -e $OPENSSL ] then - echo "Homebrew not found at $HOMEBREW" + echo "OpenSSL not found at $OPENSSL" exit 5 fi - arch -$ARCH ./configure --enable-embedded-cryptopp --enable-embedded-zstd --enable-clientupdate --with-openssl=$HOMEBREW --with-wx-prefix=$WXWIDGETS CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE -arch $ARCH" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE -arch $ARCH" CPPFLAGS="-mmacosx-version-min=10.10 -I$HOMEBREW/include -arch $ARCH" LDFLAGS="-mmacosx-version-min=10.10 -L$HOMEBREW/lib -arch $ARCH" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10 -arch $ARCH" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" + arch -$ARCH ./configure --enable-embedded-cryptopp --enable-embedded-zstd --enable-clientupdate --with-openssl=$OPENSSL --with-wx-prefix=$WXWIDGETS CXXFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE -arch $ARCH" CFLAGS="-mmacosx-version-min=10.10 -DNDEBUG -DURB_WITH_CLIENTUPDATE -arch $ARCH" CPPFLAGS="-mmacosx-version-min=10.10 -I$OPENSSL/include -arch $ARCH -DWITH_OPENSSL" LDFLAGS="-L$OPENSSL -mmacosx-version-min=10.10 -arch $ARCH" OBJCFLAGS="-mmacosx-version-min=10.10" OBJCXXFLAGS="-mmacosx-version-min=10.10 -arch $ARCH" --prefix="/Applications/UrBackup Client.app/Contents/MacOS" --sysconfdir="/Library/Application Support/UrBackup Client/etc" --localstatedir="/Library/Application Support/UrBackup Client/var" } mkdir -p osx-pkg/Library/LaunchDaemons @@ -92,6 +92,12 @@ mv "osx-pkg2/Library/Application Support" "osx-pkg/Library" rm -R "osx-pkg2/Library" mv "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/bin/urbackupclientgui" "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/" +echo "create_filesystem_snapshot=/Library/Application\ Support/UrBackup\ Client/etc/urbackup/apfs_create_snapshot" > osx-pkg/Library/Application\ Support/UrBackup\ Client/etc/urbackup/snapshot.cfg +echo "remove_filesystem_snapshot=/Library/Application\ Support/UrBackup\ Client/etc/urbackup/apfs_remove_snapshot" >> osx-pkg/Library/Application\ Support/UrBackup\ Client/etc/urbackup/snapshot.cfg + +cp linux_snapshot/apfs_create_snapshot osx-pkg/Library/Application\ Support/UrBackup\ Client/etc/urbackup/apfs_create_snapshot +cp linux_snapshot/apfs_remove_snapshot osx-pkg/Library/Application\ Support/UrBackup\ Client/etc/urbackup/apfs_remove_snapshot + if !($development); then strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/urbackupclientgui" strip "osx-pkg2/Applications/UrBackup Client.app/Contents/MacOS/sbin/urbackupclientbackend" diff --git a/linux_snapshot/apfs_create_snapshot b/linux_snapshot/apfs_create_snapshot new file mode 100755 index 000000000..bf81b104a --- /dev/null +++ b/linux_snapshot/apfs_create_snapshot @@ -0,0 +1,40 @@ +#!/bin/sh + +set -e + +SNAP_ID=$1 +SNAP_MOUNTPOINT="$2" +SNAP_DEST=/Volumes/urbackup_snaps/$SNAP_ID + +if [ "x$SNAP_MOUNTPOINT" != "x/" ] +then + echo "APFS snapshots can only be created for root mountpoint /" + exit 1 +fi + +# / seems to be the Data volume +SNAP_SEARCH_MOUNTPOINT="/System/Volumes/Data" + +DEVICE=$(df -P | egrep " ${SNAP_SEARCH_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f1) + +echo "Snapshotting device $DEVICE via apfs..." + +SNAP_DATE=$(tmutil localsnapshot | grep "date:" | sed 's/.*: //') +if [ "x$SNAP_DATE" = "x" ]; then + echo "Failed to create snapshot" + exit 1 +fi + +FULL_SNAP_NAME=$(tmutil listlocalsnapshots $SNAP_MOUNTPOINT | grep "$SNAP_DATE" | tail -n 1) +if [ "x$FULL_SNAP_NAME" = "x" ]; then + echo "Failed to get full snapshot name" + exit 1 +fi + +mkdir -p $SNAP_DEST +echo "$SNAP_DATE" > "$SNAP_DEST-snap-date" +mount_apfs -o rdonly,noatime,nobrowse -s "$FULL_SNAP_NAME" "$DEVICE" "$SNAP_DEST" + +echo "Snapshot mounted at $SNAP_DEST" + +echo "SNAPSHOT=$SNAP_DEST" \ No newline at end of file diff --git a/linux_snapshot/apfs_remove_snapshot b/linux_snapshot/apfs_remove_snapshot new file mode 100755 index 000000000..1f41f2e5d --- /dev/null +++ b/linux_snapshot/apfs_remove_snapshot @@ -0,0 +1,15 @@ +#!/bin/sh + +set -e + +SNAP_MOUNTPOINT="$2" + +umount "$SNAP_MOUNTPOINT" +rmdir "$SNAP_MOUNTPOINT" + +if [ -e "$SNAP_MOUNTPOINT-snap-date" ] +then + SNAP_DATE=$(cat "$SNAP_MOUNTPOINT-snap-date") + tmutil deletelocalsnapshots "$SNAP_DATE" + rm "$SNAP_MOUNTPOINT-snap-date" +fi \ No newline at end of file diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 9aac98ca5..ac44b08b5 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -51,6 +51,9 @@ #include #include #include +#include +#include +#include #endif //For truncating files @@ -405,7 +408,23 @@ namespace std::string getFolderMount(const std::string& path) { #ifndef HAVE_MNTENT_H +#ifdef __APPLE__ + int count; + struct statfs *mntbuf; + count = getmntinfo(&mntbuf, MNT_NOWAIT); + std::string maxmount; + for (int i = 0; i < count; i++) { + std::string mountPoint = mntbuf[i].f_mntonname; + if(path.find(mountPoint)==0 && + mountPoint.size()>maxmount.size()) + { + maxmount = mountPoint; + } + } + return maxmount; +#else return std::string(); +#endif #else FILE *aFile; @@ -6748,30 +6767,30 @@ bool IndexThread::punchHoleOrZero(IFile* f, int64 pos, const char* zero_buf, ch } return true; -} - -void IndexThread::run_sc_refs_cleanup() -{ - bool has_cleanup = false; - bool retry_all = true; - while(retry_all) - { - retry_all = false; - for (size_t i = 0; i < sc_refs.size(); ++i) - { - if (sc_refs[i]->cleanup) - { - has_cleanup = true; - - bool in_use = false; - - bool found_ref = false; - - starttoken.clear(); - - SCRef* curr = sc_refs[i]; - - for (size_t k = 0; k < curr->starttokens.size(); ++k) +} + +void IndexThread::run_sc_refs_cleanup() +{ + bool has_cleanup = false; + bool retry_all = true; + while(retry_all) + { + retry_all = false; + for (size_t i = 0; i < sc_refs.size(); ++i) + { + if (sc_refs[i]->cleanup) + { + has_cleanup = true; + + bool in_use = false; + + bool found_ref = false; + + starttoken.clear(); + + SCRef* curr = sc_refs[i]; + + for (size_t k = 0; k < curr->starttokens.size(); ++k) { starttoken = curr->starttokens[k]; @@ -6831,21 +6850,21 @@ void IndexThread::run_sc_refs_cleanup() break; } } - } - } - } - - if (!found_ref) - { - VSSLog("Reference not found. Iterating over all start tokens and share names for deletion", LL_INFO); - for (size_t k = 0; k < curr->starttokens.size() && !retry_all; ++k) + } + } + } + + if (!found_ref) + { + VSSLog("Reference not found. Iterating over all start tokens and share names for deletion", LL_INFO); + for (size_t k = 0; k < curr->starttokens.size() && !retry_all; ++k) { - starttoken = curr->starttokens[k]; - SCDirs scd; - scd.running = true; - - for (size_t j = 0; j < curr->sharenames.size() && !retry_all; ++j) - { + starttoken = curr->starttokens[k]; + SCDirs scd; + scd.running = true; + + for (size_t j = 0; j < curr->sharenames.size() && !retry_all; ++j) + { scd.dir = curr->sharenames[j]; scd.starttime = Server->getTimeSeconds(); if (sc_refs[i]->for_imagebackup) @@ -6868,33 +6887,33 @@ void IndexThread::run_sc_refs_cleanup() } scd.ref = sc_refs[i]; - - size_t orig_size = sc_refs.size(); - - release_shadowcopy(&scd, false, -1, &scd); - - if (sc_refs.size() != orig_size) - { - retry_all = true; - break; - } - } - } - } - - if (in_use) - continue; - - retry_all = true; - break; - } - } - } - - if (!has_cleanup) - { - sc_refs_cleanup = false; - } + + size_t orig_size = sc_refs.size(); + + release_shadowcopy(&scd, false, -1, &scd); + + if (sc_refs.size() != orig_size) + { + retry_all = true; + break; + } + } + } + } + + if (in_use) + continue; + + retry_all = true; + break; + } + } + } + + if (!has_cleanup) + { + sc_refs_cleanup = false; + } } bool IndexThread::finishCbt(std::string volume, int shadow_id, std::string snap_volume, From 4a65af3358fcd1559883faf6127f03e85b14f51a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Wed, 28 Jan 2026 16:16:35 +0100 Subject: [PATCH 399/469] Log about and skip cloud files --- urbackupclient/client.cpp | 21 ++++++++++++++++++--- urbackupclient/client.h | 2 ++ urbackupcommon/os_functions.h | 3 ++- urbackupcommon/os_functions_lin.cpp | 4 ++++ 4 files changed, 26 insertions(+), 4 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index ac44b08b5..81c12ab12 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -593,7 +593,8 @@ std::string add_trailing_slash(const std::string &strDirName) IndexThread::IndexThread(void) : index_error(false), last_filebackup_filetime(0), index_group(-1), with_scripts(false), volumes_cache(NULL), phash_queue(NULL), - index_backup_dirs_optional(false), sc_refs_cleanup(false) + index_backup_dirs_optional(false), sc_refs_cleanup(false), + dataless_warning_logged(false) { if(filelist_mutex==NULL) filelist_mutex=Server->createMutex(); @@ -1634,6 +1635,7 @@ IndexThread::IndexErrorInfo IndexThread::indexDirs(bool full_backup, bool simult last_tmp_update_time=Server->getTimeMS(); index_error=false; + dataless_warning_logged=false; std::string filelist_dest_fn = "urbackup/data/filelist.ub"; if (index_group != c_group_default) @@ -8715,21 +8717,34 @@ void IndexThread::removeUnconfirmedSymlinkDirs(size_t off) void IndexThread::filterEncryptedFiles(const std::string & dir, const std::string& orig_dir, std::vector& files) { bool has_encrypted = false; + bool has_dataless = false; for (size_t i = 0; i < files.size(); ++i) { if (files[i].isencrypted) { has_encrypted = true; } + if (files[i].isdataless) + { + has_dataless = true; + } } - if (has_encrypted) + if (has_encrypted || has_dataless) { std::vector new_files; for (size_t i = 0; i < files.size(); ++i) { - if (files[i].isencrypted + if (files[i].isdataless) + { + if(!dataless_warning_logged) + { + dataless_warning_logged=true; + VSSLog("Not backing up cloud storage files (file \"" + orig_dir + os_file_sep() + files[i].name + "\" is e.g. on iCloud or OneDrive -- not informing about other files)", LL_INFO); + } + } + else if (files[i].isencrypted && files[i].isdir) { bool has_error = false; diff --git a/urbackupclient/client.h b/urbackupclient/client.h index 9e27a27b6..0f9d71362 100644 --- a/urbackupclient/client.h +++ b/urbackupclient/client.h @@ -891,6 +891,8 @@ class IndexThread : public IThread, public IFileServ::IReadErrorCallback, public std::vector phash_queue_buffer; int64 file_id; + bool dataless_warning_logged; + struct SResult { ICondition* cond; diff --git a/urbackupcommon/os_functions.h b/urbackupcommon/os_functions.h index 664aead16..7f657012d 100644 --- a/urbackupcommon/os_functions.h +++ b/urbackupcommon/os_functions.h @@ -15,7 +15,7 @@ struct SFile usn(0), created(0), accessed(0), isdir(false), issym(false), isspecialf(false), isencrypted(false), - nlinks(0) + isdataless(false), nlinks(0) { } @@ -30,6 +30,7 @@ struct SFile bool issym; bool isspecialf; bool isencrypted; + bool isdataless; size_t nlinks; bool operator<(const SFile &other) const diff --git a/urbackupcommon/os_functions_lin.cpp b/urbackupcommon/os_functions_lin.cpp index 8ba36ef65..f57223435 100644 --- a/urbackupcommon/os_functions_lin.cpp +++ b/urbackupcommon/os_functions_lin.cpp @@ -165,6 +165,10 @@ std::vector getFiles(const std::string &path, bool *has_error, bool ignor { continue; } + +#ifdef __APPLE__ + f.isdataless = (f_info.st_flags & SF_DATALESS) > 0; +#endif if(S_ISLNK(f_info.st_mode)) { From 4bf6ce4d7a21ceaedfb3df4ddd80da9cbc42517e Mon Sep 17 00:00:00 2001 From: Martin Date: Wed, 18 Dec 2024 23:27:12 +0100 Subject: [PATCH 400/469] Potential fix for patching issue in combination with sparse extents (cherry picked from commit 04b678a8aff5e915b0577474af3ef7d6ff91dc86) (cherry picked from commit 070de357b9e846a6923649852e2c4726eb9077a0) --- urbackupserver/ChunkPatcher.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/urbackupserver/ChunkPatcher.cpp b/urbackupserver/ChunkPatcher.cpp index 73e3bc0df..297d9f0b3 100644 --- a/urbackupserver/ChunkPatcher.cpp +++ b/urbackupserver/ChunkPatcher.cpp @@ -202,7 +202,7 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ } next_header.patch_off=-1; } - else if(file_posnextExtent(); } - if (file_pos + tr>filesize) + if (file_posfilesize) { tr = static_cast(filesize - file_pos); } @@ -268,6 +268,14 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ } } + if(!was_sparse && (file_pos>=size || file_pos>=filesize)) + { + Server->Log("Patch corrupt. file_pos="+convert(file_pos)+" next_header.patch_off="+convert(next_header.patch_off)+" next_header.patch_size="+convert(next_header.patch_size)+" tr="+convert(tr)+" size="+convert(size)+" filesize="+convert(filesize), LL_ERROR); + assert(false); + return false; + } + + while(!was_sparse && tr>0 && file_posLog("Patch corrupt. file_pos="+convert(file_pos)+" next_header.patch_off="+convert(next_header.patch_off)+" next_header.patch_size="+convert(next_header.patch_size)+" tr="+convert(tr)+" size="+convert(size)+" filesize="+convert(filesize), LL_ERROR); - assert(false); - return false; - } if(patching_finished) { From d53175052904ae368379f3eee61c0d557c233b33 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 27 Jan 2026 19:47:08 +0100 Subject: [PATCH 401/469] Disable dmsnapshot option if grub does not search for root via UUID (cherry picked from commit 96d8bf5d298d6978652b4ac65d5beea425def0a8) --- install_client_linux.sh | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/install_client_linux.sh b/install_client_linux.sh index 2f8a3d718..b0c6a169a 100755 --- a/install_client_linux.sh +++ b/install_client_linux.sh @@ -531,7 +531,24 @@ then else echo "-dmsetup not present" fi - + + if [ $DMSETUP != no ] + then + GRUBF=/boot/grub/grub.cfg + if [ -e $GRUBF ] + then + if grep "root=UUID=" $GRUBF > /dev/null 2>&1 || grep "root=PARTUUID=" $GRUBF > /dev/null 2>&1 + then + echo "+Grub is searching for boot device via UUID" + else + echo "-Grub not searching for boot device via UUID. Disabling dmsetup snapshot option" + DMSETUP=no + fi + else + echo "-grub.cfg not found in /boot/grub. Disabling dmsetup snapshot option" + DMSETUP=no + fi + fi while true do From aea693cba6a66826eaa4a44f10d4dfbcf0312625 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 27 Jan 2026 19:49:55 +0100 Subject: [PATCH 402/469] Always disable dattobd (cherry picked from commit 58178267f3fc56fec774d9a959f7e70e3b52a84e) --- install_client_linux.sh | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/install_client_linux.sh b/install_client_linux.sh index b0c6a169a..ceb74232c 100755 --- a/install_client_linux.sh +++ b/install_client_linux.sh @@ -468,35 +468,20 @@ then then if grep 'VERSION="' /etc/os-release | grep "LTS" > /dev/null 2>&1 then - echo "+Detected Ubuntu LTS. Dattobd supported" + echo "+Detected Ubuntu LTS" UBUNTU=yes - DATTO=yes fi elif grep 'NAME="Debian' /etc/os-release > /dev/null 2>&1 then if grep 'PRETTY_NAME="' /etc/os-release | grep "/sid" > /dev/null 2>&1 then - echo "+Detected Debian unstable/sid. Dattobd not supported" + echo "+Detected Debian unstable/sid" else - echo "+Detected Debian stable. Dattobd supported" - DATTO=yes + echo "+Detected Debian stable" fi fi fi - - if [ $CENTOS != no ] - then - echo "+Detected EL/RH $CENTOS. Dattobd supported" - DATTO=yes - fi - - if [ $FEDORA != no ] - then - echo "+Detected Fedora. Dattobd supported" - DATTO=yes - fi - if [ $DATTO = no ] then echo "-dattobd not supported on this system" From 68eb239f55dbeb928dd77f6f285bba419abc3546 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 2 Feb 2026 19:00:57 +0100 Subject: [PATCH 403/469] Fill up last patch buffer before writing if possible --- .../fileclient/FileClientChunked.cpp | 22 ++++++++++++++----- urbackupcommon/fileclient/FileClientChunked.h | 2 +- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/urbackupcommon/fileclient/FileClientChunked.cpp b/urbackupcommon/fileclient/FileClientChunked.cpp index 774a7e46f..d518be8ed 100644 --- a/urbackupcommon/fileclient/FileClientChunked.cpp +++ b/urbackupcommon/fileclient/FileClientChunked.cpp @@ -1584,7 +1584,7 @@ void FileClientChunked::writePatch(_i64 pos, unsigned int length, char *buf, boo if(last || patch_buf_pos==c_chunk_size || length==0) { - writePatchInt(patch_buf_start, patch_buf_pos, patch_buf); + writePatchInt(patch_buf_start, patch_buf_pos, patch_buf, last); patch_buf_pos=0; } } @@ -1592,7 +1592,17 @@ void FileClientChunked::writePatch(_i64 pos, unsigned int length, char *buf, boo { if(patch_buf_pos>0) { - writePatchInt(patch_buf_start, patch_buf_pos, patch_buf); + if (buf!=NULL && length>0 && patch_buf_pos < c_chunk_size && pos == patch_buf_start + patch_buf_pos) + { + const unsigned int towrite = (std::min)(c_chunk_size - patch_buf_pos, length); + memcpy(&patch_buf[patch_buf_pos], buf, towrite); + patch_buf_pos += towrite; + length -= towrite; + buf += towrite; + pos += towrite; + } + + writePatchInt(patch_buf_start, patch_buf_pos, patch_buf, last); patch_buf_pos=0; } @@ -1610,7 +1620,7 @@ void FileClientChunked::writePatch(_i64 pos, unsigned int length, char *buf, boo { const unsigned int wchunks = length / c_chunk_size; const unsigned int towrite = wchunks * c_chunk_size; - writePatchInt(pos, towrite, buf); + writePatchInt(pos, towrite, buf, last); const unsigned int wleft = length - towrite; memcpy(&patch_buf[patch_buf_pos], buf + towrite, wleft); @@ -1619,15 +1629,17 @@ void FileClientChunked::writePatch(_i64 pos, unsigned int length, char *buf, boo } else { - writePatchInt(pos, length, buf); + writePatchInt(pos, length, buf, last); } } } } } -void FileClientChunked::writePatchInt(_i64 pos, unsigned int length, char *buf) +void FileClientChunked::writePatchInt(_i64 pos, unsigned int length, char *buf, const bool last) { + assert(pos % c_chunk_size == 0); + assert(last || length % c_chunk_size == 0); const unsigned int plen=sizeof(_i64)+sizeof(unsigned int); char pd[plen]; _i64 pos_tmp = little_endian(pos); diff --git a/urbackupcommon/fileclient/FileClientChunked.h b/urbackupcommon/fileclient/FileClientChunked.h index b22372ac0..bcf262f22 100644 --- a/urbackupcommon/fileclient/FileClientChunked.h +++ b/urbackupcommon/fileclient/FileClientChunked.h @@ -119,7 +119,7 @@ class FileClientChunked void writeFileRepeat(IFile *f, const char *buf, size_t bsize); void writePatch(_i64 pos, unsigned int length, char *buf, bool last); - void writePatchInt(_i64 pos, unsigned int length, char *buf); + void writePatchInt(_i64 pos, unsigned int length, char *buf, const bool last); void writePatchSize(_i64 remote_fs); void invalidateLastPatches(void); From a82ba6d052108a139e2e55f8be00ba4a6628987e Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 30 Jan 2026 01:29:28 +0100 Subject: [PATCH 404/469] Fix assertion on db deletion (cherry picked from commit 0832ce3ea9d471fa19b066833e6957100f32c554) --- Database.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Database.cpp b/Database.cpp index fc102be19..385a1669a 100644 --- a/Database.cpp +++ b/Database.cpp @@ -105,6 +105,9 @@ static void unlock_notify_cb(void **apArg, int nArg) CDatabase::~CDatabase() { +#ifndef NDEBUG + db_thread_id = Server->getThreadID(); +#endif destroyAllQueries(); for(std::map::iterator iter=prepared_queries.begin();iter!=prepared_queries.end();++iter) { From 1c567c110ccfe9d5a0a92d54c2edbb11093b2056 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 1 Feb 2026 14:00:02 +0100 Subject: [PATCH 405/469] Ask if snapshot config should be kept (cherry picked from commit 8531c9ef7db2cef77eefd8c118fac98e08d531a3) --- install_client_linux.sh | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/install_client_linux.sh b/install_client_linux.sh index ceb74232c..3c35c19ca 100755 --- a/install_client_linux.sh +++ b/install_client_linux.sh @@ -428,9 +428,17 @@ fi if [ $SILENT = no ] then - if [ -e $PREFIX/etc/urbackup/snapshot.cfg ] || [ -e $PREFIX/etc/urbackup/no_filesystem_snapshot ] + if [ -e "$PREFIX/etc/urbackup/snapshot.cfg" ] || [ -e "$PREFIX/etc/urbackup/no_filesystem_snapshot" ] then - exit 0 + echo "Snapshots already configured. Keep configuration? [Y/n]" + read yn + if [ "x$yn" != "xn" ] + then + exit 0 + else + ! [ -e "$PREFIX/etc/urbackup/snapshot.cfg" ] || rm -f "$PREFIX/etc/urbackup/snapshot.cfg" + ! [ -e "$PREFIX/etc/urbackup/no_filesystem_snapshot" ] || rm -f "$PREFIX/etc/urbackup/no_filesystem_snapshot" + fi fi CENTOS=no From 5ea96d4765fa079e494d70e99ed7d1a0f3859155 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 5 Feb 2026 21:17:29 +0100 Subject: [PATCH 406/469] Reduce Linux memory fragmentation by reducing default number of arenas --- configure.ac_client | 2 +- urbackupclient/cmdline_preprocessor.cpp | 24 ++++++++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/configure.ac_client b/configure.ac_client index af381ab2e..8c4bd525e 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -68,7 +68,7 @@ AC_LANG([C++]) # Checks for header files. AC_HEADER_STDC -AC_CHECK_HEADERS([pthread.h arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h sys/socket.h sys/time.h unistd.h mntent.h spawn.h linux/fiemap.h sys/random.h linux/fs.h]) +AC_CHECK_HEADERS([pthread.h arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h sys/socket.h sys/time.h unistd.h mntent.h spawn.h linux/fiemap.h sys/random.h linux/fs.h, malloc.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL diff --git a/urbackupclient/cmdline_preprocessor.cpp b/urbackupclient/cmdline_preprocessor.cpp index 534ba44e3..16a04918d 100644 --- a/urbackupclient/cmdline_preprocessor.cpp +++ b/urbackupclient/cmdline_preprocessor.cpp @@ -31,6 +31,10 @@ #define DATADIR "" #endif +#ifdef HAVE_MALLOC_H +#include +#endif + const std::string cmdline_version = PACKAGE_VERSION; void show_version() @@ -197,6 +201,24 @@ void read_config_file(std::string fn, std::vector& real_args) } #endif +void tune_glibc_malloc() +{ +#if defined(HAVE_MALLOC_H) && defined(M_ARENA_MAX) + if(getenv("MALLOC_ARENA_MAX") != nullptr || + getenv("GLIBC_TUNABLES") != nullptr || + getenv("MALLOC_MMAP_THRESHOLD_") != nullptr) + { + return; + } + + // Limits memory fragmentation at the cost of performance + mallopt(M_ARENA_MAX, 2); +#if defined(M_MMAP_THRESHOLD) + mallopt(M_MMAP_THRESHOLD, 128*1024); +#endif +#endif +} + #ifndef _WIN32 int restoreclient_main(int argc, char* argv[]); #endif @@ -457,6 +479,8 @@ int restoreclient_main(int argc, char* argv[]) } } + tune_glibc_malloc(); + try { TCLAP::CmdLine cmd("Run UrBackup Restore Client", ' ', cmdline_version); From 490cb8b056a36cd144f5bb7c71e77b5538b60fa4 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 5 Feb 2026 22:52:42 +0100 Subject: [PATCH 407/469] Remove stray comma --- configure.ac_client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac_client b/configure.ac_client index 8c4bd525e..f27cda3e3 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -68,7 +68,7 @@ AC_LANG([C++]) # Checks for header files. AC_HEADER_STDC -AC_CHECK_HEADERS([pthread.h arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h sys/socket.h sys/time.h unistd.h mntent.h spawn.h linux/fiemap.h sys/random.h linux/fs.h, malloc.h]) +AC_CHECK_HEADERS([pthread.h arpa/inet.h fcntl.h netdb.h netinet/in.h stdlib.h sys/socket.h sys/time.h unistd.h mntent.h spawn.h linux/fiemap.h sys/random.h linux/fs.h malloc.h]) # Checks for typedefs, structures, and compiler characteristics. AC_HEADER_STDBOOL From c16bc5d39c2bcfd8a4a9fa261dbe5f1d67cab338 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 5 Feb 2026 22:56:43 +0100 Subject: [PATCH 408/469] Tune in correct main --- urbackupclient/cmdline_preprocessor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/urbackupclient/cmdline_preprocessor.cpp b/urbackupclient/cmdline_preprocessor.cpp index 16a04918d..c57f1db25 100644 --- a/urbackupclient/cmdline_preprocessor.cpp +++ b/urbackupclient/cmdline_preprocessor.cpp @@ -249,6 +249,8 @@ int main(int argc, char* argv[]) } } + tune_glibc_malloc(); + if (argc > 0 && std::string(argv[1]) == "--internal") { @@ -479,8 +481,6 @@ int restoreclient_main(int argc, char* argv[]) } } - tune_glibc_malloc(); - try { TCLAP::CmdLine cmd("Run UrBackup Restore Client", ' ', cmdline_version); From 8bd5b0532417f3058b41f4ebd85ee4fd5e409696 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 6 Feb 2026 12:20:30 +0100 Subject: [PATCH 409/469] Store arm64 pdbs --- pdb_dirs_client.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pdb_dirs_client.txt b/pdb_dirs_client.txt index 1523064e4..a33bdf8d9 100644 --- a/pdb_dirs_client.txt +++ b/pdb_dirs_client.txt @@ -3,5 +3,7 @@ Release Release Server 2003 Release Service Release WinXP +ARM64 client\x64 -client\Release \ No newline at end of file +client\Release +client\ARM64 \ No newline at end of file From 4cde753b9bebb275981cb6720811755a26bea542 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 6 Feb 2026 13:37:28 +0100 Subject: [PATCH 410/469] Fix crash on service shutdown (cherry picked from commit 7b6b51e20d39fa2b0374cf0e92d184f6d30cbe71) --- urbackupclient/ClientServiceCMD.cpp | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index c7ad91d50..7832353fd 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1183,7 +1183,6 @@ namespace { std::mutex prevent_sleep_mutex; int64 last_prevent_sleep_time = 0; - std::thread prevent_sleep_thread; void prevent_thread_func() { @@ -1239,10 +1238,8 @@ namespace if (last_prevent_sleep_time == 0) { - if (prevent_sleep_thread.joinable()) - prevent_sleep_thread.join(); - - prevent_sleep_thread = std::thread(prevent_thread_func); + std::thread prevent_sleep_thread = std::thread(prevent_thread_func); + prevent_sleep_thread.detach(); } last_prevent_sleep_time = Server->getTimeMS(); From a0345007c2aa7eb0b9e3a52659e09b05c098b7d1 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 6 Feb 2026 13:41:25 +0100 Subject: [PATCH 411/469] Increment version (cherry picked from commit 906a908cebff02298e41c157a02c29fac7f9fd64) --- configure.ac_client | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac_client b/configure.ac_client index f27cda3e3..a8145fb6f 100644 --- a/configure.ac_client +++ b/configure.ac_client @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-client], [2.5.28.BUILDID], [martin@urbackup.org]) +AC_INIT([urbackup-client], [2.5.29.BUILDID], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CANONICAL_SYSTEM From c03c74e1463475b3b06c4ea7e1ffecb9faf5b588 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 6 Feb 2026 17:18:17 +0100 Subject: [PATCH 412/469] Use posix_spawn for posbackup hook if possible --- urbackupclient/client.cpp | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 81c12ab12..0f31e5a47 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -90,6 +90,10 @@ #include "../urbackupcommon/android_popen.h" #endif +#if defined(HAVE_SPAWN_H) +#include +#endif + volatile bool IdleCheckerThread::idle=false; volatile bool IdleCheckerThread::pause=false; @@ -4183,26 +4187,33 @@ void IndexThread::execute_postbackup_hook(std::string scriptname, int group, con CloseHandle(pi.hThread); } #else + std::string fullname = std::string(SYSCONFDIR "/urbackup/") + scriptname; + std::string group_str = convert(group); + char* const argv[]={ const_cast(fullname.c_str()), + const_cast(group_str.c_str()), const_cast(clientsubname.c_str()), NULL }; + pid_t pid1; pid1 = fork(); if( pid1==0 ) { setsid(); + + int rc = 0; +#ifdef HAVE_SPAWN_H + const char* envp[] = {NULL}; + pid_t child_pid; + rc = posix_spawn(&child_pid, fullname.c_str(), NULL, NULL, const_cast(argv), const_cast(envp)); +#else // HAVE_SPAWN_H pid_t pid2; pid2 = fork(); if(pid2==0) { - std::string fullname = std::string(SYSCONFDIR "/urbackup/") + scriptname; - std::string group_str = convert(group); - char* const argv[]={ const_cast(fullname.c_str()), - const_cast(group_str.c_str()), const_cast(clientsubname.c_str()), NULL }; - execv(const_cast(fullname.c_str()), argv); - exit(1); - } - else - { - exit(1); - } + rc = execv(const_cast(fullname.c_str()), argv); + if(rc==-1) + rc = errno; + } +#endif // HAVE_SPAWN_H + _exit(rc); } else { From c9df052698026f89b020c3228f1510454085e0d1 Mon Sep 17 00:00:00 2001 From: Martin Date: Fri, 6 Feb 2026 20:02:07 +0100 Subject: [PATCH 413/469] Update vcpkg features --- vcpkg.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vcpkg.json b/vcpkg.json index a65fac6e6..0e381d806 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -20,8 +20,8 @@ "default-features": false, "features": [ "non-http", - "schannel", - "winldap" + "ssl", + "ldap" ] } ] From 43daa0edc50a4b6b399efac19c47b74a21e12558 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 25 Sep 2025 01:31:23 +0200 Subject: [PATCH 414/469] Fix linking for curl (cherry picked from commit 187db45f73942d58e8b695245aa630068bdb50b9) --- urlplugin/urlplugin.vcxproj | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/urlplugin/urlplugin.vcxproj b/urlplugin/urlplugin.vcxproj index d0b650964..e53948312 100644 --- a/urlplugin/urlplugin.vcxproj +++ b/urlplugin/urlplugin.vcxproj @@ -144,7 +144,7 @@ true - Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Iphlpapi.lib;Secur32.lib;Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -161,7 +161,7 @@ true - Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Iphlpapi.lib;Secur32.lib;Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -178,7 +178,7 @@ true - Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Iphlpapi.lib;Secur32.lib;Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -199,7 +199,7 @@ true - Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Iphlpapi.lib;Secur32.lib;Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -221,7 +221,7 @@ true true $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs - Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Iphlpapi.lib;Secur32.lib;Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) @@ -241,7 +241,7 @@ true true $(ZlibLibDir);$(CurlLibDir);$(SolutionDir)/deps/libs - Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) + Iphlpapi.lib;Secur32.lib;Crypt32.lib;ws2_32.lib;wldap32.lib;normaliz.lib;%(AdditionalDependencies) From e01199e7ebb3ea08e37653925aab2168c17d6a32 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 7 Feb 2026 12:15:13 +0100 Subject: [PATCH 415/469] Improve macOS snapshot scripts --- linux_snapshot/apfs_create_snapshot | 10 ++++---- linux_snapshot/apfs_remove_snapshot | 37 +++++++++++++++++++++++------ 2 files changed, 35 insertions(+), 12 deletions(-) diff --git a/linux_snapshot/apfs_create_snapshot b/linux_snapshot/apfs_create_snapshot index bf81b104a..895a65512 100755 --- a/linux_snapshot/apfs_create_snapshot +++ b/linux_snapshot/apfs_create_snapshot @@ -15,23 +15,23 @@ fi # / seems to be the Data volume SNAP_SEARCH_MOUNTPOINT="/System/Volumes/Data" -DEVICE=$(df -P | egrep " ${SNAP_SEARCH_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f1) +DEVICE=$(df -P | grep -E " ${SNAP_SEARCH_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f1) echo "Snapshotting device $DEVICE via apfs..." SNAP_DATE=$(tmutil localsnapshot | grep "date:" | sed 's/.*: //') -if [ "x$SNAP_DATE" = "x" ]; then +if [ "y$SNAP_DATE" = "y" ]; then echo "Failed to create snapshot" exit 1 fi -FULL_SNAP_NAME=$(tmutil listlocalsnapshots $SNAP_MOUNTPOINT | grep "$SNAP_DATE" | tail -n 1) -if [ "x$FULL_SNAP_NAME" = "x" ]; then +FULL_SNAP_NAME=$(tmutil listlocalsnapshots "$SNAP_MOUNTPOINT" | grep "$SNAP_DATE" | tail -n 1) +if [ "y$FULL_SNAP_NAME" = "y" ]; then echo "Failed to get full snapshot name" exit 1 fi -mkdir -p $SNAP_DEST +mkdir -p "$SNAP_DEST" echo "$SNAP_DATE" > "$SNAP_DEST-snap-date" mount_apfs -o rdonly,noatime,nobrowse -s "$FULL_SNAP_NAME" "$DEVICE" "$SNAP_DEST" diff --git a/linux_snapshot/apfs_remove_snapshot b/linux_snapshot/apfs_remove_snapshot index 1f41f2e5d..32ea930ed 100755 --- a/linux_snapshot/apfs_remove_snapshot +++ b/linux_snapshot/apfs_remove_snapshot @@ -4,12 +4,35 @@ set -e SNAP_MOUNTPOINT="$2" -umount "$SNAP_MOUNTPOINT" -rmdir "$SNAP_MOUNTPOINT" +remove_snap() { + ! [ -e "$SNAP_MOUNTPOINT" ] || rmdir "$SNAP_MOUNTPOINT" -if [ -e "$SNAP_MOUNTPOINT-snap-date" ] + if [ -e "$SNAP_MOUNTPOINT-snap-date" ] + then + SNAP_DATE=$(cat "$SNAP_MOUNTPOINT-snap-date") + tmutil deletelocalsnapshots "$SNAP_DATE" || true + rm "$SNAP_MOUNTPOINT-snap-date" + fi +} + +if [ -e "$SNAP_MOUNTPOINT" ] then - SNAP_DATE=$(cat "$SNAP_MOUNTPOINT-snap-date") - tmutil deletelocalsnapshots "$SNAP_DATE" - rm "$SNAP_MOUNTPOINT-snap-date" -fi \ No newline at end of file + if ! err=$(umount "$SNAP_MOUNTPOINT" 2>&1) + then + echo "Failed to unmount snapshot at $SNAP_MOUNTPOINT: $err" + + if echo "$err" | grep -q "not currently mounted" + then + echo "Snapshot already unmounted. Removing snapshot directory..." + remove_snap + exit 0 + fi + + lsof | grep "$SNAP_MOUNTPOINT" || true + + sleep 10 + umount "$SNAP_MOUNTPOINT" + fi +fi + +remove_snap \ No newline at end of file From 6abc598c25dc23b5894df97058cbbf51d4d0c7de Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 8 Feb 2026 15:02:48 +0100 Subject: [PATCH 416/469] Add samba redirector --- urbackupclient/ClientService.cpp | 60 +++++++---- urbackupclient/ClientService.h | 18 ++-- urbackupclient/ClientServiceCMD.cpp | 2 +- urbackupclient/RestoreFiles.cpp | 2 +- urbackupclient/SambaService.cpp | 101 ++++++++++++++++++ urbackupclient/SambaService.h | 38 +++++++ urbackupclient/dllmain.cpp | 10 ++ urbackupclient/urbackupclient.vcxproj | 2 + urbackupclient/urbackupclient.vcxproj.filters | 6 ++ 9 files changed, 209 insertions(+), 30 deletions(-) create mode 100644 urbackupclient/SambaService.cpp create mode 100644 urbackupclient/SambaService.h diff --git a/urbackupclient/ClientService.cpp b/urbackupclient/ClientService.cpp index c5b3648de..83e5ea071 100644 --- a/urbackupclient/ClientService.cpp +++ b/urbackupclient/ClientService.cpp @@ -95,7 +95,7 @@ std::vector ClientConnector::new_server_idents; bool ClientConnector::end_to_end_file_backup_verification_enabled=false; std::map, ClientConnector::SChallenge> ClientConnector::challenges; bool ClientConnector::has_file_changes = false; -std::vector < ClientConnector::SFilesrvConnection > ClientConnector::fileserv_connections; +std::vector < ClientConnector::SFilesrvConnection > ClientConnector::remote_connections; RestoreOkStatus ClientConnector::restore_ok_status = RestoreOk_None; bool ClientConnector::status_updated= false; RestoreFiles* ClientConnector::restore_files = NULL; @@ -302,7 +302,7 @@ void ClientConnector::Init(THREAD_ID pTID, IPipe *pPipe, const std::string& pEnd tcpstack.setAddChecksum(false); last_update_time=lasttime; endpoint_name = pEndpointName; - make_fileserv=false; + make_conn.store(0, std::memory_order_relaxed); local_backup_running_id = 0; run_other = NULL; idle_timeout = 10000; @@ -542,7 +542,8 @@ bool ClientConnector::Run(IRunOtherCallback* p_run_other) last_channel_ping=Server->getTimeMS(); chan->state = SChannel::EChannelState_Pinging; } - if(make_fileserv + const auto make_conn_local = make_conn.load(std::memory_order_relaxed); + if(make_conn_local && chan->state == SChannel::EChannelState_Idle) { size_t idx=std::string::npos; @@ -557,10 +558,21 @@ bool ClientConnector::Run(IRunOtherCallback* p_run_other) if(idx!=std::string::npos) { - tcpstack.Send(pipe, "FILESERV"); - state=CCSTATE_FILESERV; - fileserv_connections.push_back(SFilesrvConnection(channel_pipes[idx].token, pipe)); + state = CCSTATE_FILESERV; + switch (make_conn_local) + { + case ConnectionTypeFileServ: + tcpstack.Send(pipe, "FILESERV"); + break; + case ConnectionTypeSamba: + tcpstack.Send(pipe, "SAMBA"); + break; + default: + assert(false); + } + + remote_connections.push_back(SFilesrvConnection(channel_pipes[idx].token, pipe)); channel_pipes.erase(channel_pipes.begin()+idx); } @@ -3586,21 +3598,27 @@ void ClientConnector::exit_backup_immediate(int rc) } } -IPipe* ClientConnector::getFileServConnection(const std::string& server_token, unsigned int timeoutms) +IPipe* ClientConnector::getRemoteConnection(const std::string& server_token, const unsigned int timeoutms, const int type) { IScopedLock lock(backup_mutex); - int64 starttime = Server->getTimeMS(); + const int64 starttime = Server->getTimeMS(); + + int64 last_conn_starttime = 0; do { - for(size_t i=0;igetTimeMS() - last_conn_starttime > 1000) { - if(channel_pipes[i].make_fileserv!=NULL && - channel_pipes[i].token==server_token && - !(*channel_pipes[i].make_fileserv)) + for (size_t i = 0; i < channel_pipes.size(); ++i) { - *channel_pipes[i].make_fileserv=true; + if (channel_pipes[i].make_conn != NULL && + (server_token.empty() || channel_pipes[i].token == server_token) && + !channel_pipes[i].make_conn->load(std::memory_order_relaxed)) + { + channel_pipes[i].make_conn->store(type, std::memory_order_relaxed); + last_conn_starttime = Server->getTimeMS(); + } } } @@ -3608,12 +3626,12 @@ IPipe* ClientConnector::getFileServConnection(const std::string& server_token, u Server->wait(100); lock.relock(backup_mutex); - for(size_t i=0;igetTimeMS() - fileserv_connections[i].starttime>60000) + if (Server->getTimeMS() - remote_connections[i].starttime>60000) { - Server->destroy(fileserv_connections[i].pipe); - fileserv_connections.erase(fileserv_connections.begin() + i); + Server->destroy(remote_connections[i].pipe); + remote_connections.erase(remote_connections.begin() + i); } else { diff --git a/urbackupclient/ClientService.h b/urbackupclient/ClientService.h index c86690ab4..8e91bbb46 100644 --- a/urbackupclient/ClientService.h +++ b/urbackupclient/ClientService.h @@ -7,6 +7,7 @@ #include #include +#include class ClientService : public IService { @@ -141,21 +142,21 @@ struct SRestoreToken struct SChannel { SChannel(IPipe *pipe, bool internet_connection, std::string endpoint_name, - std::string token, bool* make_fileserv, std::string server_identity, + std::string token, std::atomic* make_conn, std::string server_identity, int capa, int restore_version, std::string virtual_client) : pipe(pipe), internet_connection(internet_connection), endpoint_name(endpoint_name), - token(token), make_fileserv(make_fileserv), server_identity(server_identity), + token(token), make_conn(make_conn), server_identity(server_identity), state(EChannelState_Idle), capa(capa), restore_version(restore_version), virtual_client(virtual_client) {} SChannel(void) - : pipe(NULL), internet_connection(false), make_fileserv(NULL), + : pipe(NULL), internet_connection(false), make_conn(NULL), state(EChannelState_Idle), capa(0), restore_version(0) {} IPipe *pipe; bool internet_connection; std::string endpoint_name; std::string token; - bool* make_fileserv; + std::atomic* make_conn; std::string last_tokens; std::string server_identity; int restore_version; @@ -185,6 +186,9 @@ class RestoreFiles; const unsigned int x_pingtimeout=180000; +const int ConnectionTypeFileServ = 1; +const int ConnectionTypeSamba = 2; + class ClientConnector : public ICustomClient { friend class ScopedRemoveRunningBackup; @@ -222,7 +226,7 @@ class ClientConnector : public ICustomClient static bool restoreDone(int64 log_id, int64 status_id, int64 restore_id, bool success, const std::string& identity); - static IPipe* getFileServConnection(const std::string& server_token, unsigned int timeoutms); + static IPipe* getRemoteConnection(const std::string& server_token, const unsigned int timeoutms, const int type); static void requestRestoreRestart(); @@ -436,7 +440,7 @@ class ClientConnector : public ICustomClient IPipe* pipe; }; - static std::vector fileserv_connections; + static std::vector remote_connections; static RestoreOkStatus restore_ok_status; static RestoreFiles* restore_files; static bool status_updated; @@ -462,7 +466,7 @@ class ClientConnector : public ICustomClient std::string endpoint_name; - bool make_fileserv; + std::atomic make_conn; #ifdef _WIN32 static SVolumesCache* volumes_cache; diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 7832353fd..54a20a76f 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1402,7 +1402,7 @@ void ClientConnector::CMD_CHANNEL(const std::string &cmd, IScopedLock *g_lock, c g_lock->relock(backup_mutex); channel_pipes.push_back(SChannel(pipe, internet_conn, endpoint_name, token, - &make_fileserv, identity, capa, watoi(params["restore_version"]), params["virtual_client"])); + &make_conn, identity, capa, watoi(params["restore_version"]), params["virtual_client"])); is_channel=true; state=CCSTATE_CHANNEL; last_channel_ping=Server->getTimeMS(); diff --git a/urbackupclient/RestoreFiles.cpp b/urbackupclient/RestoreFiles.cpp index b4c386f8f..94029564b 100644 --- a/urbackupclient/RestoreFiles.cpp +++ b/urbackupclient/RestoreFiles.cpp @@ -485,7 +485,7 @@ void RestoreFiles::operator()() IPipe * RestoreFiles::new_fileclient_connection( ) { - return ClientConnector::getFileServConnection(server_token, 10000); + return ClientConnector::getRemoteConnection(server_token, 10000, ConnectionTypeFileServ); } bool RestoreFiles::connectFileClient( FileClient& fc ) diff --git a/urbackupclient/SambaService.cpp b/urbackupclient/SambaService.cpp new file mode 100644 index 000000000..d2c839159 --- /dev/null +++ b/urbackupclient/SambaService.cpp @@ -0,0 +1,101 @@ +#include "SambaService.h" +#include "../Interface/Thread.h" +#include "../Interface/Pipe.h" +#include "../Interface/Server.h" +#include "../Interface/ThreadPool.h" +#include "ClientService.h" + +namespace +{ + class StreamInput : public IThread + { + IPipe* pipe; + IPipe* smbPipe; + public: + StreamInput(IPipe* pipe, IPipe* smbPipe) + : pipe(pipe), smbPipe(smbPipe) + { } + + void operator()() + { + std::unique_ptr freeThis(this); + + size_t read; + char buffer[32768]; + while ((read = smbPipe->Read(buffer, sizeof(buffer))) > 0) + { + if (!pipe->Write(buffer, read)) + break; + } + + pipe->shutdown(); + smbPipe->shutdown(); + } + }; +} + +ICustomClient* SambaServiceFactory::createClient() +{ + return new SambaService(); +} + +void SambaServiceFactory::destroyClient(ICustomClient* pClient) +{ + delete static_cast(pClient); +} + +void SambaService::Init(THREAD_ID pTID, IPipe* pPipe, const std::string& pEndpointName) +{ + pipe = pPipe; + state = State::Init; + smbPipe.reset(); + readTicket = ILLEGAL_THREADPOOL_TICKET; +} + +bool SambaService::wantReceive() +{ + return state == State::Running; +} + +bool SambaService::Run(IRunOtherCallback* run_other) +{ + if (state == State::Shutdown) + { + if (!Server->getThreadPool()->isRunning(readTicket)) + return false; + return true; + } + + if (state == State::Init) + { + smbPipe.reset(ClientConnector::getRemoteConnection(std::string(), 10000, ConnectionTypeSamba)); + if (!smbPipe) + return false; + + state = State::Running; + readTicket = Server->getThreadPool()->execute(new StreamInput(pipe, smbPipe.get()), "smb read"); + } + + return true; +} + +void SambaService::ReceivePackets(IRunOtherCallback* run_other) +{ + char buffer[32768]; + const auto read = pipe->Read(buffer, sizeof(buffer)); + if (read == 0) + { + state = State::Shutdown; + pipe->shutdown(); + smbPipe->shutdown(); + return; + } + + if (!smbPipe->Write(buffer, read)) + { + state = State::Shutdown; + pipe->shutdown(); + smbPipe->shutdown(); + return; + } +} \ No newline at end of file diff --git a/urbackupclient/SambaService.h b/urbackupclient/SambaService.h new file mode 100644 index 000000000..98c1c7162 --- /dev/null +++ b/urbackupclient/SambaService.h @@ -0,0 +1,38 @@ +#pragma once + +#include "../Interface/CustomClient.h" +#include "../Interface/Service.h" +#include "../Interface/ThreadPool.h" +#include + +class SambaServiceFactory : public IService +{ +public: + virtual ICustomClient* createClient(); + virtual void destroyClient(ICustomClient* pClient); +}; + + +class SambaService : public ICustomClient +{ +public: + void Init(THREAD_ID pTID, IPipe* pPipe, const std::string& pEndpointName) override; + bool Run(IRunOtherCallback* run_other) override; + void ReceivePackets(IRunOtherCallback* run_other) override; + virtual bool wantReceive() override; + +private: + + enum class State + { + Init, + Shutdown, + Running + }; + + State state; + THREADPOOL_TICKET readTicket; + + IPipe* pipe; + std::unique_ptr smbPipe; +}; \ No newline at end of file diff --git a/urbackupclient/dllmain.cpp b/urbackupclient/dllmain.cpp index 176764f6f..39ea15615 100644 --- a/urbackupclient/dllmain.cpp +++ b/urbackupclient/dllmain.cpp @@ -63,6 +63,7 @@ extern IServer* Server; #include "tokens.h" #include "ClientService.h" +#include "SambaService.h" #include "client.h" #include "../stringtools.h" #include "ServerIdentityMgr.h" @@ -108,6 +109,7 @@ std::string server_identity; std::string server_token; const unsigned short default_urbackup_serviceport=35623; +const unsigned short default_sambaredir_port = 35624; void init_mutex1(void); bool testEscape(void); @@ -529,6 +531,14 @@ DLLEXPORT void LoadActions(IServer* pServer) Server->StartCustomStreamService(new ClientService(), "urbackupserver", urbackup_serviceport, -1, serviceport_bind_target); + unsigned short sambaredir_port = default_sambaredir_port; + if (!Server->getServerParameter("smabaredir_port").empty()) + { + sambaredir_port = static_cast(atoi(Server->getServerParameter("sambaredir_port").c_str())); + } + + Server->StartCustomStreamService(new SambaServiceFactory(), "sambaredir", sambaredir_port, -1, IServer::BindTarget_Localhost); + internetclient_ticket=InternetClient::start(do_leak_check); #ifdef _WIN32 diff --git a/urbackupclient/urbackupclient.vcxproj b/urbackupclient/urbackupclient.vcxproj index 91dfc8c9d..115df5f60 100644 --- a/urbackupclient/urbackupclient.vcxproj +++ b/urbackupclient/urbackupclient.vcxproj @@ -74,6 +74,7 @@ + @@ -130,6 +131,7 @@ + diff --git a/urbackupclient/urbackupclient.vcxproj.filters b/urbackupclient/urbackupclient.vcxproj.filters index 3ec371633..ff2622cbd 100644 --- a/urbackupclient/urbackupclient.vcxproj.filters +++ b/urbackupclient/urbackupclient.vcxproj.filters @@ -195,6 +195,9 @@ Quelldateien + + Quelldateien + @@ -359,5 +362,8 @@ Headerdateien + + Headerdateien + \ No newline at end of file From c37b9bcc22e78775a5567b2c180432ed640896b5 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 8 Feb 2026 20:20:18 +0100 Subject: [PATCH 417/469] Add option to set TCP_NODELAY --- Interface/Pipe.h | 7 +++++++ MemoryPipe.cpp | 5 +++++ MemoryPipe.h | 2 ++ SChannelPipe.cpp | 5 +++++ SChannelPipe.h | 2 ++ StreamPipe.cpp | 12 ++++++++++++ StreamPipe.h | 2 ++ urbackupcommon/CompressedPipe.cpp | 5 +++++ urbackupcommon/CompressedPipe.h | 2 ++ urbackupcommon/CompressedPipe2.cpp | 5 +++++ urbackupcommon/CompressedPipe2.h | 4 +++- urbackupcommon/CompressedPipeZstd.cpp | 5 +++++ urbackupcommon/CompressedPipeZstd.h | 2 ++ urbackupcommon/InternetServicePipe.cpp | 5 +++++ urbackupcommon/InternetServicePipe.h | 2 ++ urbackupcommon/InternetServicePipe2.cpp | 5 +++++ urbackupcommon/InternetServicePipe2.h | 2 ++ urbackupcommon/WebSocketPipe.h | 5 +++++ 18 files changed, 76 insertions(+), 1 deletion(-) diff --git a/Interface/Pipe.h b/Interface/Pipe.h index 12ff9e147..dc4cdb491 100644 --- a/Interface/Pipe.h +++ b/Interface/Pipe.h @@ -75,6 +75,13 @@ class IPipe : public IObject }; virtual bool setCompressionSettings(const SCompressionSettings& params) = 0; + + enum SocketOption + { + SocketOption_NoDelay = 1 + }; + + virtual bool setOption(const SocketOption opt) = 0; }; #endif //IPIPE_H diff --git a/MemoryPipe.cpp b/MemoryPipe.cpp index fab2fc206..42baad727 100644 --- a/MemoryPipe.cpp +++ b/MemoryPipe.cpp @@ -271,3 +271,8 @@ bool CMemoryPipe::setCompressionSettings(const SCompressionSettings& params) { return false; } + +bool CMemoryPipe::setOption(const SocketOption opt) +{ + return false; +} diff --git a/MemoryPipe.h b/MemoryPipe.h index cf0e6fe9d..9a8d58bc6 100644 --- a/MemoryPipe.h +++ b/MemoryPipe.h @@ -41,6 +41,8 @@ class CMemoryPipe : public IPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + bool setOption(const SocketOption opt) override; + private: std::deque queue; diff --git a/SChannelPipe.cpp b/SChannelPipe.cpp index 79bce5fcd..add3261e3 100644 --- a/SChannelPipe.cpp +++ b/SChannelPipe.cpp @@ -256,6 +256,11 @@ bool SChannelPipe::setCompressionSettings(const SCompressionSettings& params) return false; } +bool SChannelPipe::setOption(const SocketOption opt) +{ + return bpipe->setOption(opt); +} + void SChannelPipe::init() { diff --git a/SChannelPipe.h b/SChannelPipe.h index ba14d5a22..254290ab4 100644 --- a/SChannelPipe.h +++ b/SChannelPipe.h @@ -54,6 +54,8 @@ class SChannelPipe : public IPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + virtual bool setOption(const SocketOption opt); + private: bool ssl_connect_negotiate(int timeoutms, bool do_read); diff --git a/StreamPipe.cpp b/StreamPipe.cpp index 4bd80dc35..1aee28450 100644 --- a/StreamPipe.cpp +++ b/StreamPipe.cpp @@ -386,6 +386,18 @@ bool CStreamPipe::setCompressionSettings(const SCompressionSettings& params) return false; } +bool CStreamPipe::setOption(const SocketOption opt) +{ + switch (opt) + { + case SocketOption_NoDelay: + int flag; + flag = 1; + return setsockopt(s, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast(&flag), sizeof(int)) == 0; + } + return false; +} + _i64 CStreamPipe::getTransferedBytes(void) { return transfered_bytes; diff --git a/StreamPipe.h b/StreamPipe.h index e68cdb37f..20d7bb95c 100644 --- a/StreamPipe.h +++ b/StreamPipe.h @@ -50,6 +50,8 @@ class CStreamPipe : public IPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + virtual bool setOption(const SocketOption opt); + private: SOCKET s; diff --git a/urbackupcommon/CompressedPipe.cpp b/urbackupcommon/CompressedPipe.cpp index db2be3fb5..2aef8ec69 100644 --- a/urbackupcommon/CompressedPipe.cpp +++ b/urbackupcommon/CompressedPipe.cpp @@ -93,6 +93,11 @@ bool CompressedPipe::setCompressionSettings(const SCompressionSettings& params) return false; } +bool CompressedPipe::setOption(const SocketOption opt) +{ + return cs->setOption(opt); +} + size_t CompressedPipe::Read(char *buffer, size_t bsize, int timeoutms) { size_t rc=ReadToBuffer(buffer, bsize); diff --git a/urbackupcommon/CompressedPipe.h b/urbackupcommon/CompressedPipe.h index eade588b3..34fa2e86a 100644 --- a/urbackupcommon/CompressedPipe.h +++ b/urbackupcommon/CompressedPipe.h @@ -57,6 +57,8 @@ class CompressedPipe : public ICompressedPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + virtual bool setOption(const SocketOption opt); + private: void Process(const char *buffer, size_t bsize); size_t ReadToBuffer(char *buffer, size_t bsize); diff --git a/urbackupcommon/CompressedPipe2.cpp b/urbackupcommon/CompressedPipe2.cpp index 24050f63c..0ea345668 100644 --- a/urbackupcommon/CompressedPipe2.cpp +++ b/urbackupcommon/CompressedPipe2.cpp @@ -248,6 +248,11 @@ void CompressedPipe2::ProcessToString(std::string* ret, bool fromLast ) } while (input_buffer_size!=0); } +bool CompressedPipe2::setOption(const SocketOption opt) +{ + return cs->setOption(opt); +} + bool CompressedPipe2::Write(const char *buffer, size_t bsize, int timeoutms, bool flush) { IScopedLock lock(write_mutex.get()); diff --git a/urbackupcommon/CompressedPipe2.h b/urbackupcommon/CompressedPipe2.h index 37f6ce083..43b74825d 100644 --- a/urbackupcommon/CompressedPipe2.h +++ b/urbackupcommon/CompressedPipe2.h @@ -65,6 +65,8 @@ class CompressedPipe2 : public ICompressedPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + bool setOption(const SocketOption opt) override; + private: size_t ProcessToBuffer(char *buffer, size_t bsize, bool fromLast); void ProcessToString(std::string* ret, bool fromLast); @@ -86,5 +88,5 @@ class CompressedPipe2 : public ICompressedPipe z_stream def_stream; std::auto_ptr read_mutex; - std::auto_ptr write_mutex; + std::auto_ptr write_mutex; }; \ No newline at end of file diff --git a/urbackupcommon/CompressedPipeZstd.cpp b/urbackupcommon/CompressedPipeZstd.cpp index c3ed596bc..8d411f7ec 100644 --- a/urbackupcommon/CompressedPipeZstd.cpp +++ b/urbackupcommon/CompressedPipeZstd.cpp @@ -759,5 +759,10 @@ bool CompressedPipeZstd::setCompressionSettings(const SCompressionSettings& para return true; } +bool CompressedPipeZstd::setOption(const SocketOption opt) +{ + return cs->setOption(opt); +} + #endif //NO_ZSTD_COMPRESSION diff --git a/urbackupcommon/CompressedPipeZstd.h b/urbackupcommon/CompressedPipeZstd.h index b74a216d3..bbd8a6f1c 100644 --- a/urbackupcommon/CompressedPipeZstd.h +++ b/urbackupcommon/CompressedPipeZstd.h @@ -60,6 +60,8 @@ class CompressedPipeZstd : public ICompressedPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + virtual bool setOption(const SocketOption opt); + private: virtual bool WriteInt(const char *buffer, size_t bsize, int timeoutms = -1, ZSTD_EndDirective flush= ZSTD_e_continue); diff --git a/urbackupcommon/InternetServicePipe.cpp b/urbackupcommon/InternetServicePipe.cpp index 929bd5c6e..29f867bcf 100644 --- a/urbackupcommon/InternetServicePipe.cpp +++ b/urbackupcommon/InternetServicePipe.cpp @@ -206,6 +206,11 @@ bool InternetServicePipe::setCompressionSettings(const SCompressionSettings& par return false; } +bool InternetServicePipe::setOption(const SocketOption opt) +{ + return cs->setOption(opt); +} + bool InternetServicePipe::Flush(int timeoutms) { return cs->Flush(timeoutms); diff --git a/urbackupcommon/InternetServicePipe.h b/urbackupcommon/InternetServicePipe.h index a702be01a..0a6595e22 100644 --- a/urbackupcommon/InternetServicePipe.h +++ b/urbackupcommon/InternetServicePipe.h @@ -69,6 +69,8 @@ class InternetServicePipe : public IInternetServicePipe virtual bool setCompressionSettings(const SCompressionSettings& params); + virtual bool setOption(const SocketOption opt); + private: IPipe *cs; diff --git a/urbackupcommon/InternetServicePipe2.cpp b/urbackupcommon/InternetServicePipe2.cpp index 59313a150..fe2cd6bcc 100644 --- a/urbackupcommon/InternetServicePipe2.cpp +++ b/urbackupcommon/InternetServicePipe2.cpp @@ -339,3 +339,8 @@ bool InternetServicePipe2::setCompressionSettings(const SCompressionSettings& pa return false; } +bool InternetServicePipe2::setOption(const SocketOption opt) +{ + return cs->setOption(opt); +} + diff --git a/urbackupcommon/InternetServicePipe2.h b/urbackupcommon/InternetServicePipe2.h index 2de017147..1292b5b50 100644 --- a/urbackupcommon/InternetServicePipe2.h +++ b/urbackupcommon/InternetServicePipe2.h @@ -78,6 +78,8 @@ class InternetServicePipe2 : public IInternetServicePipe virtual bool setCompressionSettings(const SCompressionSettings& params); + bool setOption(const SocketOption opt); + private: std::auto_ptr dec; std::auto_ptr enc; diff --git a/urbackupcommon/WebSocketPipe.h b/urbackupcommon/WebSocketPipe.h index cc2b1b659..ecb3914e9 100644 --- a/urbackupcommon/WebSocketPipe.h +++ b/urbackupcommon/WebSocketPipe.h @@ -78,6 +78,11 @@ class WebSocketPipe : public IPipe virtual void setUsageString(const std::string& str) override; virtual bool setCompressionSettings(const SCompressionSettings& params) override; + virtual bool setOption(const SocketOption opt) override + { + return pipe->setOption(opt); + } + private: bool has_read_mask() From a39456db149160ace8a8eff981fea208d18908b5 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 8 Feb 2026 20:20:34 +0100 Subject: [PATCH 418/469] Improve samba service performance --- urbackupclient/SambaService.cpp | 37 ++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/urbackupclient/SambaService.cpp b/urbackupclient/SambaService.cpp index d2c839159..a2dfd5ff9 100644 --- a/urbackupclient/SambaService.cpp +++ b/urbackupclient/SambaService.cpp @@ -72,6 +72,8 @@ bool SambaService::Run(IRunOtherCallback* run_other) if (!smbPipe) return false; + smbPipe->setOption(IPipe::SocketOption_NoDelay); + state = State::Running; readTicket = Server->getThreadPool()->execute(new StreamInput(pipe, smbPipe.get()), "smb read"); } @@ -81,21 +83,28 @@ bool SambaService::Run(IRunOtherCallback* run_other) void SambaService::ReceivePackets(IRunOtherCallback* run_other) { - char buffer[32768]; - const auto read = pipe->Read(buffer, sizeof(buffer)); - if (read == 0) + while (true) { - state = State::Shutdown; - pipe->shutdown(); - smbPipe->shutdown(); - return; - } + char buffer[32768]; + const auto read = pipe->Read(buffer, sizeof(buffer)); + if (read == 0) + { + state = State::Shutdown; + pipe->shutdown(); + smbPipe->shutdown(); + return; + } - if (!smbPipe->Write(buffer, read)) - { - state = State::Shutdown; - pipe->shutdown(); - smbPipe->shutdown(); - return; + const bool flush = !pipe->isReadable(); + if (!smbPipe->Write(buffer, read, -1, flush)) + { + state = State::Shutdown; + pipe->shutdown(); + smbPipe->shutdown(); + return; + } + + if (flush) + break; } } \ No newline at end of file From 9e4656749582f8117a686b8c27cd4e14d3829947 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 9 Feb 2026 18:26:03 +0100 Subject: [PATCH 419/469] Write smb passwords to files --- .gitignore | 1 + urbackupclient/ClientServiceCMD.cpp | 36 ++++++++++- urbackupclient/tokens.h | 2 + urbackupclient/win_tokens.cpp | 95 +++++++++++++++++++++++++++++ 4 files changed, 133 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index bf133e3a3..fe9b9237f 100644 --- a/.gitignore +++ b/.gitignore @@ -326,3 +326,4 @@ cryptoplugin/src/m4/libtool.m4 /urbackupclient/sysvol_test/ARM64 /urbackupserver/ARM64 /urlplugin/ARM64 +/SQLGen/SQLGen/x64 diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 54a20a76f..031cb7b7d 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -1399,6 +1399,32 @@ void ClientConnector::CMD_CHANNEL(const std::string &cmd, IScopedLock *g_lock, c tcpstack.Send(pipe, "STARTUP timestamp=" + convert(startup_timestamp)); } + bool create_smb_dir = true; + + size_t idx = 0; + while (params.find("client_user_name_" + std::to_string(idx)) != params.end()) + { + if (create_smb_dir) + { + create_smb_dir = false; + os_create_dir("smbpw"); + } + + std::string user_name = params["client_user_name_" + std::to_string(idx)]; + std::string user_login = params["client_user_login_" + std::to_string(idx)]; + std::string user_pw = params["client_user_pw_" + std::to_string(idx)]; + + std::string fn = bytesToHex(user_name) + ".dat"; + + const auto smb_pw_fn = "smbpw/" + fn; + + if (!FileExists(smb_pw_fn)) + { + tokens::write_smb_pw(smb_pw_fn, user_name, user_login + ":" + user_pw); + } + ++idx; + } + g_lock->relock(backup_mutex); channel_pipes.push_back(SChannel(pipe, internet_conn, endpoint_name, token, @@ -2747,6 +2773,14 @@ void ClientConnector::CMD_CAPA(const std::string &cmd) std::string os_version_str = get_windows_version(); std::string win_volumes; std::string win_nonusb_volumes; + std::string users; + + static const auto local_users = tokens::get_local_users(); + for (const auto& user : local_users) + { + if (!users.empty()) users += "/"; + users += user; + } { IScopedLock lock(backup_mutex); @@ -2773,7 +2807,7 @@ void ClientConnector::CMD_CAPA(const std::string &cmd) "&CLIENT_VERSION_STR="+EscapeParamString((client_version_str))+"&OS_VERSION_STR="+EscapeParamString(os_version_str)+ "&ALL_VOLUMES="+EscapeParamString(win_volumes)+"&ETA=1&CDP=0&ALL_NONUSB_VOLUMES="+EscapeParamString(win_nonusb_volumes)+"&EFI=1" "&FILE_META=1&SELECT_SHA=1&PHASH=1&RESTORE="+restore+"&RESTORE_VER=1&CLIENT_BITMAP=1&CMD=2&SYMBIT=1&WTOKENS=1&FILESRVTUNNEL=1&OS_SIMPLE=windows" - "&clientuid="+EscapeParamString(clientuid)+conn_metered+ send_prev_cbitmap + imm_backup); + "&clientuid="+EscapeParamString(clientuid)+conn_metered+ send_prev_cbitmap + imm_backup + "&USERS="+EscapeParamString(users)); #else #ifdef __APPLE__ diff --git a/urbackupclient/tokens.h b/urbackupclient/tokens.h index e41267aa4..71e9a68cb 100644 --- a/urbackupclient/tokens.h +++ b/urbackupclient/tokens.h @@ -59,6 +59,8 @@ namespace tokens bool write_token( std::string hostname, bool is_user, std::string accountname, const std::string &token_fn, ClientDAO &dao, const std::string& ext_token=std::string()); + bool write_smb_pw(const std::string& fn, const std::string& accountname, const std::string& data); + std::string permissions_allow_all(); std::string accountname_normalize(const std::string& accountname); diff --git a/urbackupclient/win_tokens.cpp b/urbackupclient/win_tokens.cpp index c69e46af0..e7a55d9c6 100644 --- a/urbackupclient/win_tokens.cpp +++ b/urbackupclient/win_tokens.cpp @@ -583,6 +583,101 @@ bool write_token( std::string hostname, bool is_user, std::string accountname, c return true; } +bool write_smb_pw(const std::string& fn, const std::string& accountname, const std::string& data) +{ + DWORD account_sid_size = sizeof(SID); + SID_NAME_USE sid_name_use; + std::wstring referenced_domain; + referenced_domain.resize(1); + DWORD referenced_domain_size = 1; + std::vector sid_buffer; + sid_buffer.resize(sizeof(SID)); + + const auto local_username = Server->ConvertToWchar(accountname); + + auto b = LookupAccountNameW(NULL, + local_username.c_str(), + &sid_buffer[0], &account_sid_size, &referenced_domain[0], + &referenced_domain_size, &sid_name_use); + + if (!b && GetLastError() == ERROR_INSUFFICIENT_BUFFER) + { + referenced_domain.resize(referenced_domain_size); + sid_buffer.resize(account_sid_size); + b = LookupAccountNameW(NULL, + local_username.c_str(), + &sid_buffer[0], &account_sid_size, &referenced_domain[0], + &referenced_domain_size, &sid_name_use); + } + + if (referenced_domain.size() != referenced_domain_size) + { + referenced_domain.resize(referenced_domain_size); + } + + SID* account_sid = reinterpret_cast(&sid_buffer[0]); + + LPWSTR str_account_sid; + b = ConvertSidToStringSidW(account_sid, &str_account_sid); + if (!b) + { + Server->Log("Error converting SID to string SID. Errorcode: " + convert((int)GetLastError()), LL_ERROR); + return false; + } + + std::wstring dacl = std::wstring(L"D:(A;OICI;GA;;;") + str_account_sid + L")" + + L"(A;OICI;GA;;;BA)"; + + std::string local_account_sid = Server->ConvertFromWchar(str_account_sid); + + LocalFree(str_account_sid); + + SECURITY_ATTRIBUTES sa; + sa.nLength = sizeof(SECURITY_ATTRIBUTES); + sa.bInheritHandle = FALSE; + + + b = ConvertStringSecurityDescriptorToSecurityDescriptor( + dacl.c_str(), + SDDL_REVISION_1, + &(sa.lpSecurityDescriptor), + NULL); + + if (!b) + { + Server->Log("Error creating security descriptor. Errorcode: " + convert((int)GetLastError()), LL_ERROR); + return false; + } + + HANDLE file = CreateFileW(Server->ConvertToWchar(fn).c_str(), + GENERIC_READ | GENERIC_WRITE, 0, &sa, CREATE_ALWAYS, 0, NULL); + + if (file == INVALID_HANDLE_VALUE) + { + Server->Log("Error opening smb pw file. Errorcode: " + convert((int)GetLastError()), LL_ERROR); + LocalFree(sa.lpSecurityDescriptor); + return false; + } + + DWORD written = 0; + while (written < data.size()) + { + b = WriteFile(file, data.data() + written, static_cast(data.size()) - written, &written, NULL); + if (!b) + { + Server->Log("Error writing to smb pw file. Errorcode: " + convert((int)GetLastError()), LL_ERROR); + CloseHandle(file); + LocalFree(sa.lpSecurityDescriptor); + return true; + } + } + + CloseHandle(file); + LocalFree(sa.lpSecurityDescriptor); + + return true; +} + std::string permissions_allow_all() { CWData token_info; From e87ae1284cbe6698c3d5343097a4abf0702dbb55 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 9 Feb 2026 19:00:40 +0100 Subject: [PATCH 420/469] Use semicolon as separator --- urbackupclient/ClientServiceCMD.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupclient/ClientServiceCMD.cpp b/urbackupclient/ClientServiceCMD.cpp index 031cb7b7d..cf052848d 100644 --- a/urbackupclient/ClientServiceCMD.cpp +++ b/urbackupclient/ClientServiceCMD.cpp @@ -2778,7 +2778,7 @@ void ClientConnector::CMD_CAPA(const std::string &cmd) static const auto local_users = tokens::get_local_users(); for (const auto& user : local_users) { - if (!users.empty()) users += "/"; + if (!users.empty()) users += ";"; users += user; } From bc783cc0680992b1746b9797a90a38061e366c57 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 23 Feb 2026 13:20:57 +0100 Subject: [PATCH 421/469] Obfuscate the EICAR string a bit --- urbackupserver/serverinterface/status_check.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/urbackupserver/serverinterface/status_check.cpp b/urbackupserver/serverinterface/status_check.cpp index 9f6af2cd3..bbb587889 100644 --- a/urbackupserver/serverinterface/status_check.cpp +++ b/urbackupserver/serverinterface/status_check.cpp @@ -340,7 +340,9 @@ namespace } else { - std::string teststring = base64_decode("WDVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo="); + char eicar_str[] = "_DVPIVAlQEFQWzRcUFpYNTQoUF4pN0NDKTd9JEVJQ0FSLVNUQU5EQVJELUFOVElWSVJVUy1URVNULUZJTEUhJEgrSCo="; + eicar_str[0] = 'W'; + const std::string teststring = base64_decode(eicar_str); tmp_f->Write(teststring); std::string tmp_fn = tmp_f->getFilename(); tmp_f.reset(); From 0ab90410d59f8fc99c9342d05ee4e91bc0bd8f4c Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 23 Feb 2026 14:18:10 +0100 Subject: [PATCH 422/469] Show a warning on the status page if there is no users --- urbackupserver/serverinterface/status_check.cpp | 10 ++++++++++ urbackupserver/www/js/translation.js | 3 ++- urbackupserver/www/js/urbackup.js | 6 ++++++ urbackupserver/www/templates/no_users.htm | 9 +++++++++ 4 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 urbackupserver/www/templates/no_users.htm diff --git a/urbackupserver/serverinterface/status_check.cpp b/urbackupserver/serverinterface/status_check.cpp index bbb587889..2e2ea6117 100644 --- a/urbackupserver/serverinterface/status_check.cpp +++ b/urbackupserver/serverinterface/status_check.cpp @@ -410,6 +410,16 @@ ACTION_IMPL(status_check) ServerSettings settings(db); access_dir_checks(db, settings, settings.getSettings()->backupfolder, settings.getSettings()->backupfolder_uncompr, ret); + db_results res = db->Read("SELECT name FROM settings_db.si_users LIMIT 1"); + if (res.empty()) + { + ret.set("no_users", true); + ret.set("no_users_stop_show_key", "no_users_warning"); + if (is_stop_show(db, "no_users_warning")) + { + ret.set("no_users_show", false); + } + } } else { diff --git a/urbackupserver/www/js/translation.js b/urbackupserver/www/js/translation.js index a2c430b27..402ab8821 100644 --- a/urbackupserver/www/js/translation.js +++ b/urbackupserver/www/js/translation.js @@ -1995,7 +1995,8 @@ translations.en = { "tUse SSL encrypted SMTP (SMTPS) instead of SMTP with STARTTLS": "Use SSL encrypted SMTP (SMTPS) instead of SMTP with STARTTLS", "tLocal/passive client": "Local/passive client", "tInternet/Active client": "Internet/Active client", -"tAllow new client": "Allow new client" +"tAllow new client": "Allow new client", +"no_users_text": "No admin user created yet. Your UrBackup server is currently not protected by a password. Please create an admin user now.", } translations.en_US = { "tInfos": "About" diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 4cdf6db5d..31cd91b22 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -1196,6 +1196,12 @@ function show_status_check2(data) { check_res+=dustRender("tmpdir_error", {tmpdir_error_text: trans("tmpdir_error_text"), stop_show_key: data.tmpdir_error_stop_show_key}); } + + if(data.no_users + && (typeof data.no_users_show === "undefined" || data.no_users_show===true ) ) + { + check_res+=dustRender("no_users", {no_users_text: trans("no_users_text"), stop_show_key: data.no_users_stop_show_key}); + } var virus_error=""; if(data.virus_error diff --git a/urbackupserver/www/templates/no_users.htm b/urbackupserver/www/templates/no_users.htm new file mode 100644 index 000000000..3cf5ec003 --- /dev/null +++ b/urbackupserver/www/templates/no_users.htm @@ -0,0 +1,9 @@ + \ No newline at end of file From 5c57845358061d5beee6a79ed633a1b0456cc92c Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 23 Feb 2026 14:19:22 +0100 Subject: [PATCH 423/469] Ask for Internet server url if it is empty when adding client --- Makefile.am_server | 2 +- urbackupserver/serverinterface/add_client.cpp | 24 +++++++++++++++ urbackupserver/serverinterface/settings.cpp | 7 +++++ urbackupserver/serverinterface/settings.h | 5 ++++ .../serverinterface/status_check.cpp | 4 +++ urbackupserver/www/js/urbackup.js | 29 +++++++++++++++++-- urbackupserver/www/templates/add_client.htm | 6 ++++ urbackupserver/www/templates/client_added.htm | 5 ++++ .../www/templates/settings_inv_row.htm | 2 +- 9 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 urbackupserver/serverinterface/settings.h diff --git a/Makefile.am_server b/Makefile.am_server index 0c506ee46..279163e21 100644 --- a/Makefile.am_server +++ b/Makefile.am_server @@ -290,6 +290,6 @@ zstd_headers = \ luaplugin_headers = luaplugin/ILuaInterpreter.h luaplugin/LuaInterpreter.h luaplugin/pluginmgr.h luaplugin/src/* luaplugin/lua/dkjson_lua.h -noinst_HEADERS=SessionMgr.h WorkerThread.h Helper_win32.h Database.h defaults.h ServiceAcceptor.h Query.h SettingsReader.h file.h file_memory.h MemorySettingsReader.h Condition_lin.h LookupService.h Template.h types.h DBSettingsReader.h stringtools.h ThreadPool.h libs.h vld_.h ServiceWorker.h StreamPipe.h LoadbalancerClient.h socket_header.h FileSettingsReader.h SelectThread.h md5.h vld.h Table.h Client.h MemoryPipe.h Mutex_lin.h AcceptThread.h OutputStream.h Server.h Interface/SessionMgr.h Interface/Service.h Interface/PluginMgr.h Interface/Database.h Interface/Pipe.h Interface/CustomClient.h Interface/User.h Interface/Query.h Interface/SettingsReader.h Interface/Types.h Interface/Template.h Interface/ThreadPool.h Interface/Mutex.h Interface/File.h Interface/Condition.h Interface/Table.h Interface/Plugin.h Interface/Thread.h Interface/Action.h Interface/Object.h Interface/OutputStream.h Interface/Server.h libfastcgi/fastcgi.hpp sqlite/sqlite3.h sqlite/sqlite3ext.h utf8/utf8.h utf8/utf8/checked.h utf8/utf8/core.h utf8/utf8/unchecked.h cryptoplugin/ICryptoFactory.h cryptoplugin/IAESEncryption.h cryptoplugin/IAESDecryption.h Interface/DatabaseFactory.h Interface/DatabaseInt.h SQLiteFactory.h sqlite/shell.h PipeThrottler.h Interface/PipeThrottler.h mt19937ar.h DatabaseCursor.h Interface/DatabaseCursor.h Interface/SharedMutex.h Interface/WebSocket.h SharedMutex_lin.h httpserver/HTTPAction.h httpserver/HTTPClient.h httpserver/HTTPFile.h httpserver/HTTPProxy.h httpserver/HTTPService.h httpserver/IndexFiles.h httpserver/MIMEType.h httpserver/HTTPSocket.h urbackupserver/server_ping.h urbackupserver/server_cleanup.h urbackupcommon/os_functions.h urbackupcommon/json.h urbackupserver/serverinterface/helper.h urbackupserver/serverinterface/action_header.h urbackupserver/serverinterface/actions.h urbackupserver/server_writer.h urbackupcommon/settings.h urbackupserver/server_settings.h urbackupserver/zero_hash.h urbackupserver/server_update.h urbackupserver/server_log.h urbackupserver/server_hash.h urbackupserver/server_status.h urbackupcommon/bufmgr.h urbackupserver/server_update_stats.h urbackupcommon/sha2/sha2.h urbackupcommon/fileclient/FileClient.h common/data.h urbackupcommon/fileclient/socket_header.h urbackupcommon/fileclient/tcpstack.h urbackupcommon/fileclient/packet_ids.h urbackupserver/database.h urbackupserver/mbr_code.h urbackupserver/action_header.h urbackupcommon/escape.h urbackupserver/server.h urbackupserver/server_running.h urbackupserver/server_prepare_hash.h urbackupserver/actions.h urbackupserver/server_channel.h urbackupserver/ClientMain.h urbackupserver/treediff/TreeDiff.h urbackupserver/treediff/TreeNode.h urbackupserver/treediff/TreeReader.h fileservplugin/IFileServFactory.h fileservplugin/IFileServ.h urlplugin/IUrlFactory.h urbackupcommon/capa_bits.h cryptoplugin/ICryptoFactory.h urbackupcommon/fileclient/FileClientChunked.h urbackupserver/ChunkPatcher.h urbackupcommon/CompressedPipe.h urbackupcommon/InternetServicePipe.h urbackupcommon/InternetServicePipe2.h urbackupcommon/InternetServiceIDs.h urbackupserver/InternetServiceConnector.h md5.h urbackupcommon/settingslist.h urbackupserver/server_archive.h cryptoplugin/IZlibCompression.h cryptoplugin/IZlibDecompression.h cryptoplugin/ICryptoFactory.h cryptoplugin/IAESEncryption.h cryptoplugin/IAESDecryption.h fileservplugin/chunk_settings.h urbackupcommon/internet_pipe_capabilities.h urbackupcommon/mbrdata.h urbackupserver/filedownload.h urbackupserver/snapshot_helper.h urbackupserver/apps/cleanup_cmd.h urbackupserver/apps/repair_cmd.h urbackupserver/dao/ServerCleanupDao.h urbackupserver/lmdb/lmdb.h urbackupserver/lmdb/midl.h urbackupserver/LMDBFileIndex.h urbackupserver/create_files_index.h urbackupserver/FileIndex.h urbackupserver/serverinterface/rights.h urbackupserver/server_dir_links.h urbackupserver/dao/ServerBackupDao.h urbackupserver/apps/app.h urbackupserver/apps/export_auth_log.h urbackupserver/serverinterface/login.h urbackupserver/ServerDownloadThread.h urbackupserver/ServerDownloadThreadGroup.h common/adler32.h urbackupcommon/file_metadata.h urbackupcommon/filelist_utils.h urbackupserver/Backup.h urbackupserver/ImageBackup.h urbackupserver/FileBackup.h urbackupserver/IncrFileBackup.h urbackupserver/FullFileBackup.h urbackupserver/ContinuousBackup.h urbackupserver/ThrottleUpdater.h urbackupcommon/glob.h urbackupserver/FileMetadataDownloadThread.h urbackupserver/restore_client.h urbackupcommon/chunk_hasher.h urbackupcommon/WalCheckpointThread.h urbackupcommon/CompressedPipe2.h urlplugin/IUrlFactory.h urlplugin/pluginmgr.h urlplugin/UrlFactory.h StaticPluginRegistration.h $(cryptoplugin_headers) $(fileservplugin_headers) $(fsimageplugin_headers) $(tclap_headers) urbackupserver/backup_server_db.h urbackupcommon/SparseFile.h urbackupcommon/ExtentIterator.h urbackupserver/dao/ServerLinkDao.h urbackupserver/dao/ServerLinkJournalDao.h urbackupcommon/server_compat.h urbackupserver/dao/ServerFilesDao.h urbackupserver/apps/skiphash_copy.h urbackupserver/apps/check_files_index.h urbackupserver/apps/patch.h urbackupserver/serverinterface/backups.h urbackupserver/server_continuous.h urbackupcommon/change_ids.h urbackupcommon/TreeHash.h urbackupserver/copy_storage.h urbackupserver/ImageMount.h common/bitmap.h $(cryptopp_headers) common/miniz.h urbackupserver/DataplanDb.h common/lrucache.h urbackupserver/PhashLoad.h fileservplugin/IPipeFileExt.h urbackupserver/Alerts.h urbackupserver/Mailer.h urbackupserver/alert_lua.h urbackupserver/alert_pulseway_lua.h $(luaplugin_headers) urbackupserver/LogReport.h urbackupserver/report_lua.h urbackupcommon/CompressedPipeZstd.h blockalign_src/main.cpp blockalign_src/crc32c-adler.cpp blockalign_src/crc.cpp blockalign_src/crc.h urbackupserver/WebSocketConnector.h urbackupcommon/WebSocketPipe.h $(zstd_headers) +noinst_HEADERS=SessionMgr.h WorkerThread.h Helper_win32.h Database.h defaults.h ServiceAcceptor.h Query.h SettingsReader.h file.h file_memory.h MemorySettingsReader.h Condition_lin.h LookupService.h Template.h types.h DBSettingsReader.h stringtools.h ThreadPool.h libs.h vld_.h ServiceWorker.h StreamPipe.h LoadbalancerClient.h socket_header.h FileSettingsReader.h SelectThread.h md5.h vld.h Table.h Client.h MemoryPipe.h Mutex_lin.h AcceptThread.h OutputStream.h Server.h Interface/SessionMgr.h Interface/Service.h Interface/PluginMgr.h Interface/Database.h Interface/Pipe.h Interface/CustomClient.h Interface/User.h Interface/Query.h Interface/SettingsReader.h Interface/Types.h Interface/Template.h Interface/ThreadPool.h Interface/Mutex.h Interface/File.h Interface/Condition.h Interface/Table.h Interface/Plugin.h Interface/Thread.h Interface/Action.h Interface/Object.h Interface/OutputStream.h Interface/Server.h libfastcgi/fastcgi.hpp sqlite/sqlite3.h sqlite/sqlite3ext.h utf8/utf8.h utf8/utf8/checked.h utf8/utf8/core.h utf8/utf8/unchecked.h cryptoplugin/ICryptoFactory.h cryptoplugin/IAESEncryption.h cryptoplugin/IAESDecryption.h Interface/DatabaseFactory.h Interface/DatabaseInt.h SQLiteFactory.h sqlite/shell.h PipeThrottler.h Interface/PipeThrottler.h mt19937ar.h DatabaseCursor.h Interface/DatabaseCursor.h Interface/SharedMutex.h Interface/WebSocket.h SharedMutex_lin.h httpserver/HTTPAction.h httpserver/HTTPClient.h httpserver/HTTPFile.h httpserver/HTTPProxy.h httpserver/HTTPService.h httpserver/IndexFiles.h httpserver/MIMEType.h httpserver/HTTPSocket.h urbackupserver/server_ping.h urbackupserver/server_cleanup.h urbackupcommon/os_functions.h urbackupcommon/json.h urbackupserver/serverinterface/helper.h urbackupserver/serverinterface/action_header.h urbackupserver/serverinterface/actions.h urbackupserver/server_writer.h urbackupcommon/settings.h urbackupserver/server_settings.h urbackupserver/zero_hash.h urbackupserver/server_update.h urbackupserver/server_log.h urbackupserver/server_hash.h urbackupserver/server_status.h urbackupcommon/bufmgr.h urbackupserver/server_update_stats.h urbackupcommon/sha2/sha2.h urbackupcommon/fileclient/FileClient.h common/data.h urbackupcommon/fileclient/socket_header.h urbackupcommon/fileclient/tcpstack.h urbackupcommon/fileclient/packet_ids.h urbackupserver/database.h urbackupserver/mbr_code.h urbackupserver/action_header.h urbackupcommon/escape.h urbackupserver/server.h urbackupserver/server_running.h urbackupserver/server_prepare_hash.h urbackupserver/actions.h urbackupserver/server_channel.h urbackupserver/ClientMain.h urbackupserver/treediff/TreeDiff.h urbackupserver/treediff/TreeNode.h urbackupserver/treediff/TreeReader.h fileservplugin/IFileServFactory.h fileservplugin/IFileServ.h urlplugin/IUrlFactory.h urbackupcommon/capa_bits.h cryptoplugin/ICryptoFactory.h urbackupcommon/fileclient/FileClientChunked.h urbackupserver/ChunkPatcher.h urbackupcommon/CompressedPipe.h urbackupcommon/InternetServicePipe.h urbackupcommon/InternetServicePipe2.h urbackupcommon/InternetServiceIDs.h urbackupserver/InternetServiceConnector.h md5.h urbackupcommon/settingslist.h urbackupserver/server_archive.h cryptoplugin/IZlibCompression.h cryptoplugin/IZlibDecompression.h cryptoplugin/ICryptoFactory.h cryptoplugin/IAESEncryption.h cryptoplugin/IAESDecryption.h fileservplugin/chunk_settings.h urbackupcommon/internet_pipe_capabilities.h urbackupcommon/mbrdata.h urbackupserver/filedownload.h urbackupserver/snapshot_helper.h urbackupserver/apps/cleanup_cmd.h urbackupserver/apps/repair_cmd.h urbackupserver/dao/ServerCleanupDao.h urbackupserver/lmdb/lmdb.h urbackupserver/lmdb/midl.h urbackupserver/LMDBFileIndex.h urbackupserver/create_files_index.h urbackupserver/FileIndex.h urbackupserver/serverinterface/rights.h urbackupserver/server_dir_links.h urbackupserver/dao/ServerBackupDao.h urbackupserver/apps/app.h urbackupserver/apps/export_auth_log.h urbackupserver/serverinterface/login.h urbackupserver/ServerDownloadThread.h urbackupserver/ServerDownloadThreadGroup.h common/adler32.h urbackupcommon/file_metadata.h urbackupcommon/filelist_utils.h urbackupserver/Backup.h urbackupserver/ImageBackup.h urbackupserver/FileBackup.h urbackupserver/IncrFileBackup.h urbackupserver/FullFileBackup.h urbackupserver/ContinuousBackup.h urbackupserver/ThrottleUpdater.h urbackupcommon/glob.h urbackupserver/FileMetadataDownloadThread.h urbackupserver/restore_client.h urbackupcommon/chunk_hasher.h urbackupcommon/WalCheckpointThread.h urbackupcommon/CompressedPipe2.h urlplugin/IUrlFactory.h urlplugin/pluginmgr.h urlplugin/UrlFactory.h StaticPluginRegistration.h $(cryptoplugin_headers) $(fileservplugin_headers) $(fsimageplugin_headers) $(tclap_headers) urbackupserver/backup_server_db.h urbackupcommon/SparseFile.h urbackupcommon/ExtentIterator.h urbackupserver/dao/ServerLinkDao.h urbackupserver/dao/ServerLinkJournalDao.h urbackupcommon/server_compat.h urbackupserver/dao/ServerFilesDao.h urbackupserver/apps/skiphash_copy.h urbackupserver/apps/check_files_index.h urbackupserver/apps/patch.h urbackupserver/serverinterface/backups.h urbackupserver/server_continuous.h urbackupcommon/change_ids.h urbackupcommon/TreeHash.h urbackupserver/copy_storage.h urbackupserver/ImageMount.h common/bitmap.h $(cryptopp_headers) common/miniz.h urbackupserver/DataplanDb.h common/lrucache.h urbackupserver/PhashLoad.h fileservplugin/IPipeFileExt.h urbackupserver/Alerts.h urbackupserver/Mailer.h urbackupserver/alert_lua.h urbackupserver/alert_pulseway_lua.h $(luaplugin_headers) urbackupserver/LogReport.h urbackupserver/report_lua.h urbackupcommon/CompressedPipeZstd.h blockalign_src/main.cpp blockalign_src/crc32c-adler.cpp blockalign_src/crc.cpp blockalign_src/crc.h urbackupserver/WebSocketConnector.h urbackupcommon/WebSocketPipe.h urbackupserver/serverinterface/settings.h $(zstd_headers) EXTRA_DIST=docs/urbackupsrv.1 init.d_server defaults_server logrotate_urbackupsrv urbackup-server.service urbackup-server-firewalld.xml urbackup/status.htm urbackupserver/www/js/*.js urbackupserver/www/js/vs/* urbackupserver/www/*.htm urbackupserver/www/*.ico urbackupserver/www/css/*.css urbackupserver/www/images/*.png urbackupserver/www/images/*.gif urbackupserver/www/*.ico urbackupserver/urbackup_ecdsa409k1.pub urbackupserver/www/swf/* urbackupserver/www/fonts/* tclap/COPYING tclap/AUTHORS server-license.txt urbackup/dataplan_db.txt diff --git a/urbackupserver/serverinterface/add_client.cpp b/urbackupserver/serverinterface/add_client.cpp index e9e576ffe..39d2c9ebc 100644 --- a/urbackupserver/serverinterface/add_client.cpp +++ b/urbackupserver/serverinterface/add_client.cpp @@ -1,5 +1,7 @@ #include "action_header.h" #include "../ClientMain.h" +#include "rights.h" +#include "settings.h" ACTION_IMPL(add_client) { @@ -15,6 +17,27 @@ ACTION_IMPL(add_client) return; } + bool empty_internet_server_url = false; + str_map::iterator it_internet_server = POST.find("internet_server"); + str_map::iterator it_internet_server_port = POST.find("internet_server_port"); + if (it_internet_server != POST.end() && + it_internet_server_port != POST.end()) + { + if (it_internet_server->second.empty()) + { + empty_internet_server_url = true; + } + else if(helper.getRights("general_settings") == RIGHT_ALL) + { + str_map change_settings; + change_settings["internet_server"] = it_internet_server->second; + change_settings["internet_server_port"] = it_internet_server_port->second; + bool changed_backupfolder; + saveGeneralSettingsExternal(change_settings, helper.getDatabase(), changed_backupfolder); + ServerSettings::updateAll(); + } + } + int p_group_id = -1; str_map::iterator group_id = POST.find("group_id"); if (group_id != POST.end()) @@ -90,6 +113,7 @@ ACTION_IMPL(add_client) ret.set("server_url", server_url); ret.set("internet_server", s->internet_server); ret.set("internet_server_port", s->internet_server_port); + ret.set("empty_internet_server_url", empty_internet_server_url); if (!s->internet_server_proxy.empty()) { ret.set("internet_server_proxy", s->internet_server_proxy); diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index 0be1de6c3..d9a6ca349 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -609,6 +609,13 @@ void updateAllOnlineClientSettings(IDatabase *db) } +void saveGeneralSettingsExternal(str_map& POST, IDatabase* db, bool& changed_backupfolder) +{ + ServerBackupDao backup_dao(db); + ServerSettings server_settings(db); + saveGeneralSettings(POST, db, backup_dao, server_settings, changed_backupfolder); +} + ACTION_IMPL(settings) { Helper helper(tid, &POST, &PARAMS); diff --git a/urbackupserver/serverinterface/settings.h b/urbackupserver/serverinterface/settings.h new file mode 100644 index 000000000..02f834b63 --- /dev/null +++ b/urbackupserver/serverinterface/settings.h @@ -0,0 +1,5 @@ +#pragma once +#include "../../Interface/Types.h" +#include "../../Interface/Database.h" + +void saveGeneralSettingsExternal(str_map& POST, IDatabase* db, bool& changed_backupfolder); diff --git a/urbackupserver/serverinterface/status_check.cpp b/urbackupserver/serverinterface/status_check.cpp index 2e2ea6117..a4b4a1d1a 100644 --- a/urbackupserver/serverinterface/status_check.cpp +++ b/urbackupserver/serverinterface/status_check.cpp @@ -410,6 +410,10 @@ ACTION_IMPL(status_check) ServerSettings settings(db); access_dir_checks(db, settings, settings.getSettings()->backupfolder, settings.getSettings()->backupfolder_uncompr, ret); + + if (settings.getSettings()->internet_server.empty()) + ret.set("no_internet_server", true); + db_results res = db->Read("SELECT name FROM settings_db.si_users LIMIT 1"); if (res.empty()) { diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 31cd91b22..2da934fd1 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -1210,6 +1210,8 @@ function show_status_check2(data) check_res+=dustRender("virus_error", {stop_show_key: data.virus_error_stop_show_key, virus_error_path: data.virus_error_path}); } I("delayed_status_errors").innerHTML = check_res; + + g.no_internet_server = data.no_internet_server ? true : false; } function show_status2(data) @@ -3650,6 +3652,7 @@ function show_settings2(data) data.settings.vss_select_components = unescapeHTML(data.settings.vss_select_components); data.settings.client_settings=false; + data.settings.internet_server_url_placeholder = getServerUrlPlaceholder(); data.settings.settings_inv=dustRender("settings_inv_row", data.settings); ndata+=dustRender("settings_general", data.settings); @@ -3779,6 +3782,7 @@ function show_settings2(data) data.settings.vss_select_components = unescapeHTML(data.settings.vss_select_components); group_membership_selectpicker=true; + data.settings.internet_server_url_placeholder = getServerUrlPlaceholder(); data.settings.settings_inv=dustRender("settings_inv_row", data.settings); ndata+=dustRender(is_group ? "settings_group" : "settings_user", data.settings); @@ -6090,12 +6094,26 @@ g.maximize_or_minimize = function(refresh) } } +function getServerUrlPlaceholder() +{ + var site_url = (location.protocol == "http:" ? "ws:" : "wss:") + "//" + location.host + location.pathname; + + if(site_url.substr(site_url.length-1)!="/") + { + site_url+="/"; + } + + site_url+="socket"; + + return site_url; +} + function addNewClient1() { if(!startLoading()) return; stopLoading(); - var ndata=dustRender("add_client", {server_identity: g.server_identity, server_pubkey: g.server_pubkey}); + var ndata=dustRender("add_client", {server_identity: g.server_identity, server_pubkey: g.server_pubkey, no_internet_server: g.no_internet_server, internet_server_url_placeholder: getServerUrlPlaceholder()}); if(g.data_f!=ndata) { @@ -6113,9 +6131,16 @@ function addNewClient2() { return; } + + var pars = "clientname="+encodeURIComponent(I("internet_client_name").value) + + if(I("internet_server_url")) + { + pars += getInternetSettings(); + } if(!startLoading()) return; - new getJSON("add_client", "clientname="+encodeURIComponent(I("internet_client_name").value), addNewClient3); + new getJSON("add_client", pars, addNewClient3); } else { diff --git a/urbackupserver/www/templates/add_client.htm b/urbackupserver/www/templates/add_client.htm index 4b386bafe..26b0b6e13 100644 --- a/urbackupserver/www/templates/add_client.htm +++ b/urbackupserver/www/templates/add_client.htm @@ -26,6 +26,12 @@
    + {no_internet_server} +
    + +
    +
    + {/no_internet_server}
    diff --git a/urbackupserver/www/templates/client_added.htm b/urbackupserver/www/templates/client_added.htm index 5f19a151a..b73dd6fa4 100644 --- a/urbackupserver/www/templates/client_added.htm +++ b/urbackupserver/www/templates/client_added.htm @@ -1,6 +1,11 @@
    {tClient added successfully}
    + {empty_internet_server_url} +
    + {tYou added an Internet/active client but did not specify a server URL. The client won't be able to connect to the server until you specify the server URL.} +
    + {/empty_internet_server_url}

    {tAdded new client with name:} {new_clientname|s}

    diff --git a/urbackupserver/www/templates/settings_inv_row.htm b/urbackupserver/www/templates/settings_inv_row.htm index a6d154f7d..d223f5932 100644 --- a/urbackupserver/www/templates/settings_inv_row.htm +++ b/urbackupserver/www/templates/settings_inv_row.htm @@ -525,7 +525,7 @@
    - +
    From 831976e76c7d8aaaf079a8e5368d2fcefffa6bf8 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 23 Feb 2026 14:24:56 +0100 Subject: [PATCH 424/469] Compiled and fixed templates --- urbackupserver/www/js/templates.js | 7 ++++--- urbackupserver/www/templates/add_client.htm | 2 +- urbackupserver/www/templates/client_added.htm | 2 +- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/urbackupserver/www/js/templates.js b/urbackupserver/www/js/templates.js index 102c8d030..69f28a91f 100644 --- a/urbackupserver/www/js/templates.js +++ b/urbackupserver/www/js/templates.js @@ -1,5 +1,5 @@ (function(){dust.register("about_urbackup",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAbout UrBackup"], false),ctx,"h").w("
    UrBackup Server ").f(ctx.get(["version"], false),ctx,"h").w("

    Authors:
    Translators:
    Martin Raiber, Ettore Atalan (German)
    Luis Miguel Muñoz (Spanish)
    Mehmet Binici (Turkish)
    Jussi Bergström (Finnish)
    mehdincd, Charles Peltier (French)
    Samuele, Paolo, Marco Longo (Italian)
    buzzertnl, Pimmetje, buzzertnl (Dutch)
    Artur Corumba, Juan Pablo Kerber (Portuguese (Brazil))
    J. Almeida (Portuguese)
    matsr (Norwegian)
    janda (Slovak)
    Jonas Aaslund (Svedish)
    Ales Hermann (Czech)
    Artem Alabin (Russian)
    Olivian Daniel Tofan (Romanian)
    Ihor Maydanovich (Ukrainian)
    osiengine group (Farsi)
    Zhengyu Ren, Johnny Xing, 五月鸢飞 (Traditional and Simplified Chinese)
    Czeslaw Mruk, JarosÅ‚aw Gorzelnik, Krzysztof PaÅ‚ka, Åukasz Milata, Maciej Dyczko, Thomas Pancherz, Wojciech Staszewski (Polish)



    UrBackup is using following libraries/code:
    UrBackup License:
    \"AGPLv3+\"/
    UrBackup is licensed as AGPLv3+. See here or the server-license.txt in your software distribution for the full license text of UrBackup and the licenses of used third-party software.
    The source code of this server software instance is available for example at https://github.com/uroni/urbackup_backend.

    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("



    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("add_client",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tAdd client"], false),ctx,"h").w("

    ").f(ctx.get(["tUrBackup automatically discovers clients in your local network. If the server is in the same sub-network as the client just install the client and wait for it to be discovered."], false),ctx,"h").w("

    ").f(ctx.get(["tDownload the client from:"], false),ctx,"h").w(" www.urbackup.org

    ").f(ctx.get(["tIf you want a client to use multiple backup servers this server's identity is:"], false),ctx,"h").w(" ").f(ctx.get(["server_identity"], false),ctx,"h").w("

    ").f(ctx.get(["tFor security reasons check/add following line in the file server_idents.txt on your client:"], false),ctx,"h").w("

    ").f(ctx.get(["server_pubkey"], false),ctx,"h",["s"]).w("


    ").x(ctx.get(["no_internet_server"], false),ctx,{"block":body_1},{}).w("

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tEdit alert scripts"], false),ctx,"h").w("
     

    ").f(ctx.get(["tAlert script parameters"], false),ctx,"h").w("

    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("



    ").f(ctx.get(["tAlert script"], false),ctx,"h").w("

    \t\t

    ").x(ctx.get(["saved_ok"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    Saved script successfully.
    ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("alert_script_edit_params",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tName:"], false),ctx,"h").w("
    ").f(ctx.get(["tLabel:"], false),ctx,"h").w("
    ").f(ctx.get(["tDefault value:"], false),ctx,"h").w("
    ").f(ctx.get(["tType:"], false),ctx,"h").w("
     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("backup_item",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["show_client_breadcrumb"], false),ctx,{"block":body_1},{}).w("").f(ctx.get(["clientname"], false),ctx,"h").w(" > ").f(ctx.get(["cpath"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["can_restore"], false),ctx,{"block":body_2},{}).w("").s(ctx.get(["items"], false),ctx,{"block":body_3},{}).w("
     ").f(ctx.get(["tFile"], false),ctx,"h").w("").f(ctx.get(["tSize"], false),ctx,"h").w("").f(ctx.get(["tCreated"], false),ctx,"h").w("").f(ctx.get(["tLast modified"], false),ctx,"h").w("").f(ctx.get(["tLast accessed"], false),ctx,"h").w("").f(ctx.get(["tBackup time"], false),ctx,"h").w("").f(ctx.get(["tVersion"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["tClients"], false),ctx,"h").w(" >");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w(" ").f(ctx.get(["name"], false),ctx,"h",["s"]).w("").f(ctx.get(["size"], false),ctx,"h",["s"]).w("").f(ctx.get(["creat"], false),ctx,"h",["s"]).w("").f(ctx.get(["mod"], false),ctx,"h",["s"]).w("").f(ctx.get(["access"], false),ctx,"h",["s"]).w("").f(ctx.get(["backuptime"], false),ctx,"h",["s"]).w("").x(ctx.get(["has_version"], false),ctx,{"block":body_4},{}).x(ctx.get(["can_restore"], false),ctx,{"block":body_5},{}).w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("").f(ctx.get(["version"], false),ctx,"h").w("");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("").f(ctx.get(["tRestore"], false),ctx,"h").w("");}body_5.__dustBody=!0;return body_0;})(); @@ -14,7 +14,7 @@ (function(){dust.register("change_pw",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChange password"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_fail",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanging password failed:"], false),ctx,"h").w("
    ").f(ctx.get(["fail_reason"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("change_pw_ok",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tChanged password successfully"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); -(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h",["s"]).w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h",["s"]).w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the URL to connect to:"], false),ctx,"h").w(" ").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tSet the name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("client_added",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tClient added successfully"], false),ctx,"h").w("
    ").x(ctx.get(["empty_internet_server_url"], false),ctx,{"block":body_1},{}).w("

    ").f(ctx.get(["tAdded new client with name:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("

    ").f(ctx.get(["tDefault authentication key:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("

    • ").f(ctx.get(["tDownload preconfigured client installer for Windows"], false),ctx,"h").w("
    • ").f(ctx.get(["tDownload preconfigured client installer for Linux"], false),ctx,"h").w("

      ").f(ctx.get(["tInstall it directly in the terminal via:"], false),ctx,"h").w("

      TF=`mktemp` && wget \"").f(ctx.get(["linux_url"], false),ctx,"h",["s"]).w("\" -O $TF && sudo sh $TF; rm -f $TF

      ").f(ctx.get(["tWith Docker (web interface accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"").f(ctx.get(["linux_url"], false),ctx,"h",["s"]).w("\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

      ").f(ctx.get(["tWith Docker (web interface not accessible from client):"], false),ctx,"h").w("

      RUN TF=`mktemp` &&\\
      wget \"https://hndl.urbackup.org/Client/latest/update/UrBackupUpdateLinux.sh\" -O $TF &&\\
      sh $TF &&\\
      rm -f $TF &&\\
      urbackupclientctl wait-for-backend &&\\
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w(" &&\\
      ( [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient ) &&\\
      ( [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient )

    • ").f(ctx.get(["tAlternatively after you installed the client from:"], false),ctx,"h").w(" https://www.urbackup.org/download.html

      • ").f(ctx.get(["tGo to the settings screen on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tEnable the internet mode on the client"], false),ctx,"h").w("
      • ").f(ctx.get(["tSet the URL to connect to:"], false),ctx,"h").w(" ").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tSet the name to:"], false),ctx,"h").w(" ").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tSet the authentication key to:"], false),ctx,"h").w(" ").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("
      • ").f(ctx.get(["tWithout firewall/NAT: Enable internet only mode if you only plan to use the client via internet. On Linux by changing INTERNET_ONLY to true in /etc/default/urbackupclient or /etc/sysconfig/urbackupclient"], false),ctx,"h").w("

      ").f(ctx.get(["tWith the command line:"], false),ctx,"h").w("

      urbackupclientctl wait-for-backend
      urbackupclientctl set-settings --server-url \"").f(ctx.get(["server_url"], false),ctx,"h",["s"]).w("\" --name \"").f(ctx.get(["new_clientname"], false),ctx,"h",["s"]).w("\" --authkey \"").f(ctx.get(["new_authkey"], false),ctx,"h",["s"]).w("\"").f(ctx.get(["internet_proxy_settings"], false),ctx,"h").w("
      [ ! -e /etc/default/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/default/urbackupclient
      [ ! -e /etc/sysconfig/urbackupclient ] || sed -i 's/INTERNET_ONLY=false/INTERNET_ONLY=true/' /etc/sysconfig/urbackupclient

    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ").f(ctx.get(["tYou added an Internet/active client but did not specify a server URL. The client won't be able to connect to the server until you specify the server URL."], false),ctx,"h").w("
    ");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("database_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["database_error_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("dir_error",body_0);function body_0(chk,ctx){return chk.w("
    ").x(ctx.get(["generic_text"], false),ctx,{"block":body_1},{}).f(ctx.get(["ext_text"], false),ctx,"h",["s"]).x(ctx.get(["stop_show_key"], false),ctx,{"block":body_2},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.f(ctx.get(["dir_error_text"], false),ctx,"h");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("

    ").f(ctx.get(["tOk. Stop showing this error"], false),ctx,"h").w("");}body_2.__dustBody=!0;return body_0;})(); (function(){dust.register("file_cache_error",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["creating_filesindex_text"], false),ctx,"h").w("
    ").f(ctx.get(["tNumber of file entries processed"], false),ctx,"h").w(": ").f(ctx.get(["processed_file_entries"], false),ctx,"h").w("
    ").f(ctx.get(["tPercent finished"], false),ctx,"h").w(": ").f(ctx.get(["percent_finished"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); @@ -36,6 +36,7 @@ (function(){dust.register("main_nav",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("main_nav_sel",body_0);function body_0(chk,ctx){return chk.w("
  • ").f(ctx.get(["name"], false),ctx,"h").w("
  • ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("new_version_available",body_0);function body_0(chk,ctx){return chk.f(ctx.get(["tThere is a new version of UrBackup server available"], false),ctx,"h").w(" (").f(ctx.get(["new_version_number"], false),ctx,"h").w("). Download it here.
    ").f(ctx.get(["tOk. Stop showing this."], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); +(function(){dust.register("no_users",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["no_users_text"], false),ctx,"h").x(ctx.get(["stop_show_key"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("

    ").f(ctx.get(["tGo to user settings"], false),ctx,"h").w("
    ").f(ctx.get(["tOk. Stop showing this warning"], false),ctx,"h").w("");}body_1.__dustBody=!0;return body_0;})(); (function(){dust.register("nospc_fatal",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["nospc_fatal_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("nospc_stalled",body_0);function body_0(chk,ctx){return chk.w("\t\t\t
    ").f(ctx.get(["nospc_stalled_text"], false),ctx,"h").w("

    ").f(ctx.get(["tOk. Reset this error"], false),ctx,"h").w("


    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("progress_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["name"], false),ctx,"h").w("").f(ctx.get(["action"], false),ctx,"h").w("").x(ctx.get(["image"], false),ctx,{"else":body_1,"block":body_6},{}).x(ctx.get(["show_details"], false),ctx,{"block":body_7},{}).x(ctx.get(["backups_interrupted"], false),ctx,{"block":body_8},{}).w("
    ").x(ctx.get(["percent"], false),ctx,{"block":body_10},{}).w("
    ").x(ctx.get(["indexing"], false),ctx,{"block":body_11},{}).w("
    ").x(ctx.get(["f_total_bytes"], false),ctx,{"block":body_12},{}).w("").f(ctx.get(["eta"], false),ctx,"h").w("").x(ctx.get(["paused"], false),ctx,{"else":body_13,"block":body_14},{}).w("").f(ctx.get(["queue"], false),ctx,"h").w("").x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_15},{}).x(ctx.get(["can_stop_backup"], false),ctx,{"block":body_16},{}).x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_18},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["client_update"], false),ctx,{"else":body_2,"block":body_5},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.x(ctx.get(["file_restore"], false),ctx,{"else":body_3,"block":body_4},{});}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("-");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.f(ctx.get(["tPath:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h",["s"]);}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.f(ctx.get(["tTo version:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["tVolume:"], false),ctx,"h").w(" ").f(ctx.get(["details"], false),ctx,"h");}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.f(ctx.get(["details"], false),ctx,"h");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.w("
    ").f(ctx.get(["tBackups interrupted"], false),ctx,"h");}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("min-width: 2em;");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.f(ctx.get(["pcdone"], false),ctx,"h").w("%");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.f(ctx.get(["tIndexing..."], false),ctx,"h");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.w("
    ").f(ctx.get(["f_done_bytes"], false),ctx,"h").w(" / ").f(ctx.get(["f_total_bytes"], false),ctx,"h").w("
    ");}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.f(ctx.get(["speed"], false),ctx,"h");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.f(ctx.get(["tPaused"], false),ctx,"h");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.x(ctx.get(["can_show_backup_log"], false),ctx,{"block":body_17},{});}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w(" ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("");}body_18.__dustBody=!0;return body_0;})(); @@ -46,7 +47,7 @@ (function(){dust.register("settings_archive_row",body_0);function body_0(chk,ctx){return chk.w("").f(ctx.get(["archive_every"], false),ctx,"h").w("").f(ctx.get(["archive_for"], false),ctx,"h").w("").f(ctx.get(["archive_window"], false),ctx,"h").w("").f(ctx.get(["archive_backup_type_str"], false),ctx,"h").w("").f(ctx.get(["archive_letters_str"], false),ctx,"h").w("").x(ctx.get(["show_archive_timeleft"], false),ctx,{"block":body_1},{}).w("").x(ctx.get(["source_group"], false),ctx,{"block":body_2},{}).x(ctx.get(["source_here"], false),ctx,{"block":body_3},{}).w("");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("").f(ctx.get(["archive_timeleft"], false),ctx,"h").w("");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("disabled");}body_4.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_general",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["ONLY_WIN32_BEGIN"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["ONLY_WIN32_END"], false),ctx,"h",["s"]).w("
    MBit/s
     
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("

     
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_group",body_0);function body_0(chk,ctx){return chk.w("

    ").f(ctx.get(["tGroup"], false),ctx,"h").w(" ").f(ctx.get(["groupname"], false),ctx,"h").w("

    Reset:
    \t\t\t\t\t\t
    \" onclick=\"addClientToGroup()\" />

    \t\t\t\t\t\t\t\t\t\t
    ").f(ctx.get(["settings_inv"], false),ctx,"h",["s"]).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("");}body_1.__dustBody=!0;return body_0;})(); -(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    \t
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); +(function(){dust.register("settings_inv_row",body_0);function body_0(chk,ctx){return chk.x(ctx.get(["client_settings"], false),ctx,{"else":body_1,"block":body_2},{}).w("
    ").f(ctx.get(["thours"], false),ctx,"h").w("
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    \t\t\t\t
    ").f(ctx.get(["tdays"], false),ctx,"h").w("
    ").f(ctx.get(["tDays"], false),ctx,"h").w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_4},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_5},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_6},{}).w("\t\t\t").x(ctx.get(["main_client"], false),ctx,{"block":body_7},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_8},{}).w("
    ").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tArchive every"], false),ctx,"h").w("").f(ctx.get(["tArchive for"], false),ctx,"h").w("").f(ctx.get(["tArchive window"], false),ctx,"h").w(" ?").f(ctx.get(["tBackup type"], false),ctx,"h").w("").f(ctx.get(["tVolume letters"], false),ctx,"h").w("").f(ctx.get(["tNext archival"], false),ctx,"h").w("  
     ").x(ctx.get(["archive_global"], false),ctx,{"block":body_9},{}).f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]).w("\t\t
    ").x(ctx.get(["can_edit_scripts"], false),ctx,{"block":body_10},{}).w("
    \t\t
    ").f(ctx.get(["mod_alert_params"], false),ctx,"h",["s"]).w("
    \t
    MBit/s
    ").f(ctx.get(["internet_settings_start"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_11},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_12},{}).w("
    KBit/s
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_15},{}).x(ctx.get(["main_client"], false),ctx,{"block":body_16},{}).w("
    ").x(ctx.get(["main_client"], false),ctx,{"block":body_17},{}).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").x(ctx.get(["global_settings"], false),ctx,{"block":body_18},{}).w("
    ").f(ctx.get(["internet_settings_end"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    \t\t\t
    ").f(ctx.get(["global_settings_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["tMB"], false),ctx,"h").w("
    ").f(ctx.get(["global_settings_end"], false),ctx,"h",["s"]).w("
    ").x(ctx.get(["client_settings"], false),ctx,{"block":body_19},{});}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.w("
    ");}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("");}body_3.__dustBody=!0;function body_4(chk,ctx){return chk.w("
    ");}body_4.__dustBody=!0;function body_5(chk,ctx){return chk.w("
    ").f(ctx.get(["tMin"], false),ctx,"h").w("
    ");}body_5.__dustBody=!0;function body_6(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_6.__dustBody=!0;function body_7(chk,ctx){return chk.w("
    ");}body_7.__dustBody=!0;function body_8(chk,ctx){return chk.f(ctx.get(["no_compname_start"], false),ctx,"h",["s"]).w("
    ").f(ctx.get(["no_compname_end"], false),ctx,"h",["s"]);}body_8.__dustBody=!0;function body_9(chk,ctx){return chk.w("");}body_9.__dustBody=!0;function body_10(chk,ctx){return chk.w("").f(ctx.get(["tEdit scripts"], false),ctx,"h").w("");}body_10.__dustBody=!0;function body_11(chk,ctx){return chk.w("
    ");}body_11.__dustBody=!0;function body_12(chk,ctx){return chk.nx(ctx.get(["global_settings"], false),ctx,{"block":body_13},{}).x(ctx.get(["with_authkey"], false),ctx,{"block":body_14},{});}body_12.__dustBody=!0;function body_13(chk,ctx){return chk.w("
    ");}body_13.__dustBody=!0;function body_14(chk,ctx){return chk.w("
    ");}body_14.__dustBody=!0;function body_15(chk,ctx){return chk.w("
    KBit/s
    ");}body_15.__dustBody=!0;function body_16(chk,ctx){return chk.w("
    ");}body_16.__dustBody=!0;function body_17(chk,ctx){return chk.w("
    ");}body_17.__dustBody=!0;function body_18(chk,ctx){return chk.w("
    ");}body_18.__dustBody=!0;function body_19(chk,ctx){return chk.w("
    ");}body_19.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_ldap",body_0);function body_0(chk,ctx){return chk.w("
    LDAP/AD login is currently undergoing development and testing. Please do not expect it to work.
    ").x(ctx.get(["test_login"], false),ctx,{"block":body_1},{}).w("
    ");}body_0.__dustBody=!0;function body_1(chk,ctx){return chk.x(ctx.get(["test_login_ok"], false),ctx,{"else":body_2,"block":body_3},{});}body_1.__dustBody=!0;function body_2(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_err"], false),ctx,"h").w("
    ");}body_2.__dustBody=!0;function body_3(chk,ctx){return chk.w("
    ").f(ctx.get(["tTest login succeeded. Rights of user:"], false),ctx,"h").w(" ").f(ctx.get(["ldap_rights"], false),ctx,"h").w("
    ");}body_3.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail",body_0);function body_0(chk,ctx){return chk.w("
    ");}body_0.__dustBody=!0;return body_0;})(); (function(){dust.register("settings_mail_test_failed",body_0);function body_0(chk,ctx){return chk.w("
    ").f(ctx.get(["tSending test mail failed. Error:"], false),ctx,"h").w(" ").f(ctx.get(["mail_err"], false),ctx,"h").w("
    ");}body_0.__dustBody=!0;return body_0;})(); diff --git a/urbackupserver/www/templates/add_client.htm b/urbackupserver/www/templates/add_client.htm index 26b0b6e13..564176f95 100644 --- a/urbackupserver/www/templates/add_client.htm +++ b/urbackupserver/www/templates/add_client.htm @@ -26,7 +26,7 @@
    - {no_internet_server} + {?no_internet_server}

    diff --git a/urbackupserver/www/templates/client_added.htm b/urbackupserver/www/templates/client_added.htm index b73dd6fa4..ce921e87e 100644 --- a/urbackupserver/www/templates/client_added.htm +++ b/urbackupserver/www/templates/client_added.htm @@ -1,7 +1,7 @@
    {tClient added successfully}
    - {empty_internet_server_url} + {?empty_internet_server_url}
    {tYou added an Internet/active client but did not specify a server URL. The client won't be able to connect to the server until you specify the server URL.}
    From cb48cd20fe57716591f73537f1ff1831856626f8 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 24 Feb 2026 13:29:36 +0100 Subject: [PATCH 425/469] Fix archive window being used as letter This caused image backup archival to fail --- urbackupserver/dllmain.cpp | 17 ++++++++++++++++- urbackupserver/server_archive.cpp | 2 +- urbackupserver/serverinterface/settings.cpp | 13 ++++++++++--- urbackupserver/serverinterface/settings.h | 1 + 4 files changed, 28 insertions(+), 5 deletions(-) diff --git a/urbackupserver/dllmain.cpp b/urbackupserver/dllmain.cpp index cc5ac3160..a909968af 100644 --- a/urbackupserver/dllmain.cpp +++ b/urbackupserver/dllmain.cpp @@ -102,6 +102,7 @@ SStartupStatus startup_status; #include "../urbackupcommon/chunk_hasher.h" #include "LogReport.h" #include "WebSocketConnector.h" +#include "serverinterface/settings.h" #define MINIZ_NO_ZLIB_COMPATIBLE_NAMES #include "../common/miniz.h" @@ -2518,6 +2519,13 @@ bool upgrade67_68() return true; } +bool upgrade68_69() +{ + IDatabase* db = Server->getDatabase(Server->getThreadID(), URBACKUPDB_SERVER); + + return updateArchiveSettingsExternal(0, db); +} + void upgrade(void) { Server->destroyAllDatabases(); @@ -2539,7 +2547,7 @@ void upgrade(void) int ver=watoi(res_v[0]["tvalue"]); int old_v; - int max_v=68; + int max_v=69; { IScopedLock lock(startup_status.mutex); startup_status.target_db_version=max_v; @@ -2959,6 +2967,13 @@ void upgrade(void) } ++ver; break; + case 68: + if (!upgrade68_69()) + { + has_error = true; + } + ++ver; + break; default: break; } diff --git a/urbackupserver/server_archive.cpp b/urbackupserver/server_archive.cpp index 3b7aeec2d..4781c0f4d 100644 --- a/urbackupserver/server_archive.cpp +++ b/urbackupserver/server_archive.cpp @@ -386,7 +386,7 @@ void ServerAutomaticArchive::updateArchiveSettings(int clientid) std::string backup_type_str = params["backup_type_" + idx]; archive.backup_types = ServerAutomaticArchive::getBackupTypes(backup_type_str); archive.window = params["window_" + idx]; - archive.letters = params["window_" + idx]; + archive.letters = params["letters_" + idx]; archive.every_unit = params["every_unit_" + idx]; archive.for_unit = params["for_unit_" + idx]; archive.uuid = hexToBytes(params["uuid_" + idx]); diff --git a/urbackupserver/serverinterface/settings.cpp b/urbackupserver/serverinterface/settings.cpp index d9a6ca349..23d2077ae 100644 --- a/urbackupserver/serverinterface/settings.cpp +++ b/urbackupserver/serverinterface/settings.cpp @@ -552,8 +552,9 @@ void updateClientSettings(int t_clientid, str_map &POST, IDatabase *db) } } -void updateArchiveSettings(int clientid, IDatabase *db) +bool updateArchiveSettings(int clientid, IDatabase *db) { + bool ret = true; IQuery* q = db->Prepare("INSERT INTO settings_db.settings(key, value, clientid) VALUES ('archive_update', '1', ?)"); if (clientid <= 0) { @@ -562,15 +563,16 @@ void updateArchiveSettings(int clientid, IDatabase *db) for (size_t i = 0; i < res_ids.size(); ++i) { q->Bind(res_ids[i]["id"]); - q->Write(); + ret &= q->Write(); q->Reset(); } } else { q->Bind(clientid); - q->Write(); + ret &= q->Write(); } + return ret; } void updateOnlineClientSettings(IDatabase *db, int clientid) @@ -616,6 +618,11 @@ void saveGeneralSettingsExternal(str_map& POST, IDatabase* db, bool& changed_bac saveGeneralSettings(POST, db, backup_dao, server_settings, changed_backupfolder); } +bool updateArchiveSettingsExternal(int clientid, IDatabase* db) +{ + return updateArchiveSettings(clientid, db); +} + ACTION_IMPL(settings) { Helper helper(tid, &POST, &PARAMS); diff --git a/urbackupserver/serverinterface/settings.h b/urbackupserver/serverinterface/settings.h index 02f834b63..63383f736 100644 --- a/urbackupserver/serverinterface/settings.h +++ b/urbackupserver/serverinterface/settings.h @@ -3,3 +3,4 @@ #include "../../Interface/Database.h" void saveGeneralSettingsExternal(str_map& POST, IDatabase* db, bool& changed_backupfolder); +bool updateArchiveSettingsExternal(int clientid, IDatabase* db); \ No newline at end of file From 7d55bbecd6e78b7897ca078145b2cde95358d654 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 24 Feb 2026 13:46:31 +0100 Subject: [PATCH 426/469] Add new file to filters --- urbackupserver/urbackupserver.vcxproj.filters | 3 +++ 1 file changed, 3 insertions(+) diff --git a/urbackupserver/urbackupserver.vcxproj.filters b/urbackupserver/urbackupserver.vcxproj.filters index 8c5c9e4c9..8f6074994 100644 --- a/urbackupserver/urbackupserver.vcxproj.filters +++ b/urbackupserver/urbackupserver.vcxproj.filters @@ -710,5 +710,8 @@ Headerdateien + + serverinterface + \ No newline at end of file From e44664e67abe63773ba979e0b47ad20689562532 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 3 Mar 2026 17:23:35 +0100 Subject: [PATCH 427/469] Fix client build on Linux --- Makefile.am_client | 2 +- OpenSSLPipe.cpp | 5 +++++ OpenSSLPipe.h | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/Makefile.am_client b/Makefile.am_client index 7e661f60d..135842552 100644 --- a/Makefile.am_client +++ b/Makefile.am_client @@ -23,7 +23,7 @@ urbackupclientbackend_SOURCES += cryptoplugin/dllmain.cpp cryptoplugin/AESDecryp urbackupclientbackend_SOURCES += fsimageplugin/dllmain.cpp fsimageplugin/filesystem.cpp fsimageplugin/FSImageFactory.cpp fsimageplugin/pluginmgr.cpp fsimageplugin/vhdfile.cpp fsimageplugin/vhdxfile.cpp fsimageplugin/fs/ntfs.cpp fsimageplugin/fs/unknown.cpp fsimageplugin/CompressedFile.cpp fsimageplugin/LRUMemCache.cpp fsimageplugin/cowfile.cpp fsimageplugin/FileWrapper.cpp fsimageplugin/ClientBitmap.cpp fsimageplugin/partclone.cpp -urbackupclientbackend_SOURCES += urbackupclient/dllmain.cpp urbackupclient/clientdao.cpp urbackupclient/client.cpp urbackupclient/ClientService.cpp urbackupclient/ClientSend.cpp urbackupclient/client_restore.cpp urbackupclient/ServerIdentityMgr.cpp urbackupclient/ClientServiceCMD.cpp urbackupclient/ImageThread.cpp urbackupclient/InternetClient.cpp urbackupclient/file_permissions.cpp urbackupclient/lin_ver.cpp urbackupclient/lin_tokens.cpp urbackupclient/common_tokens.cpp urbackupclient/FileMetadataDownloadThread.cpp urbackupclient/RestoreFiles.cpp urbackupclient/RestoreDownloadThread.cpp urbackupclient/TokenCallback.cpp common/miniz.c urbackupclient/cmdline_preprocessor.cpp urbackupclient/ParallelHash.cpp urbackupclient/ClientHash.cpp +urbackupclientbackend_SOURCES += urbackupclient/dllmain.cpp urbackupclient/clientdao.cpp urbackupclient/client.cpp urbackupclient/ClientService.cpp urbackupclient/ClientSend.cpp urbackupclient/client_restore.cpp urbackupclient/ServerIdentityMgr.cpp urbackupclient/ClientServiceCMD.cpp urbackupclient/ImageThread.cpp urbackupclient/InternetClient.cpp urbackupclient/file_permissions.cpp urbackupclient/lin_ver.cpp urbackupclient/lin_tokens.cpp urbackupclient/common_tokens.cpp urbackupclient/FileMetadataDownloadThread.cpp urbackupclient/RestoreFiles.cpp urbackupclient/RestoreDownloadThread.cpp urbackupclient/TokenCallback.cpp common/miniz.c urbackupclient/cmdline_preprocessor.cpp urbackupclient/ParallelHash.cpp urbackupclient/ClientHash.cpp urbackupclient/SambaService.cpp urbackupclientbackend_SOURCES += fileservplugin/dllmain.cpp fileservplugin/bufmgr.cpp fileservplugin/CClientThread.cpp fileservplugin/CriticalSection.cpp fileservplugin/CTCPFileServ.cpp fileservplugin/CUDPThread.cpp fileservplugin/FileServ.cpp fileservplugin/FileServFactory.cpp fileservplugin/log.cpp fileservplugin/main.cpp fileservplugin/map_buffer.cpp fileservplugin/pluginmgr.cpp fileservplugin/ChunkSendThread.cpp fileservplugin/PipeFile.cpp fileservplugin/PipeSessions.cpp fileservplugin/PipeFileUnix.cpp fileservplugin/PipeFileBase.cpp fileservplugin/FileMetadataPipe.cpp fileservplugin/PipeFileTar.cpp fileservplugin/PipeFileExt.cpp diff --git a/OpenSSLPipe.cpp b/OpenSSLPipe.cpp index 1e4e05ac3..4cbc6ca9a 100644 --- a/OpenSSLPipe.cpp +++ b/OpenSSLPipe.cpp @@ -564,4 +564,9 @@ bool OpenSSLPipe::setCompressionSettings(const SCompressionSettings& params) return false; } +bool OpenSSLPipe::setOption(const SocketOption opt) +{ + bpipe->setOption(opt); +} + #endif //WITH_OPENSSL diff --git a/OpenSSLPipe.h b/OpenSSLPipe.h index ccd19355f..1005ea5b1 100644 --- a/OpenSSLPipe.h +++ b/OpenSSLPipe.h @@ -63,6 +63,7 @@ class OpenSSLPipe : public IPipe virtual bool setCompressionSettings(const SCompressionSettings& params); + bool setOption(const SocketOption opt); private: std::auto_ptr bpipe; From 2f24e50217234f33996fdf43d36b3f41a077b8e8 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 3 Mar 2026 17:23:50 +0100 Subject: [PATCH 428/469] Fix client build on Linux --- urbackupclient/lin_tokens.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/urbackupclient/lin_tokens.cpp b/urbackupclient/lin_tokens.cpp index dd75a176a..6e0a5d85f 100644 --- a/urbackupclient/lin_tokens.cpp +++ b/urbackupclient/lin_tokens.cpp @@ -709,4 +709,10 @@ std::string accountname_normalize(const std::string& accountname) return accountname; } +bool write_smb_pw(const std::string& fn, const std::string& accountname, const std::string& data) +{ + //Noop + return false; +} + } //namespace tokens From 4ad9ac999b8c5759194422612bd08b80adbdc52c Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 3 Mar 2026 21:42:27 +0100 Subject: [PATCH 429/469] Add btrfs and vfat image backup support --- Makefile.am_client | 2 +- fsimageplugin/partclone.cpp | 4 +- install_client_linux.sh | 8 +- linux_snapshot/dm_create_snapshot | 156 +----------------- linux_snapshot/dm_create_snapshot_common | 197 +++++++++++++++++++++++ linux_snapshot/dm_create_volume_snapshot | 6 + linux_snapshot/dm_remove_snapshot | 99 +----------- linux_snapshot/dm_remove_snapshot_common | 148 +++++++++++++++++ linux_snapshot/dm_remove_volume_snapshot | 6 + urbackupclient/lin_sysvol.h | 2 + 10 files changed, 370 insertions(+), 258 deletions(-) create mode 100755 linux_snapshot/dm_create_snapshot_common create mode 100755 linux_snapshot/dm_create_volume_snapshot create mode 100755 linux_snapshot/dm_remove_snapshot_common create mode 100755 linux_snapshot/dm_remove_volume_snapshot diff --git a/Makefile.am_client b/Makefile.am_client index 135842552..a630aa999 100644 --- a/Makefile.am_client +++ b/Makefile.am_client @@ -359,4 +359,4 @@ noinst_HEADERS=SessionMgr.h WorkerThread.h Helper_win32.h Database.h defaults.h EXTRA_DIST_GUI = client/info.txt client/data/backup-bad.xpm client/data/backup-ok.xpm client/data/backup-progress.xpm client/data/backup-progress-pause.xpm client/data/backup-no-server.xpm client/data/backup-no-recent.xpm client/data/backup-indexing.xpm client/data/logo1.png client/data/lang/it/urbackup.mo client/data/lang/pl/urbackup.mo client/data/lang/pt_BR/urbackup.mo client/data/lang/sk/urbackup.mo client/data/lang/zh_TW/urbackup.mo client/data/lang/zh_CN/urbackup.mo client/data/lang/de/urbackup.mo client/data/lang/es/urbackup.mo client/data/lang/fr/urbackup.mo client/data/lang/ru/urbackup.mo client/data/lang/uk/urbackup.mo client/data/lang/da/urbackup.mo client/data/lang/nl/urbackup.mo client/data/lang/fa/urbackup.mo client/data/lang/cs/urbackup.mo client/gui/GUISetupWizard.h client/SetupWizard.h client/fa-copy.png client/fa-home.png client/fa-lock.png client/fa-road.png -EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/list_incr urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbxtrabackup_incr urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat +EXTRA_DIST=docs/urbackupclientbackend.1 init.d_client init.d_client_rh defaults_client $(EXTRA_DIST_GUI) tclap/COPYING tclap/AUTHORS urbackupclientbackend-debian.service urbackupclientbackend-redhat.service urbackupclient/backup_scripts/list urbackupclient/backup_scripts/list_incr urbackupclient/backup_scripts/mariadbdump.conf urbackupclient/backup_scripts/mariadbdump urbackupclient/backup_scripts/postgresqldump.conf urbackupclient/backup_scripts/postgresqldump urbackupclient/backup_scripts/postgresbase urbackupclient/backup_scripts/postgresqlprebackup urbackupclient/backup_scripts/postgresqlpostbackup urbackupclient/backup_scripts/setup-postgresbackup urbackupclient/backup_scripts/postgresbase.conf urbackupclient/backup_scripts/mariadbxtrabackup.conf urbackupclient/backup_scripts/mariadbxtrabackup urbackupclient/backup_scripts/mariadbxtrabackup_incr urbackupclient/backup_scripts/mariadbprebackup urbackupclient/backup_scripts/mariadbpostbackup urbackupclient/backup_scripts/setup-mariadbbackup urbackupclient/backup_scripts/restore-mariadbbackup client/version.txt client/data/urbackup_ecdsa409k1.pub linux_snapshot/btrfs_create_filesystem_snapshot linux_snapshot/btrfs_remove_filesystem_snapshot linux_snapshot/dattobd_create_snapshot linux_snapshot/dattobd_remove_snapshot linux_snapshot/lvm_create_filesystem_snapshot linux_snapshot/lvm_remove_filesystem_snapshot client/data/updates_h.dat linux_snapshot/dm_create_snapshot linux_snapshot/dm_remove_snapshot linux_snapshot/dm_create_volume_snapshot linux_snapshot/dm_remove_volume_snapshot linux_snapshot/dm_create_snapshot_common linux_snapshot/dm_remove_snapshot_common diff --git a/fsimageplugin/partclone.cpp b/fsimageplugin/partclone.cpp index d3077f701..d03b62c2f 100644 --- a/fsimageplugin/partclone.cpp +++ b/fsimageplugin/partclone.cpp @@ -155,9 +155,9 @@ void Partclone::init() Server->Log("Detected fs type "+fstype); if(fstype!="ext2" && fstype!="ext3" - && fstype!="ext4" && fstype!="xfs") + && fstype!="ext4" && fstype!="xfs" && fstype!="btrfs") { - Server->Log("Fs type not supported"); + Server->Log("Fs type not supported: "+fstype); has_error = true; return; } diff --git a/install_client_linux.sh b/install_client_linux.sh index 3c35c19ca..dcccfb525 100755 --- a/install_client_linux.sh +++ b/install_client_linux.sh @@ -241,6 +241,10 @@ install -c "dattobd_create_snapshot" "$PREFIX/share/urbackup" install -c "dattobd_remove_snapshot" "$PREFIX/share/urbackup" install -c "dm_create_snapshot" "$PREFIX/share/urbackup" install -c "dm_remove_snapshot" "$PREFIX/share/urbackup" +install -c "dm_create_snapshot_common" "$PREFIX/share/urbackup" +install -c "dm_cremove_snapshot_common" "$PREFIX/share/urbackup" +install -c "dm_create_volume_snapshot" "$PREFIX/share/urbackup" +install -c "dm_remove_volume_snapshot" "$PREFIX/share/urbackup" install -c "filesystem_snapshot_common" "$PREFIX/share/urbackup" test -e "$PREFIX/etc/urbackup/mariadbdump.conf" || install -c "backup_scripts/mariadbdump.conf" "$PREFIX/etc/urbackup" @@ -647,8 +651,8 @@ then then CREATE_SNAPSHOT_SCRIPT="$PREFIX/share/urbackup/dm_create_snapshot" REMOVE_SNAPSHOT_SCRIPT="$PREFIX/share/urbackup/dm_remove_snapshot" - CREATE_VOLUME_SNAPSHOT="$PREFIX/share/urbackup/dm_create_snapshot" - REMOVE_VOLUME_SNAPSHOT="$PREFIX/share/urbackup/dm_remove_snapshot" + CREATE_VOLUME_SNAPSHOT="$PREFIX/share/urbackup/dm_create_volume_snapshot" + REMOVE_VOLUME_SNAPSHOT="$PREFIX/share/urbackup/dm_remove_volume_snapshot" if [ $DEBIAN = yes ] || [ $UBUNTU = yes ] then diff --git a/linux_snapshot/dm_create_snapshot b/linux_snapshot/dm_create_snapshot index 882237fcc..eff886b1b 100755 --- a/linux_snapshot/dm_create_snapshot +++ b/linux_snapshot/dm_create_snapshot @@ -2,159 +2,5 @@ set -e -mkdir -p /mnt/urbackup_snaps - CDIR=`dirname $0` - -SNAP_ID=$1 -SNAP_MOUNTPOINT="$2" -SNAP_DEST=/mnt/urbackup_snaps/$SNAP_ID - -CDIR=`dirname $0` -. $CDIR/filesystem_snapshot_common -exit_exclude_snapshot_mountpoints "$SNAP_MOUNTPOINT" - -DEVICE=$(df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f1) -set_filesystem_type "${SNAP_MOUNTPOINT}" - -if [ "x$TYPE" = "x" ] -then - if btrfs subvolume list -o "$SNAP_MOUNTPOINT" > /dev/null 2>&1 - then - TYPE="btrfs" - fi -fi - -if [ "x$TYPE" = "xbtrfs" ] -then - $CDIR/btrfs_create_filesystem_snapshot "$@" - exit $? -fi - -if [ "x$TYPE" != "xxfs" ] && [ "x$TYPE" != "xext4" ] -then - echo "File system $TYPE not supported" - exit 1 -fi - -if [ "x$DEVICE" = "x" ] -then - echo "Cannot get device for filesystem $SNAP_MOUNTPOINT" - exit 1 -fi - -add_to_updatedb_conf "/mnt/urbackup_snaps" - -echo "Snapshotting device $DEVICE via dm..." - -if ! dmsetup table "$DEVICE" > /dev/null 2>&1 -then - echo "$DEVICE is not a device mapper device. Cannot snapshot via dm." - exit 1 -fi - -modprobe dm_snapshot - -DEV_SIZE=$(blockdev --getsz "$DEVICE") - -if [ "x$DEV_SIZE" = "x" ] -then - echo "Cannot get device size of device $DEVICE" - exit 1 -fi - -DEVNAME=$(basename $DEVICE) - -RUUID="a31725acca86421d" - -ORIG_DEVICE="/dev/mapper/$DEVNAME-$RUUID-clone" -ERA_META_FN=".era-meta_3d41c58e-6724-4d47-8981-11c766a08a24" - -ERA_RESET=0 -if ! command -v era_dump >/dev/null 2>&1 -then - echo "thin-provisioning-tools not installed. CBT not enabled." -elif modprobe dm_era && ! dmsetup table "$DEVICE" | grep " era " > /dev/null 2>&1 && ! dmsetup table "$DEVICE" | grep " snapshot-origin " > /dev/null 2>&1 -then - echo "Layering in dm-era device..." - META_SIZE=$(( (((DEV_SIZE*4)/1024)/512)*512 + 3*1024*1024 )) - if [ $META_SIZE -lt $((4*1024*1024)) ] - then - META_SIZE=$((4*1024*1024)) - fi - - if [ -e "$SNAP_MOUNTPOINT/$ERA_META_FN" ] - then - chattr -i "$SNAP_MOUNTPOINT/$ERA_META_FN" || true - rm "$SNAP_MOUNTPOINT/$ERA_META_FN" - fi - - fallocate -l $META_SIZE "$SNAP_MOUNTPOINT/$ERA_META_FN" - chattr +i "$SNAP_MOUNTPOINT/$ERA_META_FN" - echo "FLOCK_PERM=$SNAP_MOUNTPOINT/$ERA_META_FN" - - ORIG_DEVICE="/dev/mapper/$DEVNAME-$RUUID-clone-era" - dmsetup table "$DEVICE" | dmsetup create "$DEVNAME-$RUUID-clone-era" - urbackupclientbackend --internal --print-dm-file-extents "$SNAP_MOUNTPOINT/$ERA_META_FN" --file-dm-block-dev "$ORIG_DEVICE" | dmsetup create "$DEVNAME-$RUUID-era-metadata" - dmsetup table "$DEVNAME-$RUUID-era-metadata" | dmsetup create "$DEVNAME-$RUUID-era-metadata-access" - dd if=/dev/zero of=/dev/mapper/$DEVNAME-$RUUID-era-metadata bs=512 count=$(blockdev --getsz /dev/mapper/$DEVNAME-$RUUID-era-metadata) > /dev/null 2>&1 - echo "0 $DEV_SIZE era /dev/mapper/$DEVNAME-$RUUID-era-metadata $ORIG_DEVICE 1024" | dmsetup create "$DEVNAME-$RUUID-clone" - echo "CBT=type=era&reset=1" - ERA_RESET=1 -fi - -OVERLAY_FN="$SNAP_MOUNTPOINT/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" -if [ -e "$OVERLAY_FN" ] -then - chattr -i "$OVERLAY_FN" || true - rm "$OVERLAY_FN" -fi - -fallocate -l 5G "$OVERLAY_FN" -chattr +i "$OVERLAY_FN" -echo "FLOCK=$OVERLAY_FN" - -if ! [ -e "/dev/mapper/$DEVNAME-$RUUID-clone" ] -then - dmsetup table "$DEVICE" | dmsetup create "$DEVNAME-$RUUID-clone" -fi - -urbackupclientbackend --internal --print-dm-file-extents "$OVERLAY_FN" --file-dm-block-dev "$ORIG_DEVICE" | dmsetup create "$DEVNAME-$SNAP_ID-cow-storage" -# Needs to run with mlockall for root device -> cannot do that in bash -urbackupclient_dmsnaptool --dev "$DEVICE" --clone-dev "/dev/mapper/$DEVNAME-$RUUID-clone" --snap-dev "/dev/mapper/$DEVNAME-$SNAP_ID" --cow-dev "/dev/mapper/$DEVNAME-$SNAP_ID-cow-storage" --origin-dev "/dev/mapper/$DEVNAME-$RUUID-origin" --era-access-dev "/dev/mapper/$DEVNAME-$RUUID-era-metadata-access" --dev-size "$DEV_SIZE" - -truncate -s100M $SNAP_MOUNTPOINT/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap - -LODEV=`losetup -f` - -if [ "x$LODEV" = x ] -then - echo "TODO: Cleanup" - exit 1 -fi - -losetup $LODEV $SNAP_MOUNTPOINT/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap - -echo "0 $DEV_SIZE snapshot /dev/mapper/$DEVNAME-$SNAP_ID $LODEV N 8" | dmsetup create "$DEVNAME-$SNAP_ID-wsnap" - -echo "Mounting /dev/mapper/$DEVNAME-$SNAP_ID-wsnap..." - -MOUNTOPTS="ro" - -if [ $TYPE = "xfs" ] -then - MOUNTOPTS="ro,nouuid" -fi - -mkdir -p "$SNAP_DEST" - -if ! mount -o $MOUNTOPTS "/dev/mapper/$DEVNAME-$SNAP_ID-wsnap" "$SNAP_DEST" -then - echo "Mounting filesystem failed" - #TODO: CLEANUP - exit 1 -fi - -echo "$DEVNAME" > ${SNAP_DEST}-name -echo "/dev/mapper/$DEVNAME-$SNAP_ID" > ${SNAP_DEST}-dev -echo "SNAPSHOT=$SNAP_DEST" +$CDIR/dm_create_snapshot_common fs "$@" \ No newline at end of file diff --git a/linux_snapshot/dm_create_snapshot_common b/linux_snapshot/dm_create_snapshot_common new file mode 100755 index 000000000..7ed4db7cc --- /dev/null +++ b/linux_snapshot/dm_create_snapshot_common @@ -0,0 +1,197 @@ +#!/bin/sh + +set -e + +mkdir -p /mnt/urbackup_snaps + +CDIR=`dirname $0` + +VOLUME_SNAP=0 +if [ "x$1" = "xvol" ] +then + VOLUME_SNAP=1 +elif [ "x$1" != "xfs" ] +then + echo "First parameter must be either 'fs' or 'vol'" + exit 1 +fi +shift + +SNAP_ID=$1 +SNAP_MOUNTPOINT="$2" +SNAP_DEST=/mnt/urbackup_snaps/$SNAP_ID + +CDIR=`dirname $0` +. $CDIR/filesystem_snapshot_common +exit_exclude_snapshot_mountpoints "$SNAP_MOUNTPOINT" + +DEVICE=$(df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f1) +set_filesystem_type "${SNAP_MOUNTPOINT}" +DEVNAME=$(basename $DEVICE) + + +if [ "x$TYPE" = "x" ] +then + if btrfs subvolume list -o "$SNAP_MOUNTPOINT" > /dev/null 2>&1 + then + TYPE="btrfs" + fi +fi + +if [ $VOLUME_SNAP = 0 ] && [ "x$TYPE" = "xbtrfs" ] +then + $CDIR/btrfs_create_filesystem_snapshot "$@" + exit $? +fi + +if [ "x$TYPE" = "xvfat" ] +then + mount -o remount,ro "$SNAP_MOUNTPOINT" + echo "$DEVICE" > "$SNAP_MOUNTPOINT-dev" + echo "vfat" > "$SNAP_MOUNTPOINT-name" + echo "SNAPSHOT=$SNAP_MOUNTPOINT" + exit 0 +fi + + +if [ "x$TYPE" != "xxfs" ] && [ "x$TYPE" != "xext4" ] && [ "x$TYPE" != "xbtrfs" ] +then + echo "File system $TYPE not supported" + exit 1 +fi + +if [ "x$DEVICE" = "x" ] +then + echo "Cannot get device for filesystem $SNAP_MOUNTPOINT" + exit 1 +fi + +echo "Snapshotting device $DEVICE via dm..." + +if ! dmsetup table "$DEVICE" > /dev/null 2>&1 +then + echo "$DEVICE is not a device mapper device. Cannot snapshot via dm." + exit 1 +fi + +modprobe dm_snapshot + +DEV_SIZE=$(blockdev --getsz "$DEVICE") + +if [ "x$DEV_SIZE" = "x" ] +then + echo "Cannot get device size of device $DEVICE" + exit 1 +fi + + +RUUID="a31725acca86421d" + +ORIG_DEVICE="/dev/mapper/$DEVNAME-$RUUID-clone" +ERA_META_FN=".era-meta_3d41c58e-6724-4d47-8981-11c766a08a24" + +ERA_RESET=0 +if ! command -v era_dump >/dev/null 2>&1 +then + echo "thin-provisioning-tools not installed. CBT not enabled." +elif modprobe dm_era && ! dmsetup table "$DEVICE" | grep " era " > /dev/null 2>&1 && ! dmsetup table "$DEVICE" | grep " snapshot-origin " > /dev/null 2>&1 +then + echo "Layering in dm-era device..." + META_SIZE=$(( (((DEV_SIZE*4)/1024)/512)*512 + 3*1024*1024 )) + if [ $META_SIZE -lt $((4*1024*1024)) ] + then + META_SIZE=$((4*1024*1024)) + fi + + if [ -e "$SNAP_MOUNTPOINT/$ERA_META_FN" ] + then + chattr -i "$SNAP_MOUNTPOINT/$ERA_META_FN" || true + rm "$SNAP_MOUNTPOINT/$ERA_META_FN" + fi + + if [ "$TYPE" = "btrfs" ] + then + touch "$SNAP_MOUNTPOINT/$ERA_META_FN" + chattr +C "$SNAP_MOUNTPOINT/$ERA_META_FN" + fi + + fallocate -l $META_SIZE "$SNAP_MOUNTPOINT/$ERA_META_FN" + chattr +i "$SNAP_MOUNTPOINT/$ERA_META_FN" + echo "FLOCK_PERM=$SNAP_MOUNTPOINT/$ERA_META_FN" + + ORIG_DEVICE="/dev/mapper/$DEVNAME-$RUUID-clone-era" + dmsetup table "$DEVICE" | dmsetup create "$DEVNAME-$RUUID-clone-era" + urbackupclientbackend --internal --print-dm-file-extents "$SNAP_MOUNTPOINT/$ERA_META_FN" --file-dm-block-dev "$ORIG_DEVICE" | dmsetup create "$DEVNAME-$RUUID-era-metadata" + dmsetup table "$DEVNAME-$RUUID-era-metadata" | dmsetup create "$DEVNAME-$RUUID-era-metadata-access" + dd if=/dev/zero of=/dev/mapper/$DEVNAME-$RUUID-era-metadata bs=512 count=$(blockdev --getsz /dev/mapper/$DEVNAME-$RUUID-era-metadata) > /dev/null 2>&1 + echo "0 $DEV_SIZE era /dev/mapper/$DEVNAME-$RUUID-era-metadata $ORIG_DEVICE 1024" | dmsetup create "$DEVNAME-$RUUID-clone" + echo "CBT=type=era&reset=1" + ERA_RESET=1 +fi + +OVERLAY_FN="$SNAP_MOUNTPOINT/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" +if [ -e "$OVERLAY_FN" ] +then + chattr -i "$OVERLAY_FN" || true + rm "$OVERLAY_FN" +fi + +if [ "$TYPE" = "btrfs" ] +then + touch "$OVERLAY_FN" + chattr +C "$OVERLAY_FN" +fi + +fallocate -l 5G "$OVERLAY_FN" +chattr +i "$OVERLAY_FN" +echo "FLOCK=$OVERLAY_FN" + +if ! [ -e "/dev/mapper/$DEVNAME-$RUUID-clone" ] +then + dmsetup table "$DEVICE" | dmsetup create "$DEVNAME-$RUUID-clone" +fi + +urbackupclientbackend --internal --print-dm-file-extents "$OVERLAY_FN" --file-dm-block-dev "$ORIG_DEVICE" | dmsetup create "$DEVNAME-$SNAP_ID-cow-storage" +# Needs to run with mlockall for root device -> cannot do that in bash +urbackupclient_dmsnaptool --dev "$DEVICE" --clone-dev "/dev/mapper/$DEVNAME-$RUUID-clone" --snap-dev "/dev/mapper/$DEVNAME-$SNAP_ID" --cow-dev "/dev/mapper/$DEVNAME-$SNAP_ID-cow-storage" --origin-dev "/dev/mapper/$DEVNAME-$RUUID-origin" --era-access-dev "/dev/mapper/$DEVNAME-$RUUID-era-metadata-access" --dev-size "$DEV_SIZE" + +truncate -s100M $SNAP_MOUNTPOINT/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap + +LODEV=`losetup -f` + +if [ "x$LODEV" = x ] +then + echo "TODO: Cleanup" + exit 1 +fi + +losetup $LODEV $SNAP_MOUNTPOINT/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap + +echo "0 $DEV_SIZE snapshot /dev/mapper/$DEVNAME-$SNAP_ID $LODEV N 8" | dmsetup create "$DEVNAME-$SNAP_ID-wsnap" + +echo "Mounting /dev/mapper/$DEVNAME-$SNAP_ID-wsnap..." + +MOUNTOPTS="ro" + +if [ $TYPE = "xfs" ] +then + MOUNTOPTS="ro,nouuid" +elif [ $TYPE = "btrfs" ] && [ $VOLUME_SNAP = 0 ] +then + btrfstune -m "/dev/mapper/$DEVNAME-$SNAP_ID-wsnap" +fi + +mkdir -p "$SNAP_DEST" +if [ $VOLUME_SNAP = 0 ] +then + if ! mount -o $MOUNTOPTS "/dev/mapper/$DEVNAME-$SNAP_ID-wsnap" "$SNAP_DEST" + then + echo "Mounting filesystem failed" + #TODO: CLEANUP + exit 1 + fi +fi + +echo "$DEVNAME" > ${SNAP_DEST}-name +echo "/dev/mapper/$DEVNAME-$SNAP_ID" > ${SNAP_DEST}-dev +echo "SNAPSHOT=$SNAP_DEST" diff --git a/linux_snapshot/dm_create_volume_snapshot b/linux_snapshot/dm_create_volume_snapshot new file mode 100755 index 000000000..89dcdcae4 --- /dev/null +++ b/linux_snapshot/dm_create_volume_snapshot @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +CDIR=`dirname $0` +$CDIR/dm_create_snapshot_common vol "$@" \ No newline at end of file diff --git a/linux_snapshot/dm_remove_snapshot b/linux_snapshot/dm_remove_snapshot index 3ce16e05d..4189ab016 100755 --- a/linux_snapshot/dm_remove_snapshot +++ b/linux_snapshot/dm_remove_snapshot @@ -2,102 +2,5 @@ set -e -SNAP_ID=$1 -SNAP_MOUNTPOINT="$2" -SNAP_ORIG_PATH="$5" - CDIR=`dirname $0` - -remove_overlay() { - if test -e "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" - then - chattr -i "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" - rm "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" - fi - - if test -e "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap" - then - LODEV=`losetup -j "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap" | cut -d':' -f1` - if [ "x$LODEV" != x ] - then - losetup -d "$LODEV" - fi - rm "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap" - fi -} - -remove_dm() { - if ! [ -e "${SNAP_MOUNTPOINT}-name" ] - then - echo "Could not find snapshot device name at ${SNAP_MOUNTPOINT}-name. Cannot remove dm nodes" - return 1 - fi - - DEVNAME=$(cat "${SNAP_MOUNTPOINT}-name") - - echo "Removing dm snapshot..." - - dmsetup remove "$DEVNAME-$SNAP_ID-wsnap" || true - dmsetup remove "$DEVNAME-$SNAP_ID" || true - - echo "Removing snapshot cow storage..." - dmsetup remove "$DEVNAME-$SNAP_ID-cow-storage" || true - - rm "${SNAP_MOUNTPOINT}-name" - rm "${SNAP_MOUNTPOINT}-dev" - rmdir "${SNAP_MOUNTPOINT}" -} - -if ! test -e $SNAP_MOUNTPOINT -then - echo "Snapshot at $SNAP_MOUNTPOINT was already removed" - remove_dm - remove_overlay - exit 0 -fi - -TYPE=$(df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f2) - -if [ "x$TYPE" = "x" ] -then - if btrfs subvolume list -o "$SNAP_MOUNTPOINT" > /dev/null 2>&1 - then - TYPE="btrfs" - fi -fi - -if [ "x$TYPE" = "xbtrfs" ] -then - $CDIR/btrfs_remove_filesystem_snapshot "$@" - exit $? -fi - -if ! df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" > /dev/null 2>&1 -then - echo "Snapshot is not mounted. Already removed" - remove_dm - remove_overlay - exit 0 -fi - -if ! [ -e "${SNAP_MOUNTPOINT}-name" ] -then - echo "Could not find snapshot device name at ${SNAP_MOUNTPOINT}-name" - exit 1 -fi - -DEVNAME=$(cat "${SNAP_MOUNTPOINT}-name") - -echo "Unmounting /dev/mapper/$DEVNAME-$SNAP_ID at /mnt/urbackup_snaps/$SNAP_ID..." - -if ! umount /mnt/urbackup_snaps/$SNAP_ID -then - lsof | grep /mnt/urbackup_snaps/$SNAP_ID || true - echo "Unmounting /mnt/urbackup_snaps/$SNAP_ID failed. Retrying in 10s..." - sleep 10 - umount /mnt/urbackup_snaps/$SNAP_ID -fi - -remove_dm -remove_overlay -exit 0 +$CDIR/dm_remove_snapshot_common fs "$@" \ No newline at end of file diff --git a/linux_snapshot/dm_remove_snapshot_common b/linux_snapshot/dm_remove_snapshot_common new file mode 100755 index 000000000..8565d4c21 --- /dev/null +++ b/linux_snapshot/dm_remove_snapshot_common @@ -0,0 +1,148 @@ +#!/bin/sh + +set -e + +VOLUME_SNAP=0 +if [ "x$1" = "xvol" ] +then + VOLUME_SNAP=1 +elif [ "x$1" != "xfs" ] +then + echo "First parameter must be either 'fs' or 'vol'" + exit 1 +fi +shift + +SNAP_ID=$1 +SNAP_MOUNTPOINT="$2" +SNAP_ORIG_PATH="$5" + +CDIR=`dirname $0` + +remove_overlay() { + if test -e "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" + then + chattr -i "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" + rm "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID" + fi + + if test -e "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap" + then + LODEV=`losetup -j "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap" | cut -d':' -f1` + if [ "x$LODEV" != x ] + then + losetup -d "$LODEV" + fi + rm "$SNAP_ORIG_PATH/.overlay_2fefd007-3e48-4162-b2c6-45ccdda22f37_$SNAP_ID-wsnap" + fi +} + +remove_dm() { + if ! [ -e "${SNAP_MOUNTPOINT}-name" ] + then + if [ $VOLUME_SNAP = 1 ] + then + TYPE=$(df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f2) + if [ "x$TYPE" = "xvfat" ] + then + echo "Snapshot is an vfat partition and no name found. No dm nodes to remove" + return 0 + fi + fi + + echo "Could not find snapshot device name at ${SNAP_MOUNTPOINT}-name. Cannot remove dm nodes" + return 1 + fi + + DEVNAME=$(cat "${SNAP_MOUNTPOINT}-name") + + if [ "x$DEVNAME" = "xvfat" ] + then + echo "Snapshot is an vfat partition. No dm nodes to remove" + rm "${SNAP_MOUNTPOINT}-name" || true + rm "${SNAP_MOUNTPOINT}-dev" || true + return 0 + fi + + echo "Removing dm snapshot..." + + dmsetup remove "$DEVNAME-$SNAP_ID-wsnap" || true + dmsetup remove "$DEVNAME-$SNAP_ID" || true + + echo "Removing snapshot cow storage..." + dmsetup remove "$DEVNAME-$SNAP_ID-cow-storage" || true + + rm "${SNAP_MOUNTPOINT}-name" + rm "${SNAP_MOUNTPOINT}-dev" + rmdir "${SNAP_MOUNTPOINT}" +} + +if ! test -e $SNAP_MOUNTPOINT +then + echo "Snapshot at $SNAP_MOUNTPOINT was already removed" + remove_dm + remove_overlay + exit 0 +fi + +TYPE=$(df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" | head -n 1 | tr -s " " | cut -d" " -f2) + +if [ "x$TYPE" = "x" ] +then + if [ $VOLUME_SNAP = 1 ] + then + echo "Removing volume snapshot $SNAP_MOUNTPOINT..." + remove_dm + remove_overlay + exit 0 + fi + + if btrfs subvolume list -o "$SNAP_MOUNTPOINT" > /dev/null 2>&1 + then + TYPE="btrfs" + fi +fi + +if [ "x$TYPE" = "xbtrfs" ] && [ $VOLUME_SNAP = 0 ] +then + $CDIR/btrfs_remove_filesystem_snapshot "$@" + exit $? +fi + +if ! df -T -P | egrep " ${SNAP_MOUNTPOINT}\$" > /dev/null 2>&1 +then + echo "Snapshot is not mounted. Already removed" + remove_dm + remove_overlay + exit 0 +fi + +if [ "x$TYPE" = "xvfat" ] +then + mount -o remount,rw "$SNAP_MOUNTPOINT" + remove_dm + remove_overlay + exit 0 +fi + +if ! [ -e "${SNAP_MOUNTPOINT}-name" ] +then + echo "Could not find snapshot device name at ${SNAP_MOUNTPOINT}-name" + exit 1 +fi + +DEVNAME=$(cat "${SNAP_MOUNTPOINT}-name") + +echo "Unmounting /dev/mapper/$DEVNAME-$SNAP_ID at /mnt/urbackup_snaps/$SNAP_ID..." + +if ! umount /mnt/urbackup_snaps/$SNAP_ID +then + lsof | grep /mnt/urbackup_snaps/$SNAP_ID || true + echo "Unmounting /mnt/urbackup_snaps/$SNAP_ID failed. Retrying in 10s..." + sleep 10 + umount /mnt/urbackup_snaps/$SNAP_ID +fi + +remove_dm +remove_overlay +exit 0 diff --git a/linux_snapshot/dm_remove_volume_snapshot b/linux_snapshot/dm_remove_volume_snapshot new file mode 100755 index 000000000..579ed1916 --- /dev/null +++ b/linux_snapshot/dm_remove_volume_snapshot @@ -0,0 +1,6 @@ +#!/bin/sh + +set -e + +CDIR=`dirname $0` +$CDIR/dm_remove_snapshot_common vol "$@" \ No newline at end of file diff --git a/urbackupclient/lin_sysvol.h b/urbackupclient/lin_sysvol.h index be63636b1..8a004eaac 100644 --- a/urbackupclient/lin_sysvol.h +++ b/urbackupclient/lin_sysvol.h @@ -48,6 +48,8 @@ namespace std::string getEspVolumeCached(std::string& mpath) { mpath = getMountDevice("/boot/efi"); + if(mpath.empty()) + mpath = getMountDevice("/efi"); return mpath; } From c9957df038b302215c7fe36c36285069ebf10ce9 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 3 Mar 2026 21:49:12 +0100 Subject: [PATCH 430/469] Disable running backups in background per default --- urbackupclient/client.cpp | 4 ++-- urbackupserver/server_settings.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/urbackupclient/client.cpp b/urbackupclient/client.cpp index 0f31e5a47..a89215ada 100644 --- a/urbackupclient/client.cpp +++ b/urbackupclient/client.cpp @@ -5806,10 +5806,10 @@ bool IndexThread::backgroundBackupsEnabled(const std::string& clientsubname) if(curr_settings->getValue("background_backups", &background_backups) || curr_settings->getValue("background_backups_def", &background_backups) ) { - return background_backups!="false"; + return background_backups=="true"; } } - return true; + return false; } void IndexThread::writeTokens() diff --git a/urbackupserver/server_settings.cpp b/urbackupserver/server_settings.cpp index 66351de0c..0b2ec9664 100644 --- a/urbackupserver/server_settings.cpp +++ b/urbackupserver/server_settings.cpp @@ -408,7 +408,7 @@ void ServerSettings::readSettingsDefault(ISettingsReader* settings_default, settings->internet_readd_file_entries = true; settings->max_running_jobs_per_client = 1; settings->create_linked_user_views = false; - settings->background_backups = true; + settings->background_backups = false; settings->local_incr_image_style = incr_image_style_to_full; settings->local_full_image_style = full_image_style_full; settings->internet_incr_image_style = incr_image_style_to_last; From 17e8f9f9c55faeb83137f572db6851c37b7b8574 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 3 Mar 2026 21:49:57 +0100 Subject: [PATCH 431/469] Double check there is no user on login Double check there is no user before allowing anonymous login --- urbackupserver/serverinterface/login.cpp | 39 +++++++++++++----------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/urbackupserver/serverinterface/login.cpp b/urbackupserver/serverinterface/login.cpp index 896b286a5..7bb5355ca 100644 --- a/urbackupserver/serverinterface/login.cpp +++ b/urbackupserver/serverinterface/login.cpp @@ -219,24 +219,29 @@ ACTION_IMPL(login) } else { - ret.set("success", JSON::Value(true) ); - if(!has_session) + db_results res_users_c = db->Read("SELECT COUNT(*) AS c FROM settings_db.si_users"); + if (!res_users_c.empty() + && watoi(res_users_c[0]["c"]) == 0) { - ses=helper.generateSession("anonymous"); - POST["ses"]=ses; - ret.set("session", JSON::Value(ses)); - helper.update(tid, &POST, &PARAMS); - } - SUser *session=helper.getSession(); - if(session!=NULL) - { - logSuccessfulLogin(helper, PARAMS, "anonymous", LoginMethod_Webinterface); - session->mStr["login"]="ok"; - session->id=SESSION_ID_ADMIN; - } - else - { - ret.set("error", JSON::Value(1)); + ret.set("success", JSON::Value(true)); + if (!has_session) + { + ses = helper.generateSession("anonymous"); + POST["ses"] = ses; + ret.set("session", JSON::Value(ses)); + helper.update(tid, &POST, &PARAMS); + } + SUser* session = helper.getSession(); + if (session != NULL) + { + logSuccessfulLogin(helper, PARAMS, "anonymous", LoginMethod_Webinterface); + session->mStr["login"] = "ok"; + session->id = SESSION_ID_ADMIN; + } + else + { + ret.set("error", JSON::Value(1)); + } } } } From 26f2695ab1d4d94f5510ee547715aa9de8116d5d Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 3 Mar 2026 21:52:13 +0100 Subject: [PATCH 432/469] Fix permission issue allowing access to last actions of clients Fix issue allowing users with a valid session (logged in) but no permission to access any last actions of any client to access last actions of all clients --- urbackupserver/serverinterface/lastacts.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/serverinterface/lastacts.cpp b/urbackupserver/serverinterface/lastacts.cpp index b03cc2d48..bb1f77c1c 100644 --- a/urbackupserver/serverinterface/lastacts.cpp +++ b/urbackupserver/serverinterface/lastacts.cpp @@ -143,7 +143,7 @@ ACTION_IMPL(lastacts) } } - if(session!=NULL && (rights=="all" || clientids.empty()) ) + if(session!=NULL && (rights=="all" || !clientids.empty()) ) { getLastActs(helper, ret, clientids); } From 3e47d9f2ee052e40baad59f63a2b2201579bb9ce Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 5 Mar 2026 18:41:23 +0100 Subject: [PATCH 433/469] Increment version --- configure.ac_server | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configure.ac_server b/configure.ac_server index 8337791ec..18ea816b3 100644 --- a/configure.ac_server +++ b/configure.ac_server @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ(2.61) -AC_INIT([urbackup-server], [2.5.35.0], [martin@urbackup.org]) +AC_INIT([urbackup-server], [2.5.36.0], [martin@urbackup.org]) AC_CONFIG_SRCDIR([AcceptThread.cpp]) AC_CONFIG_HEADER([config.h]) AC_CONFIG_MACRO_DIR([m4]) From f1445f114a51f2491bab894c979fa57827bb65d8 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 5 Mar 2026 18:42:02 +0100 Subject: [PATCH 434/469] Increment version --- urbackupserver/www/js/urbackup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 2da934fd1..728ec97aa 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -5,7 +5,7 @@ g.startup=true; g.no_tab_mouse_click=false; g.tabberidx=-1; g.progress_stop_id=-1; -g.current_version=2005003500; +g.current_version=2005003600; g.status_show_all=false; g.ldap_login=false; g.datatable_default_config={}; From 1c3d75513a2ea20abeccc22c5d7e69c5185f8b0d Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 5 Mar 2026 20:43:21 +0100 Subject: [PATCH 435/469] Fix internet server id --- urbackupserver/www/js/urbackup.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 728ec97aa..36c6fd384 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -6134,7 +6134,7 @@ function addNewClient2() var pars = "clientname="+encodeURIComponent(I("internet_client_name").value) - if(I("internet_server_url")) + if(I("internet_server")) { pars += getInternetSettings(); } From 4b745effb2d77fc38fb67988d05a38bbb10c7838 Mon Sep 17 00:00:00 2001 From: Martin Date: Thu, 5 Mar 2026 20:52:35 +0100 Subject: [PATCH 436/469] Fix proxy validation issue --- urbackupserver/www/js/urbackup.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/urbackupserver/www/js/urbackup.js b/urbackupserver/www/js/urbackup.js index 36c6fd384..de7b0a0e1 100644 --- a/urbackupserver/www/js/urbackup.js +++ b/urbackupserver/www/js/urbackup.js @@ -4559,7 +4559,10 @@ function getInternetSettings() pars+="&internet_server="+encodeURIComponent(internet_server_par); pars+="&internet_server_port="+encodeURIComponent(internet_server_port); - if(!validate_text_regex([{ id: "internet_server_proxy", regexp: /(^(http|https):\/\/[\w-]+([\w-]*)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$)|(^$)/i }])) return null; + if(I("internet_server_proxy")) + { + if(!validate_text_regex([{ id: "internet_server_proxy", regexp: /(^(http|https):\/\/[\w-]+([\w-]*)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$)|(^$)/i }])) return null; + } for(var i=0;i Date: Tue, 10 Mar 2026 14:49:52 +0100 Subject: [PATCH 437/469] Get configuration from environment as well --- urbackupserver/cmdline_preprocessor.cpp | 313 +++++++++--------------- 1 file changed, 115 insertions(+), 198 deletions(-) diff --git a/urbackupserver/cmdline_preprocessor.cpp b/urbackupserver/cmdline_preprocessor.cpp index fea63904e..33fbc66b6 100644 --- a/urbackupserver/cmdline_preprocessor.cpp +++ b/urbackupserver/cmdline_preprocessor.cpp @@ -125,12 +125,38 @@ std::string unquote_value(std::string val) } #ifndef _WIN32 +bool get_setting_value_with_env(ISettingsReader* settings, const std::string& key, std::string& value, const bool do_trim) +{ + if (settings && settings->getValue(key, &value)) + { + value = unquote_value(value); + if (do_trim) + { + value = trim(value); + } + return !value.empty(); + } + + std::string env_key = "URBACKUP_" + key; + const char* env_value = getenv(env_key.c_str()); + if (env_value != NULL) + { + value = unquote_value(env_value); + if (do_trim) + { + value = trim(value); + } + return !value.empty(); + } + return false; +} + void read_config_file(std::string fn, std::vector& real_args) { - if (!FileExists(fn)) + const bool config_present = !fn.empty() && FileExists(fn); + if (!fn.empty() && !config_present) { std::cout << "Config file at " << fn << " does not exist. Ignoring." << std::endl; - return; } bool destroy_server=false; @@ -141,114 +167,69 @@ void read_config_file(std::string fn, std::vector& real_args) } { - std::auto_ptr settings(Server->createFileSettingsReader(fn)); + std::auto_ptr settings(config_present ? Server->createFileSettingsReader(fn) : NULL); std::string val; - if(settings->getValue("FASTCGI_PORT", &val)) + if(get_setting_value_with_env(settings.get(), "FASTCGI_PORT", &val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--port"); - real_args.push_back(val); - } + real_args.push_back("--port"); + real_args.push_back(val); } - if(settings->getValue("HTTP_PORT", &val)) + if(get_setting_value_with_env(settings.get(), "HTTP_PORT", &val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--http_port"); - real_args.push_back(val); - } + real_args.push_back("--http_port"); + real_args.push_back(val); } - if(settings->getValue("LOGFILE", &val)) + if(get_setting_value_with_env(settings.get(), "LOGFILE", &val)) { - val = unquote_value(val); - - if(!val.empty()) + if(val[0]!='/') { - if(val[0]!='/') - { - val = "/var/log/"+val; - } - real_args.push_back("--logfile"); - real_args.push_back(val); + val = "/var/log/"+val; } + real_args.push_back("--logfile"); + real_args.push_back(val); } - if(settings->getValue("LOGLEVEL", &val)) + if(get_setting_value_with_env(settings.get(), "LOGLEVEL", &val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--loglevel"); - real_args.push_back(unquote_value(val)); - } + real_args.push_back("--loglevel"); + real_args.push_back(unquote_value(val)); } - if(settings->getValue("DAEMON_TMPDIR", &val)) + if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", &val)) { - std::string tmpdir = unquote_value(val); - if(!tmpdir.empty()) + if(setenv("TMPDIR", tmpdir.c_str(), 1)!=0) { - if(setenv("TMPDIR", tmpdir.c_str(), 1)!=0) - { - std::cout << "Error setting TMPDIR" << std::endl; - exit(1); - } + std::cout << "Error setting TMPDIR" << std::endl; + exit(1); } } - if(settings->getValue("SQLITE_TMPDIR", &val)) + if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", &val)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--sqlite_tmpdir"); - real_args.push_back(val); - } + real_args.push_back("--sqlite_tmpdir"); + real_args.push_back(val); } - if(settings->getValue("BROADCAST_INTERFACES", &val)) + if(get_setting_value_with_env(settings.get(), "BROADCAST_INTERFACES", &val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--broadcast_interfaces"); - real_args.push_back(val); - } + real_args.push_back("--broadcast_interfaces"); + real_args.push_back(val); } - if(settings->getValue("HTTP_SERVER", &val)) + if(get_setting_value_with_env(settings.get(), "HTTP_SERVER", &val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--http_server"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--http_server"); + real_args.push_back(strlower(val)); } - if (settings->getValue("HTTP_LOCALHOST_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "HTTP_LOCALHOST_ONLY", &val, true)) { - val = unquote_value(val); - - if (!val.empty() - && ( val=="1" || + if ( val=="1" || strlower(val)=="true" || - strlower(val)=="yes") ) + strlower(val)=="yes") { real_args.push_back("--http_localhost_only"); real_args.push_back("1"); } } - if (settings->getValue("FASTCGI_LOCALHOST_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "FASTCGI_LOCALHOST_ONLY", &val, true)) { - val = unquote_value(val); - - if (!val.empty() - && (val == "1" || + if (val == "1" || strlower(val) == "true" || strlower(val) == "yes")) { @@ -256,181 +237,113 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("1"); } } - if (settings->getValue("LOG_ROTATE_FILESIZE", &val)) + if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_FILESIZE", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--rotate-filesize"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--rotate-filesize"); + real_args.push_back(strlower(val)); } - if (settings->getValue("LOG_ROTATE_NUM", &val)) + if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_NUM", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--rotate-numfiles"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--rotate-numfiles"); + real_args.push_back(strlower(val)); } - if (settings->getValue("SQLITE_MMAP_HUGE", &val)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_HUGE", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--sqlite_mmap_huge"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--sqlite_mmap_huge"); + real_args.push_back(strlower(val)); } - if (settings->getValue("SQLITE_MMAP_MEDIUM", &val)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_MEDIUM", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--sqlite_mmap_medium"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--sqlite_mmap_medium"); + real_args.push_back(strlower(val)); } - if (settings->getValue("SQLITE_MMAP_SMALL", &val)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_SMALL", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--sqlite_mmap_small"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--sqlite_mmap_small"); + real_args.push_back(strlower(val)); } - if (settings->getValue("HTTP_PROXY", &val)) + if (get_setting_value_with_env(settings.get(), "HTTP_PROXY", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--http_proxy"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--http_proxy"); + real_args.push_back(strlower(val)); } - if (settings->getValue("USER", &val)) + if (get_setting_value_with_env(settings.get(), "USER", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--user"); - real_args.push_back(val); - } + real_args.push_back("--user"); + real_args.push_back(val); } - if (settings->getValue("INTERNET_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_ONLY", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - if (val == "1") val = "true"; - real_args.push_back("--internet_only_mode"); - real_args.push_back(strlower(val)); - } + if (val == "1") val = "true"; + real_args.push_back("--internet_only_mode"); + real_args.push_back(strlower(val)); } - if (settings->getValue("LUA_SANDBOX", &val)) + if (get_setting_value_with_env(settings.get(), "LUA_SANDBOX", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - if (val == "1") val = "true"; - real_args.push_back("--lua_sandbox"); - real_args.push_back(strlower(val)); - } + if (val == "1") val = "true"; + real_args.push_back("--lua_sandbox"); + real_args.push_back(strlower(val)); } - if (settings->getValue("INTERNET_MODE_DISABLED", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_MODE_DISABLED", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--internet_mode_disabled"); real_args.push_back("1"); } } - if (settings->getValue("INTERNET_PORT", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_PORT", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--internet_port"); - real_args.push_back(val); - } + real_args.push_back("--internet_port"); + real_args.push_back(val); } - if (settings->getValue("INTERNET_LOCALHOST_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_LOCALHOST_ONLY", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--internet_localhost_only"); real_args.push_back("1"); } } - if (settings->getValue("INTERNET_DISABLE_WEBSOCKET", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_DISABLE_WEBSOCKET", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--internet_disable_websocket"); real_args.push_back("1"); } } - if (settings->getValue("FAILED_LOGIN_RATELIMIT", &val)) + if (get_setting_value_with_env(settings.get(), "FAILED_LOGIN_RATELIMIT", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "0" || + if (val == "0" || strlower(val) == "false" || - strlower(val) == "no")) + strlower(val) == "no") { real_args.push_back("--failed_login_ratelimit"); real_args.push_back("0"); } } - if (settings->getValue("ALLOW_USER_ENUMERATION", &val)) + if (get_setting_value_with_env(settings.get(), "ALLOW_USER_ENUMERATION", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "0" || + if (val == "0" || strlower(val) == "false" || - strlower(val) == "no")) + strlower(val) == "no") { real_args.push_back("--allow_user_enumeration"); real_args.push_back("0"); } } - if (settings->getValue("LOCK_SESSION_TO_IP_AND_USER_AGENT", &val)) + if (get_setting_value_with_env(settings.get(), "LOCK_SESSION_TO_IP_AND_USER_AGENT", &val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--lock_session_to_ip_and_user_agent"); real_args.push_back("1"); @@ -521,6 +434,10 @@ int action_run(std::vector args) { read_config_file(config_arg.getValue(), real_args); } + else + { + read_config_file("", real_args); + } #endif if(std::find(real_args.begin(), real_args.end(), "--port")==real_args.end()) From 93c50262f8555b41a343310ef2a67f5b6858a39a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 10 Mar 2026 14:56:09 +0100 Subject: [PATCH 438/469] Fix build --- urbackupserver/cmdline_preprocessor.cpp | 52 ++++++++++++------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/urbackupserver/cmdline_preprocessor.cpp b/urbackupserver/cmdline_preprocessor.cpp index 33fbc66b6..fba5cabe2 100644 --- a/urbackupserver/cmdline_preprocessor.cpp +++ b/urbackupserver/cmdline_preprocessor.cpp @@ -169,17 +169,17 @@ void read_config_file(std::string fn, std::vector& real_args) { std::auto_ptr settings(config_present ? Server->createFileSettingsReader(fn) : NULL); std::string val; - if(get_setting_value_with_env(settings.get(), "FASTCGI_PORT", &val, true)) + if(get_setting_value_with_env(settings.get(), "FASTCGI_PORT", val, true)) { real_args.push_back("--port"); real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "HTTP_PORT", &val, true)) + if(get_setting_value_with_env(settings.get(), "HTTP_PORT", val, true)) { real_args.push_back("--http_port"); real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "LOGFILE", &val)) + if(get_setting_value_with_env(settings.get(), "LOGFILE", val)) { if(val[0]!='/') { @@ -188,12 +188,12 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("--logfile"); real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "LOGLEVEL", &val, true)) + if(get_setting_value_with_env(settings.get(), "LOGLEVEL", val, true)) { real_args.push_back("--loglevel"); real_args.push_back(unquote_value(val)); } - if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", &val)) + if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", val)) { if(setenv("TMPDIR", tmpdir.c_str(), 1)!=0) { @@ -201,23 +201,23 @@ void read_config_file(std::string fn, std::vector& real_args) exit(1); } } - if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", &val)) + if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", val)) { real_args.push_back("--sqlite_tmpdir"); real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "BROADCAST_INTERFACES", &val, true)) + if(get_setting_value_with_env(settings.get(), "BROADCAST_INTERFACES", val, true)) { real_args.push_back("--broadcast_interfaces"); real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "HTTP_SERVER", &val, true)) + if(get_setting_value_with_env(settings.get(), "HTTP_SERVER", val, true)) { real_args.push_back("--http_server"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "HTTP_LOCALHOST_ONLY", &val, true)) + if (get_setting_value_with_env(settings.get(), "HTTP_LOCALHOST_ONLY", val, true)) { if ( val=="1" || strlower(val)=="true" || @@ -227,7 +227,7 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "FASTCGI_LOCALHOST_ONLY", &val, true)) + if (get_setting_value_with_env(settings.get(), "FASTCGI_LOCALHOST_ONLY", val, true)) { if (val == "1" || strlower(val) == "true" || @@ -237,54 +237,54 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_FILESIZE", &val, true)) + if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_FILESIZE", val, true)) { real_args.push_back("--rotate-filesize"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_NUM", &val, true)) + if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_NUM", val, true)) { real_args.push_back("--rotate-numfiles"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_HUGE", &val, true)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_HUGE", val, true)) { real_args.push_back("--sqlite_mmap_huge"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_MEDIUM", &val, true)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_MEDIUM", val, true)) { real_args.push_back("--sqlite_mmap_medium"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_SMALL", &val, true)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_SMALL", val, true)) { real_args.push_back("--sqlite_mmap_small"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "HTTP_PROXY", &val, true)) + if (get_setting_value_with_env(settings.get(), "HTTP_PROXY", val, true)) { real_args.push_back("--http_proxy"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "USER", &val, true)) + if (get_setting_value_with_env(settings.get(), "USER", val, true)) { real_args.push_back("--user"); real_args.push_back(val); } - if (get_setting_value_with_env(settings.get(), "INTERNET_ONLY", &val, true)) + if (get_setting_value_with_env(settings.get(), "INTERNET_ONLY", val, true)) { if (val == "1") val = "true"; real_args.push_back("--internet_only_mode"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "LUA_SANDBOX", &val, true)) + if (get_setting_value_with_env(settings.get(), "LUA_SANDBOX", val, true)) { if (val == "1") val = "true"; real_args.push_back("--lua_sandbox"); real_args.push_back(strlower(val)); } - if (get_setting_value_with_env(settings.get(), "INTERNET_MODE_DISABLED", &val, true)) + if (get_setting_value_with_env(settings.get(), "INTERNET_MODE_DISABLED", val, true)) { if (val == "1" || strlower(val) == "true" || @@ -294,12 +294,12 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "INTERNET_PORT", &val, true)) + if (get_setting_value_with_env(settings.get(), "INTERNET_PORT", val, true)) { real_args.push_back("--internet_port"); real_args.push_back(val); } - if (get_setting_value_with_env(settings.get(), "INTERNET_LOCALHOST_ONLY", &val, true)) + if (get_setting_value_with_env(settings.get(), "INTERNET_LOCALHOST_ONLY", val, true)) { if (val == "1" || strlower(val) == "true" || @@ -309,7 +309,7 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "INTERNET_DISABLE_WEBSOCKET", &val, true)) + if (get_setting_value_with_env(settings.get(), "INTERNET_DISABLE_WEBSOCKET", val, true)) { if (val == "1" || strlower(val) == "true" || @@ -319,7 +319,7 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "FAILED_LOGIN_RATELIMIT", &val, true)) + if (get_setting_value_with_env(settings.get(), "FAILED_LOGIN_RATELIMIT", val, true)) { if (val == "0" || strlower(val) == "false" || @@ -329,7 +329,7 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("0"); } } - if (get_setting_value_with_env(settings.get(), "ALLOW_USER_ENUMERATION", &val, true)) + if (get_setting_value_with_env(settings.get(), "ALLOW_USER_ENUMERATION", val, true)) { if (val == "0" || strlower(val) == "false" || @@ -339,7 +339,7 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("0"); } } - if (get_setting_value_with_env(settings.get(), "LOCK_SESSION_TO_IP_AND_USER_AGENT", &val, true)) + if (get_setting_value_with_env(settings.get(), "LOCK_SESSION_TO_IP_AND_USER_AGENT", val, true)) { if (val == "1" || strlower(val) == "true" || From 1bf6a7da37053856f448d60e42bf4355c37eced0 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 10 Mar 2026 14:58:46 +0100 Subject: [PATCH 439/469] Fix build --- urbackupserver/cmdline_preprocessor.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/urbackupserver/cmdline_preprocessor.cpp b/urbackupserver/cmdline_preprocessor.cpp index fba5cabe2..9ee2c7342 100644 --- a/urbackupserver/cmdline_preprocessor.cpp +++ b/urbackupserver/cmdline_preprocessor.cpp @@ -179,7 +179,7 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("--http_port"); real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "LOGFILE", val)) + if(get_setting_value_with_env(settings.get(), "LOGFILE", val, false)) { if(val[0]!='/') { @@ -193,15 +193,15 @@ void read_config_file(std::string fn, std::vector& real_args) real_args.push_back("--loglevel"); real_args.push_back(unquote_value(val)); } - if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", val)) + if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", val, false)) { - if(setenv("TMPDIR", tmpdir.c_str(), 1)!=0) + if(setenv("TMPDIR", val.c_str(), 1)!=0) { std::cout << "Error setting TMPDIR" << std::endl; exit(1); } } - if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", val)) + if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", val, false)) { real_args.push_back("--sqlite_tmpdir"); real_args.push_back(val); @@ -231,7 +231,7 @@ void read_config_file(std::string fn, std::vector& real_args) { if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--fastcgi_localhost_only"); real_args.push_back("1"); From c0dd396738904bee30714b1d1b5bfd2479f9a54a Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 12 Mar 2026 10:40:45 +0100 Subject: [PATCH 440/469] Revert "Get configuration from environment as well" This reverts commit b0fa07d508af538ecf3f02f3e791b023eed7c1d4. --- urbackupserver/cmdline_preprocessor.cpp | 315 +++++++++++++++--------- 1 file changed, 199 insertions(+), 116 deletions(-) diff --git a/urbackupserver/cmdline_preprocessor.cpp b/urbackupserver/cmdline_preprocessor.cpp index 9ee2c7342..fea63904e 100644 --- a/urbackupserver/cmdline_preprocessor.cpp +++ b/urbackupserver/cmdline_preprocessor.cpp @@ -125,38 +125,12 @@ std::string unquote_value(std::string val) } #ifndef _WIN32 -bool get_setting_value_with_env(ISettingsReader* settings, const std::string& key, std::string& value, const bool do_trim) -{ - if (settings && settings->getValue(key, &value)) - { - value = unquote_value(value); - if (do_trim) - { - value = trim(value); - } - return !value.empty(); - } - - std::string env_key = "URBACKUP_" + key; - const char* env_value = getenv(env_key.c_str()); - if (env_value != NULL) - { - value = unquote_value(env_value); - if (do_trim) - { - value = trim(value); - } - return !value.empty(); - } - return false; -} - void read_config_file(std::string fn, std::vector& real_args) { - const bool config_present = !fn.empty() && FileExists(fn); - if (!fn.empty() && !config_present) + if (!FileExists(fn)) { std::cout << "Config file at " << fn << " does not exist. Ignoring." << std::endl; + return; } bool destroy_server=false; @@ -167,183 +141,296 @@ void read_config_file(std::string fn, std::vector& real_args) } { - std::auto_ptr settings(config_present ? Server->createFileSettingsReader(fn) : NULL); + std::auto_ptr settings(Server->createFileSettingsReader(fn)); std::string val; - if(get_setting_value_with_env(settings.get(), "FASTCGI_PORT", val, true)) + if(settings->getValue("FASTCGI_PORT", &val)) { - real_args.push_back("--port"); - real_args.push_back(val); + val = unquote_value(val); + + if(!val.empty()) + { + real_args.push_back("--port"); + real_args.push_back(val); + } } - if(get_setting_value_with_env(settings.get(), "HTTP_PORT", val, true)) + if(settings->getValue("HTTP_PORT", &val)) { - real_args.push_back("--http_port"); - real_args.push_back(val); + val = unquote_value(val); + + if(!val.empty()) + { + real_args.push_back("--http_port"); + real_args.push_back(val); + } } - if(get_setting_value_with_env(settings.get(), "LOGFILE", val, false)) + if(settings->getValue("LOGFILE", &val)) { - if(val[0]!='/') + val = unquote_value(val); + + if(!val.empty()) { - val = "/var/log/"+val; + if(val[0]!='/') + { + val = "/var/log/"+val; + } + real_args.push_back("--logfile"); + real_args.push_back(val); } - real_args.push_back("--logfile"); - real_args.push_back(val); } - if(get_setting_value_with_env(settings.get(), "LOGLEVEL", val, true)) + if(settings->getValue("LOGLEVEL", &val)) { - real_args.push_back("--loglevel"); - real_args.push_back(unquote_value(val)); + val = unquote_value(val); + + if(!val.empty()) + { + real_args.push_back("--loglevel"); + real_args.push_back(unquote_value(val)); + } } - if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", val, false)) + if(settings->getValue("DAEMON_TMPDIR", &val)) { - if(setenv("TMPDIR", val.c_str(), 1)!=0) + std::string tmpdir = unquote_value(val); + if(!tmpdir.empty()) { - std::cout << "Error setting TMPDIR" << std::endl; - exit(1); + if(setenv("TMPDIR", tmpdir.c_str(), 1)!=0) + { + std::cout << "Error setting TMPDIR" << std::endl; + exit(1); + } } } - if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", val, false)) + if(settings->getValue("SQLITE_TMPDIR", &val)) { - real_args.push_back("--sqlite_tmpdir"); - real_args.push_back(val); + val = unquote_value(val); + + if(!val.empty()) + { + real_args.push_back("--sqlite_tmpdir"); + real_args.push_back(val); + } } - if(get_setting_value_with_env(settings.get(), "BROADCAST_INTERFACES", val, true)) + if(settings->getValue("BROADCAST_INTERFACES", &val)) { - real_args.push_back("--broadcast_interfaces"); - real_args.push_back(val); + val = unquote_value(val); + + if(!val.empty()) + { + real_args.push_back("--broadcast_interfaces"); + real_args.push_back(val); + } } - if(get_setting_value_with_env(settings.get(), "HTTP_SERVER", val, true)) + if(settings->getValue("HTTP_SERVER", &val)) { - real_args.push_back("--http_server"); - real_args.push_back(strlower(val)); + val = unquote_value(val); + + if(!val.empty()) + { + real_args.push_back("--http_server"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "HTTP_LOCALHOST_ONLY", val, true)) + if (settings->getValue("HTTP_LOCALHOST_ONLY", &val)) { - if ( val=="1" || + val = unquote_value(val); + + if (!val.empty() + && ( val=="1" || strlower(val)=="true" || - strlower(val)=="yes") + strlower(val)=="yes") ) { real_args.push_back("--http_localhost_only"); real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "FASTCGI_LOCALHOST_ONLY", val, true)) + if (settings->getValue("FASTCGI_LOCALHOST_ONLY", &val)) { - if (val == "1" || + val = unquote_value(val); + + if (!val.empty() + && (val == "1" || strlower(val) == "true" || - strlower(val) == "yes") + strlower(val) == "yes")) { real_args.push_back("--fastcgi_localhost_only"); real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_FILESIZE", val, true)) + if (settings->getValue("LOG_ROTATE_FILESIZE", &val)) { - real_args.push_back("--rotate-filesize"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--rotate-filesize"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_NUM", val, true)) + if (settings->getValue("LOG_ROTATE_NUM", &val)) { - real_args.push_back("--rotate-numfiles"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--rotate-numfiles"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_HUGE", val, true)) + if (settings->getValue("SQLITE_MMAP_HUGE", &val)) { - real_args.push_back("--sqlite_mmap_huge"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--sqlite_mmap_huge"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_MEDIUM", val, true)) + if (settings->getValue("SQLITE_MMAP_MEDIUM", &val)) { - real_args.push_back("--sqlite_mmap_medium"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--sqlite_mmap_medium"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_SMALL", val, true)) + if (settings->getValue("SQLITE_MMAP_SMALL", &val)) { - real_args.push_back("--sqlite_mmap_small"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--sqlite_mmap_small"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "HTTP_PROXY", val, true)) + if (settings->getValue("HTTP_PROXY", &val)) { - real_args.push_back("--http_proxy"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--http_proxy"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "USER", val, true)) + if (settings->getValue("USER", &val)) { - real_args.push_back("--user"); - real_args.push_back(val); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--user"); + real_args.push_back(val); + } } - if (get_setting_value_with_env(settings.get(), "INTERNET_ONLY", val, true)) + if (settings->getValue("INTERNET_ONLY", &val)) { - if (val == "1") val = "true"; - real_args.push_back("--internet_only_mode"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + if (val == "1") val = "true"; + real_args.push_back("--internet_only_mode"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "LUA_SANDBOX", val, true)) + if (settings->getValue("LUA_SANDBOX", &val)) { - if (val == "1") val = "true"; - real_args.push_back("--lua_sandbox"); - real_args.push_back(strlower(val)); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + if (val == "1") val = "true"; + real_args.push_back("--lua_sandbox"); + real_args.push_back(strlower(val)); + } } - if (get_setting_value_with_env(settings.get(), "INTERNET_MODE_DISABLED", val, true)) + if (settings->getValue("INTERNET_MODE_DISABLED", &val)) { - if (val == "1" || + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "1" || strlower(val) == "true" || - strlower(val) == "yes") + strlower(val) == "yes")) { real_args.push_back("--internet_mode_disabled"); real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "INTERNET_PORT", val, true)) + if (settings->getValue("INTERNET_PORT", &val)) { - real_args.push_back("--internet_port"); - real_args.push_back(val); + val = trim(unquote_value(val)); + + if (!val.empty()) + { + real_args.push_back("--internet_port"); + real_args.push_back(val); + } } - if (get_setting_value_with_env(settings.get(), "INTERNET_LOCALHOST_ONLY", val, true)) + if (settings->getValue("INTERNET_LOCALHOST_ONLY", &val)) { - if (val == "1" || + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "1" || strlower(val) == "true" || - strlower(val) == "yes") + strlower(val) == "yes")) { real_args.push_back("--internet_localhost_only"); real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "INTERNET_DISABLE_WEBSOCKET", val, true)) + if (settings->getValue("INTERNET_DISABLE_WEBSOCKET", &val)) { - if (val == "1" || + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "1" || strlower(val) == "true" || - strlower(val) == "yes") + strlower(val) == "yes")) { real_args.push_back("--internet_disable_websocket"); real_args.push_back("1"); } } - if (get_setting_value_with_env(settings.get(), "FAILED_LOGIN_RATELIMIT", val, true)) + if (settings->getValue("FAILED_LOGIN_RATELIMIT", &val)) { - if (val == "0" || + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "0" || strlower(val) == "false" || - strlower(val) == "no") + strlower(val) == "no")) { real_args.push_back("--failed_login_ratelimit"); real_args.push_back("0"); } } - if (get_setting_value_with_env(settings.get(), "ALLOW_USER_ENUMERATION", val, true)) + if (settings->getValue("ALLOW_USER_ENUMERATION", &val)) { - if (val == "0" || + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "0" || strlower(val) == "false" || - strlower(val) == "no") + strlower(val) == "no")) { real_args.push_back("--allow_user_enumeration"); real_args.push_back("0"); } } - if (get_setting_value_with_env(settings.get(), "LOCK_SESSION_TO_IP_AND_USER_AGENT", val, true)) + if (settings->getValue("LOCK_SESSION_TO_IP_AND_USER_AGENT", &val)) { - if (val == "1" || + val = trim(unquote_value(val)); + + if (!val.empty() && + (val == "1" || strlower(val) == "true" || - strlower(val) == "yes") + strlower(val) == "yes")) { real_args.push_back("--lock_session_to_ip_and_user_agent"); real_args.push_back("1"); @@ -434,10 +521,6 @@ int action_run(std::vector args) { read_config_file(config_arg.getValue(), real_args); } - else - { - read_config_file("", real_args); - } #endif if(std::find(real_args.begin(), real_args.end(), "--port")==real_args.end()) From 0caa60fb5140aa17e37ef97567705b421fc8c6f0 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 12 Mar 2026 11:34:27 +0100 Subject: [PATCH 441/469] Fix build on older OS --- fsimageplugin/CompressedFile.cpp | 2 +- urbackupclient/cmdline_preprocessor.cpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fsimageplugin/CompressedFile.cpp b/fsimageplugin/CompressedFile.cpp index 67325ffd3..0291fa7ce 100644 --- a/fsimageplugin/CompressedFile.cpp +++ b/fsimageplugin/CompressedFile.cpp @@ -757,7 +757,7 @@ std::vector CompressedFile::getFileExtents(int64 starting_ IVdlVolCache* CompressedFile::createVdlVolCache() { - return nullptr; + return NULL; } int64 CompressedFile::getValidDataLength(IVdlVolCache* vol_cache) diff --git a/urbackupclient/cmdline_preprocessor.cpp b/urbackupclient/cmdline_preprocessor.cpp index c57f1db25..d2b984f1b 100644 --- a/urbackupclient/cmdline_preprocessor.cpp +++ b/urbackupclient/cmdline_preprocessor.cpp @@ -204,9 +204,9 @@ void read_config_file(std::string fn, std::vector& real_args) void tune_glibc_malloc() { #if defined(HAVE_MALLOC_H) && defined(M_ARENA_MAX) - if(getenv("MALLOC_ARENA_MAX") != nullptr || - getenv("GLIBC_TUNABLES") != nullptr || - getenv("MALLOC_MMAP_THRESHOLD_") != nullptr) + if(getenv("MALLOC_ARENA_MAX") != NULL || + getenv("GLIBC_TUNABLES") != NULL || + getenv("MALLOC_MMAP_THRESHOLD_") != NULL) { return; } From ac3e21b46c7a4398e7e6fbb6791c01cd738f327d Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 12 Mar 2026 20:36:03 +0100 Subject: [PATCH 442/469] Reapply "Get configuration from environment as well" This reverts commit c0dd396738904bee30714b1d1b5bfd2479f9a54a. --- urbackupserver/cmdline_preprocessor.cpp | 315 +++++++++--------------- 1 file changed, 116 insertions(+), 199 deletions(-) diff --git a/urbackupserver/cmdline_preprocessor.cpp b/urbackupserver/cmdline_preprocessor.cpp index fea63904e..9ee2c7342 100644 --- a/urbackupserver/cmdline_preprocessor.cpp +++ b/urbackupserver/cmdline_preprocessor.cpp @@ -125,12 +125,38 @@ std::string unquote_value(std::string val) } #ifndef _WIN32 +bool get_setting_value_with_env(ISettingsReader* settings, const std::string& key, std::string& value, const bool do_trim) +{ + if (settings && settings->getValue(key, &value)) + { + value = unquote_value(value); + if (do_trim) + { + value = trim(value); + } + return !value.empty(); + } + + std::string env_key = "URBACKUP_" + key; + const char* env_value = getenv(env_key.c_str()); + if (env_value != NULL) + { + value = unquote_value(env_value); + if (do_trim) + { + value = trim(value); + } + return !value.empty(); + } + return false; +} + void read_config_file(std::string fn, std::vector& real_args) { - if (!FileExists(fn)) + const bool config_present = !fn.empty() && FileExists(fn); + if (!fn.empty() && !config_present) { std::cout << "Config file at " << fn << " does not exist. Ignoring." << std::endl; - return; } bool destroy_server=false; @@ -141,296 +167,183 @@ void read_config_file(std::string fn, std::vector& real_args) } { - std::auto_ptr settings(Server->createFileSettingsReader(fn)); + std::auto_ptr settings(config_present ? Server->createFileSettingsReader(fn) : NULL); std::string val; - if(settings->getValue("FASTCGI_PORT", &val)) + if(get_setting_value_with_env(settings.get(), "FASTCGI_PORT", val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--port"); - real_args.push_back(val); - } + real_args.push_back("--port"); + real_args.push_back(val); } - if(settings->getValue("HTTP_PORT", &val)) + if(get_setting_value_with_env(settings.get(), "HTTP_PORT", val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--http_port"); - real_args.push_back(val); - } + real_args.push_back("--http_port"); + real_args.push_back(val); } - if(settings->getValue("LOGFILE", &val)) + if(get_setting_value_with_env(settings.get(), "LOGFILE", val, false)) { - val = unquote_value(val); - - if(!val.empty()) + if(val[0]!='/') { - if(val[0]!='/') - { - val = "/var/log/"+val; - } - real_args.push_back("--logfile"); - real_args.push_back(val); + val = "/var/log/"+val; } + real_args.push_back("--logfile"); + real_args.push_back(val); } - if(settings->getValue("LOGLEVEL", &val)) + if(get_setting_value_with_env(settings.get(), "LOGLEVEL", val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--loglevel"); - real_args.push_back(unquote_value(val)); - } + real_args.push_back("--loglevel"); + real_args.push_back(unquote_value(val)); } - if(settings->getValue("DAEMON_TMPDIR", &val)) + if(get_setting_value_with_env(settings.get(), "DAEMON_TMPDIR", val, false)) { - std::string tmpdir = unquote_value(val); - if(!tmpdir.empty()) + if(setenv("TMPDIR", val.c_str(), 1)!=0) { - if(setenv("TMPDIR", tmpdir.c_str(), 1)!=0) - { - std::cout << "Error setting TMPDIR" << std::endl; - exit(1); - } + std::cout << "Error setting TMPDIR" << std::endl; + exit(1); } } - if(settings->getValue("SQLITE_TMPDIR", &val)) + if(get_setting_value_with_env(settings.get(), "SQLITE_TMPDIR", val, false)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--sqlite_tmpdir"); - real_args.push_back(val); - } + real_args.push_back("--sqlite_tmpdir"); + real_args.push_back(val); } - if(settings->getValue("BROADCAST_INTERFACES", &val)) + if(get_setting_value_with_env(settings.get(), "BROADCAST_INTERFACES", val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--broadcast_interfaces"); - real_args.push_back(val); - } + real_args.push_back("--broadcast_interfaces"); + real_args.push_back(val); } - if(settings->getValue("HTTP_SERVER", &val)) + if(get_setting_value_with_env(settings.get(), "HTTP_SERVER", val, true)) { - val = unquote_value(val); - - if(!val.empty()) - { - real_args.push_back("--http_server"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--http_server"); + real_args.push_back(strlower(val)); } - if (settings->getValue("HTTP_LOCALHOST_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "HTTP_LOCALHOST_ONLY", val, true)) { - val = unquote_value(val); - - if (!val.empty() - && ( val=="1" || + if ( val=="1" || strlower(val)=="true" || - strlower(val)=="yes") ) + strlower(val)=="yes") { real_args.push_back("--http_localhost_only"); real_args.push_back("1"); } } - if (settings->getValue("FASTCGI_LOCALHOST_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "FASTCGI_LOCALHOST_ONLY", val, true)) { - val = unquote_value(val); - - if (!val.empty() - && (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--fastcgi_localhost_only"); real_args.push_back("1"); } } - if (settings->getValue("LOG_ROTATE_FILESIZE", &val)) + if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_FILESIZE", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--rotate-filesize"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--rotate-filesize"); + real_args.push_back(strlower(val)); } - if (settings->getValue("LOG_ROTATE_NUM", &val)) + if (get_setting_value_with_env(settings.get(), "LOG_ROTATE_NUM", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--rotate-numfiles"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--rotate-numfiles"); + real_args.push_back(strlower(val)); } - if (settings->getValue("SQLITE_MMAP_HUGE", &val)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_HUGE", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--sqlite_mmap_huge"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--sqlite_mmap_huge"); + real_args.push_back(strlower(val)); } - if (settings->getValue("SQLITE_MMAP_MEDIUM", &val)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_MEDIUM", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--sqlite_mmap_medium"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--sqlite_mmap_medium"); + real_args.push_back(strlower(val)); } - if (settings->getValue("SQLITE_MMAP_SMALL", &val)) + if (get_setting_value_with_env(settings.get(), "SQLITE_MMAP_SMALL", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--sqlite_mmap_small"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--sqlite_mmap_small"); + real_args.push_back(strlower(val)); } - if (settings->getValue("HTTP_PROXY", &val)) + if (get_setting_value_with_env(settings.get(), "HTTP_PROXY", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--http_proxy"); - real_args.push_back(strlower(val)); - } + real_args.push_back("--http_proxy"); + real_args.push_back(strlower(val)); } - if (settings->getValue("USER", &val)) + if (get_setting_value_with_env(settings.get(), "USER", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--user"); - real_args.push_back(val); - } + real_args.push_back("--user"); + real_args.push_back(val); } - if (settings->getValue("INTERNET_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_ONLY", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - if (val == "1") val = "true"; - real_args.push_back("--internet_only_mode"); - real_args.push_back(strlower(val)); - } + if (val == "1") val = "true"; + real_args.push_back("--internet_only_mode"); + real_args.push_back(strlower(val)); } - if (settings->getValue("LUA_SANDBOX", &val)) + if (get_setting_value_with_env(settings.get(), "LUA_SANDBOX", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - if (val == "1") val = "true"; - real_args.push_back("--lua_sandbox"); - real_args.push_back(strlower(val)); - } + if (val == "1") val = "true"; + real_args.push_back("--lua_sandbox"); + real_args.push_back(strlower(val)); } - if (settings->getValue("INTERNET_MODE_DISABLED", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_MODE_DISABLED", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--internet_mode_disabled"); real_args.push_back("1"); } } - if (settings->getValue("INTERNET_PORT", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_PORT", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty()) - { - real_args.push_back("--internet_port"); - real_args.push_back(val); - } + real_args.push_back("--internet_port"); + real_args.push_back(val); } - if (settings->getValue("INTERNET_LOCALHOST_ONLY", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_LOCALHOST_ONLY", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--internet_localhost_only"); real_args.push_back("1"); } } - if (settings->getValue("INTERNET_DISABLE_WEBSOCKET", &val)) + if (get_setting_value_with_env(settings.get(), "INTERNET_DISABLE_WEBSOCKET", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--internet_disable_websocket"); real_args.push_back("1"); } } - if (settings->getValue("FAILED_LOGIN_RATELIMIT", &val)) + if (get_setting_value_with_env(settings.get(), "FAILED_LOGIN_RATELIMIT", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "0" || + if (val == "0" || strlower(val) == "false" || - strlower(val) == "no")) + strlower(val) == "no") { real_args.push_back("--failed_login_ratelimit"); real_args.push_back("0"); } } - if (settings->getValue("ALLOW_USER_ENUMERATION", &val)) + if (get_setting_value_with_env(settings.get(), "ALLOW_USER_ENUMERATION", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "0" || + if (val == "0" || strlower(val) == "false" || - strlower(val) == "no")) + strlower(val) == "no") { real_args.push_back("--allow_user_enumeration"); real_args.push_back("0"); } } - if (settings->getValue("LOCK_SESSION_TO_IP_AND_USER_AGENT", &val)) + if (get_setting_value_with_env(settings.get(), "LOCK_SESSION_TO_IP_AND_USER_AGENT", val, true)) { - val = trim(unquote_value(val)); - - if (!val.empty() && - (val == "1" || + if (val == "1" || strlower(val) == "true" || - strlower(val) == "yes")) + strlower(val) == "yes") { real_args.push_back("--lock_session_to_ip_and_user_agent"); real_args.push_back("1"); @@ -521,6 +434,10 @@ int action_run(std::vector args) { read_config_file(config_arg.getValue(), real_args); } + else + { + read_config_file("", real_args); + } #endif if(std::find(real_args.begin(), real_args.end(), "--port")==real_args.end()) From 47c1c6c14d462a08942d0db7a4187186236dd057 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Fri, 13 Mar 2026 23:27:39 +0100 Subject: [PATCH 443/469] Improve sparse patch handling --- .../fileclient/FileClientChunked.cpp | 2 +- urbackupserver/ChunkPatcher.cpp | 29 +++++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/urbackupcommon/fileclient/FileClientChunked.cpp b/urbackupcommon/fileclient/FileClientChunked.cpp index d518be8ed..796c4f896 100644 --- a/urbackupcommon/fileclient/FileClientChunked.cpp +++ b/urbackupcommon/fileclient/FileClientChunked.cpp @@ -312,7 +312,7 @@ _u32 FileClientChunked::GetFile(std::string remotefn, _i64& filesize_out, int64 } while (curr_sparse_extent.offset!=-1 - && next_chunk*c_checkpoint_dist < curr_sparse_extent.offset) + && (next_chunk + 1)*c_checkpoint_dist > curr_sparse_extent.offset + curr_sparse_extent.size ) { curr_sparse_extent = extent_iterator->nextExtent(); } diff --git a/urbackupserver/ChunkPatcher.cpp b/urbackupserver/ChunkPatcher.cpp index 297d9f0b3..d49d7ae5a 100644 --- a/urbackupserver/ChunkPatcher.cpp +++ b/urbackupserver/ChunkPatcher.cpp @@ -138,7 +138,25 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ if(!has_header && (file_pos>=filesize || file_pos>=size) ) { - break; + bool has_sparse = false; + if(file_posnextExtent(); + } + + if (curr_sparse_extent.offset != -1 + && curr_sparse_extent.offset <= file_pos + && curr_sparse_extent.offset + curr_sparse_extent.size > file_pos) + { + has_sparse = true; + } + } + + if(!has_sparse) + break; } unsigned int tr = max_read; @@ -216,11 +234,14 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ } bool was_sparse = false; + bool was_unaligned = false; if (curr_sparse_extent.offset != -1 && tr>=sparse_blocksize && tr>= unchanged_align && curr_sparse_extent.offset <= file_pos - && curr_sparse_extent.offset + curr_sparse_extent.size >= file_pos + tr) + && curr_sparse_extent.offset + curr_sparse_extent.size >= file_pos + sparse_blocksize) { + tr = static_cast((std::min)((int64)tr, curr_sparse_extent.offset + curr_sparse_extent.size - file_pos)); + if ( (sparse_blocksize == 0 || file_pos%sparse_blocksize == 0) && (unchanged_align == 0 @@ -230,12 +251,14 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ && tr%sparse_blocksize != 0) { tr = tr - tr%sparse_blocksize; + was_unaligned = true; } if (unchanged_align != 0 && tr%unchanged_align != 0) { tr = tr - tr%unchanged_align; + was_unaligned = true; } VLOG(Server->Log("Sparse extent at " + convert(file_pos) + " length=" + convert(tr), LL_DEBUG)); @@ -268,7 +291,7 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ } } - if(!was_sparse && (file_pos>=size || file_pos>=filesize)) + if( (!was_sparse || was_unaligned) && (file_pos>=size || file_pos>=filesize)) { Server->Log("Patch corrupt. file_pos="+convert(file_pos)+" next_header.patch_off="+convert(next_header.patch_off)+" next_header.patch_size="+convert(next_header.patch_size)+" tr="+convert(tr)+" size="+convert(size)+" filesize="+convert(filesize), LL_ERROR); assert(false); From 645ec168d8e6caf4d4ca87b19ff9413e5b4068ac Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 14 Mar 2026 00:32:25 +0100 Subject: [PATCH 444/469] Fix multiple large sparse extents at end --- urbackupserver/ChunkPatcher.cpp | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/urbackupserver/ChunkPatcher.cpp b/urbackupserver/ChunkPatcher.cpp index d49d7ae5a..bf01c031a 100644 --- a/urbackupserver/ChunkPatcher.cpp +++ b/urbackupserver/ChunkPatcher.cpp @@ -109,14 +109,19 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ { max_read -= UINT_MAX%unchanged_align; } + if (sparse_blocksize != 0) + { + max_read -= max_read%sparse_blocksize; + } SPatchHeader next_header; next_header.patch_off=-1; next_header.patch_size = 0; bool has_header=true; + bool has_sparse_at_end = false; _i64 file_pos; _i64 size; - for(file_pos=0,size=file->Size(); (file_posSize(); (file_pos=filesize || file_pos>=size) ) { - bool has_sparse = false; + has_sparse_at_end = false; if(file_pos file_pos) { - has_sparse = true; + has_sparse_at_end = true; } } - if(!has_sparse) + if(!has_sparse_at_end) break; } @@ -275,6 +280,8 @@ bool ChunkPatcher::ApplyPatch(IFile *file, IFile *patch, ExtentIterator* extent_ file_pos += tr; } was_sparse = true; + if(!has_header && (file_pos>=size || file_pos>=filesize)) + has_sparse_at_end = true; } else { From 4306327b0ad6548ae1bb3f2e5e609fc184c8ac8d Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 17 Mar 2026 17:21:29 +0100 Subject: [PATCH 445/469] Document security features --- urbackupserver/doc/admin_guide.tex | 57 +++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/urbackupserver/doc/admin_guide.tex b/urbackupserver/doc/admin_guide.tex index da58091f2..c349ddc60 100644 --- a/urbackupserver/doc/admin_guide.tex +++ b/urbackupserver/doc/admin_guide.tex @@ -349,7 +349,7 @@ \subsubsection{Apache configuration} ProxyPass "/urbackup/x" "fcgi://127.0.0.1:55413" \end{verbatim} The path (``/urbackup/x'') depends on where your web root of UrBackup is (``index.htm" should be in the same directory) -and where you want the web interface to be. UrBackup should now be accessible via apache. +and where you want the web interface to be. You could even use some random (secret) path to make it impossible for attackers to find the web interface as long as they do not know the secret path component. UrBackup should now be accessible via apache. \subsubsection{Lighttp configuration} @@ -413,6 +413,61 @@ \subsection{Internet mode security} iterations. The data is encrypted and authenticated using AES-GCM. Additionally the local network server authentication via server identity key and ECDSA private/public key authentication is done. +\subsection{Using secure websocket} + +Instead of using UrBackups encryption mechanism, it may be advised to use a secure websocket connection to the server for the communication between client and server. Per default the websocket is available as ``/socket'' endpoint on the server. If you already setup a SSL frontend as described in section \ref{sec_webinterface_ssl} you can proxy the websocket via HTTP to UrBackup. For example with apache2 like this: + +\begin{verbatim} +ProxyPass "/socket" "ws://127.0.0.1:55414/socket" +\end{verbatim} + +You can then configure the URL client connect to in the server settings to be ``wss://backup.example.com/socket''. This will make the client connect to the server via secure websocket and thus all communication between client and server is encrypted and authenticated via SSL. +To increase server performance you could then disable the build in Internet/active client encryption. + +\subsection{Disable user enumeration} + +Per default UrBackup allows connecting clients or users to discover if a client or user with a certain name exists on the server. This is required to allow for useful error messages if a client or user tries to connect with wrong credentials. However, if you want to disable this feature to prevent user enumeration attacks you can set configuration to give more generic error messages. + +On Linux this is done via a setting in the configuration file passed via the ``-c'' command line option. On e.g. Debian this file is present at ``/etc/default/urbackupsrv''. Add +\begin{verbatim} +ALLOW_USER_ENUMERATION=0 +\end{verbatim} +to this file to disable user enumeration. On Windows add +\begin{verbatim} +--allow_user_enumeration +0 +\end{verbatim} +to \textsl{C:\textbackslash Program files \textbackslash UrBackup \textbackslash args.txt}. + +\subsection{Failed login rate-limiting} + +There is a default rate limit of failed login attempts, that blocks IP that fail too often in a short amount of time. The IP is blocked if there are more than 10 failed login attempts withing 10 seconds from this IP. The IP is blocked for 10 minutes. Currently those values are not configurable.\\ +The mechnism depends on the IP being known to UrBackup, so make sure the proxy in front of UrBackup forwards the client IP in the X-Forwarded-For header. + +You can disable the failed login rate-limiting by adding +\begin{verbatim} + FAILED_LOGIN_RATELIMIT=0 +\end{verbatim} +to the Linux configuration file (e.g. ``/etc/default/urbackupsrv'') or by adding +\begin{verbatim} + --failed_login_ratelimit + 0 +\end{verbatim} +to the Windows configuration file (``C:\textbackslash Program files \textbackslash UrBackup \textbackslash args.txt''). + +UrBackup also logs failed login attempts to the auth syslog. So you can use fail2ban or similar software to block IPs with too many failed login attempts. +Following is a filter for fail2ban: + +\begin{verbatim} + [Definition] + +_daemon = urbackupsrv + +failregex = ^%(__prefix_line)sAuthentication failure for .*? from via .*?$ + ^%(__prefix_line)sClient authentication failure for .*? from $ +\end{verbatim} + + \section{Client discovery in local area networks} \label{client_discovery} From 854118ce7fbb79c1ea54430a58a46eb56387bd11 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Tue, 17 Mar 2026 17:26:26 +0100 Subject: [PATCH 446/469] Document security features --- urbackupserver/doc/admin_guide.tex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/urbackupserver/doc/admin_guide.tex b/urbackupserver/doc/admin_guide.tex index c349ddc60..2773f0303 100644 --- a/urbackupserver/doc/admin_guide.tex +++ b/urbackupserver/doc/admin_guide.tex @@ -426,7 +426,7 @@ \subsection{Using secure websocket} \subsection{Disable user enumeration} -Per default UrBackup allows connecting clients or users to discover if a client or user with a certain name exists on the server. This is required to allow for useful error messages if a client or user tries to connect with wrong credentials. However, if you want to disable this feature to prevent user enumeration attacks you can set configuration to give more generic error messages. +Per default UrBackup allows connecting clients or users to discover if a client or user with a certain name exists on the server. This is required to allow for useful error messages if a client or user tries to connect with wrong credentials. However, if you want to disable this to prevent user enumeration attacks you can set configuration to give more generic error messages. On Linux this is done via a setting in the configuration file passed via the ``-c'' command line option. On e.g. Debian this file is present at ``/etc/default/urbackupsrv''. Add \begin{verbatim} From 4b6e7630445100964aec78479c2fa35fe8fcbae1 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Thu, 19 Mar 2026 00:29:08 +0100 Subject: [PATCH 447/469] Set DisplayVersion during install --- urbackupserver_installer_win/urbackup_server.nsi | 1 + 1 file changed, 1 insertion(+) diff --git a/urbackupserver_installer_win/urbackup_server.nsi b/urbackupserver_installer_win/urbackup_server.nsi index 5f88b0871..b5c4c6c43 100644 --- a/urbackupserver_installer_win/urbackup_server.nsi +++ b/urbackupserver_installer_win/urbackup_server.nsi @@ -129,6 +129,7 @@ Section "install" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\UrBackupServer" "DisplayName" "UrBackupServer (remove only)" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\UrBackupServer" "UninstallString" "$INSTDIR\Uninstall.exe" WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\UrBackupServer" "Path" "$INSTDIR" + WriteRegStr HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\UrBackupServer" "DisplayVersion" "$version_short$" SetOutPath "$INSTDIR" File "data_common\args.txt" From e077e3779ea3327091b93d971ad46358d78f8cdb Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 23 Mar 2026 13:35:03 +0100 Subject: [PATCH 448/469] Document user views not working with btrfs --- urbackupserver/doc/admin_guide.tex | 1 + 1 file changed, 1 insertion(+) diff --git a/urbackupserver/doc/admin_guide.tex b/urbackupserver/doc/admin_guide.tex index 2773f0303..ca1d80942 100644 --- a/urbackupserver/doc/admin_guide.tex +++ b/urbackupserver/doc/admin_guide.tex @@ -1147,6 +1147,7 @@ \subsection{Create symbolically linked views for each user on the clients after After a successful file backup UrBackup will create symbolically linked views for each used on the client machine on the backup server. Those views can then be made accessible e.g. via samba file sharing. +This feature does not work with btrfs or ZFS cow backup storage. \subsection{Maximum number of simultaneous jobs per client} From bb879cc775b5954b62ca79e1c368e2187d97768e Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 4 Apr 2026 14:15:47 +0200 Subject: [PATCH 449/469] Improve isReadable method to check for buffered data --- SChannelPipe.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/SChannelPipe.cpp b/SChannelPipe.cpp index add3261e3..9ab7eef30 100644 --- a/SChannelPipe.cpp +++ b/SChannelPipe.cpp @@ -531,6 +531,9 @@ bool SChannelPipe::isReadable(int timeoutms) if (has_error) return false; + if (decbuf_pos > 0) + return true; + return bpipe->isReadable(timeoutms); } From 790f44b19789cb349e1d4a4d2b5a79528f8dbc7f Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 4 Apr 2026 01:10:09 +0200 Subject: [PATCH 450/469] Fix writing with OpenSSL (cherry picked from commit 665ca425bc074ae5e91eab23d6f2d9b4ea0e5269) --- OpenSSLPipe.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/OpenSSLPipe.cpp b/OpenSSLPipe.cpp index 4cbc6ca9a..6d5f40b8d 100644 --- a/OpenSSLPipe.cpp +++ b/OpenSSLPipe.cpp @@ -446,31 +446,34 @@ bool OpenSSLPipe::Write(const char * buffer, size_t bsize, int timeoutms, bool f if (bsize == 0) return true; - if (!bpipe->isWritable(timeoutms)) + while(true) { - return false; - } + if (!bpipe->isWritable(timeoutms)) + { + return false; + } - int rc = BIO_write(bbio, buffer, static_cast(bsize)); + int rc = BIO_write(bbio, buffer, static_cast(bsize)); - if (rc <= 0) - { - if (!BIO_should_retry(bbio)) + if (rc <= 0) { - has_error = true; + if (!BIO_should_retry(bbio)) + { + has_error = true; + return false; + } } - return false; - } - else - { - if (rc < bsize) + else { - bpipe->doThrottle(rc, true, true); + if (rc < bsize) + { + bpipe->doThrottle(rc, true, true); - return Write(buffer + rc, bsize - rc, -1, flush); - } + return Write(buffer + rc, bsize - rc, -1, flush); + } - return true; + return true; + } } } From 2d8e5c156330b658c19e87e0e80f040ed400834f Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 4 Apr 2026 14:44:02 +0200 Subject: [PATCH 451/469] Return already read data in case of timeout --- SChannelPipe.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SChannelPipe.cpp b/SChannelPipe.cpp index 9ab7eef30..62fb88c14 100644 --- a/SChannelPipe.cpp +++ b/SChannelPipe.cpp @@ -332,7 +332,7 @@ size_t SChannelPipe::Read(char * buffer, size_t bsize, int timeoutms) size_t read = bpipe->Read(&encbuf[encbuf_pos], encbuf_size_incr, remaining_time); if (read == 0) - return 0; + return orig_bsize - bsize; encbuf_pos += read; } From 26911071432b994eb649ace96e9127e8609da46b Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 4 Apr 2026 14:44:38 +0200 Subject: [PATCH 452/469] Flush write after flushing encryption --- urbackupcommon/InternetServicePipe2.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/urbackupcommon/InternetServicePipe2.cpp b/urbackupcommon/InternetServicePipe2.cpp index fe2cd6bcc..c34a684d8 100644 --- a/urbackupcommon/InternetServicePipe2.cpp +++ b/urbackupcommon/InternetServicePipe2.cpp @@ -190,6 +190,7 @@ bool InternetServicePipe2::Write( const char *buffer, size_t bsize, int timeoutm enc->flush(); curr_write_chunk_size=0; last_flush_time=Server->getTimeMS(); + flush = true; } std::string tosend = enc->get(); From 167dae2e80e48c75b45d56d943ddfdd3ae9344f9 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sat, 4 Apr 2026 01:10:23 +0200 Subject: [PATCH 453/469] Reset last flush time on flush (cherry picked from commit 09b8cb2bb09f6807acc38a92f761edd0cd4704a7) --- SChannelPipe.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/SChannelPipe.cpp b/SChannelPipe.cpp index 62fb88c14..7bcc06df2 100644 --- a/SChannelPipe.cpp +++ b/SChannelPipe.cpp @@ -454,6 +454,8 @@ bool SChannelPipe::Flush(int timeoutms) if (has_error) return false; + last_flush_time = Server->getTimeMS(); + size_t sendbuf_off = 0; while (sendbuf_pos- sendbuf_off> 0) { From 33568b0c79159991c1bd041e7edc45102d2900cd Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 5 Apr 2026 17:29:59 +0200 Subject: [PATCH 454/469] Return pipe being readable if encbuf_pos>0 --- SChannelPipe.cpp | 20 +++++++++++++------- SChannelPipe.h | 1 + 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/SChannelPipe.cpp b/SChannelPipe.cpp index 7bcc06df2..926358f22 100644 --- a/SChannelPipe.cpp +++ b/SChannelPipe.cpp @@ -12,7 +12,7 @@ SChannelPipe::SChannelPipe(CStreamPipe * bpipe) : bpipe(bpipe), has_cred_handle(false), has_ctxt_handle(false), decbuf_pos(0), sendbuf_pos(0), last_flush_time(0), - has_error(false) + has_error(false), incomplete_message(false) { } @@ -306,12 +306,15 @@ size_t SChannelPipe::Read(char * buffer, size_t bsize, int timeoutms) encbuf.resize(encbuf.size() + bsize); } - size_t read = bpipe->Read(&encbuf[encbuf_pos], bsize, timeoutms); - - if (read == 0) - return 0; + if (encbuf_pos == 0) + { + size_t read = bpipe->Read(&encbuf[encbuf_pos], bsize, timeoutms); - encbuf_pos += read; + if (read == 0) + return 0; + + encbuf_pos += read; + } size_t orig_bsize = bsize; @@ -352,6 +355,8 @@ size_t SChannelPipe::Read(char * buffer, size_t bsize, int timeoutms) res = sec->DecryptMessage(&ctxt_handle, &inbuf_desc, 0, NULL); + incomplete_message = res == SEC_E_INCOMPLETE_MESSAGE; + if (res == SEC_E_OK || res== SEC_I_RENEGOTIATE) { @@ -536,7 +541,8 @@ bool SChannelPipe::isReadable(int timeoutms) if (decbuf_pos > 0) return true; - return bpipe->isReadable(timeoutms); + if (encbuf_pos > 0 && !incomplete_message) + return true; } bool SChannelPipe::hasError(void) diff --git a/SChannelPipe.h b/SChannelPipe.h index 254290ab4..02a01f4eb 100644 --- a/SChannelPipe.h +++ b/SChannelPipe.h @@ -79,6 +79,7 @@ class SChannelPipe : public IPipe std::vector header_buf; std::vector trailer_buf; bool has_error; + bool incomplete_message; SecPkgContext_StreamSizes stream_sizes; }; From 20a08dfc1a0b044645908ae4ce062aefdebada60 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 8 Feb 2026 12:27:37 +0100 Subject: [PATCH 455/469] Update ShellState (cherry picked from commit 6675e32f12912e1d0f6230815ff447048ba15543) --- sqlite/shell.h | 167 ++++++++++++++++++++++++++++++------------------- 1 file changed, 102 insertions(+), 65 deletions(-) diff --git a/sqlite/shell.h b/sqlite/shell.h index 86a5f29d9..98eec1fba 100644 --- a/sqlite/shell.h +++ b/sqlite/shell.h @@ -2,89 +2,126 @@ typedef unsigned char u8; typedef struct sqlite3expert sqlite3expert; +#if defined(SQLITE_ENABLE_SESSION) +/* +** State information for a single open session +*/ +typedef struct OpenSession OpenSession; +struct OpenSession { + char *zName; /* Symbolic name for this session */ + int nFilter; /* Number of xFilter rejection GLOB patterns */ + char **azFilter; /* Array of xFilter rejection GLOB patterns */ + sqlite3_session *p; /* The open session */ +}; +#endif + typedef struct ExpertInfo ExpertInfo; struct ExpertInfo { - sqlite3expert* pExpert; - int bVerbose; + sqlite3expert *pExpert; + int bVerbose; }; /* A single line in the EQP output */ typedef struct EQPGraphRow EQPGraphRow; struct EQPGraphRow { - int iEqpId; /* ID for this row */ - int iParentId; /* ID of the parent row */ - EQPGraphRow* pNext; /* Next row in sequence */ - char zText[1]; /* Text to display for this row */ + int iEqpId; /* ID for this row */ + int iParentId; /* ID of the parent row */ + EQPGraphRow *pNext; /* Next row in sequence */ + char zText[1]; /* Text to display for this row */ }; /* All EQP output is collected into an instance of the following */ typedef struct EQPGraph EQPGraph; struct EQPGraph { - EQPGraphRow* pRow; /* Linked list of all rows of the EQP output */ - EQPGraphRow* pLast; /* Last element of the pRow list */ - char zPrefix[100]; /* Graph prefix */ + EQPGraphRow *pRow; /* Linked list of all rows of the EQP output */ + EQPGraphRow *pLast; /* Last element of the pRow list */ + char zPrefix[100]; /* Graph prefix */ }; +/* Parameters affecting columnar mode result display (defaulting together) */ +typedef struct ColModeOpts { + int iWrap; /* In columnar modes, wrap lines reaching this limit */ + u8 bQuote; /* Quote results for .mode box and table */ + u8 bWordWrap; /* In columnar modes, wrap at word boundaries */ +} ColModeOpts; + typedef struct ShellState ShellState; struct ShellState { - sqlite3* db; /* The database */ - u8 autoExplain; /* Automatically turn on .explain mode */ - u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to seach SQL stmt */ - u8 autoEQPtest; /* autoEQP is in test mode */ - u8 autoEQPtrace; /* autoEQP is in trace mode */ - u8 statsOn; /* True to display memory stats before each finalize */ - u8 scanstatsOn; /* True to display scan stats before each finalize */ - u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ - u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ - u8 nEqpLevel; /* Depth of the EQP output graph */ - u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ - unsigned mEqpLines; /* Mask of veritical lines in the EQP output graph */ - int outCount; /* Revert to stdout when reaching zero */ - int cnt; /* Number of records displayed so far */ - int lineno; /* Line number of last line read from in */ - int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ - FILE* in; /* Read commands from this stream */ - FILE* out; /* Write results here */ - FILE* traceOut; /* Output for sqlite3_trace() */ - int nErr; /* Number of errors seen */ - int mode; /* An output mode setting */ - int modePrior; /* Saved mode */ - int cMode; /* temporary output mode for the current query */ - int normalMode; /* Output mode before ".explain on" */ - int writableSchema; /* True if PRAGMA writable_schema=ON */ - int showHeader; /* True to show column names in List or Column mode */ - int nCheck; /* Number of ".check" commands run */ - unsigned nProgress; /* Number of progress callbacks encountered */ - unsigned mxProgress; /* Maximum progress callbacks before failing */ - unsigned flgProgress; /* Flags for the progress callback */ - unsigned shellFlgs; /* Various flags */ - sqlite3_int64 szMax; /* --maxsize argument to .open */ - char* zDestTable; /* Name of destination table when MODE_Insert */ - char* zTempFile; /* Temporary file that might need deleting */ - char zTestcase[30]; /* Name of current test case */ - char colSeparator[20]; /* Column separator character for several modes */ - char rowSeparator[20]; /* Row separator character for MODE_Ascii */ - char colSepPrior[20]; /* Saved column separator */ - char rowSepPrior[20]; /* Saved row separator */ - int colWidth[100]; /* Requested width of each column when in column mode*/ - int actualWidth[100]; /* Actual width of each column */ - char nullValue[20]; /* The text to print when a NULL comes back from - ** the database */ - char outfile[FILENAME_MAX]; /* Filename for *out */ - const char* zDbFilename; /* name of the database file */ - char* zFreeOnClose; /* Filename to free when closing */ - const char* zVfs; /* Name of VFS to use */ - sqlite3_stmt* pStmt; /* Current statement if any. */ - FILE* pLog; /* Write log output here */ - int* aiIndent; /* Array of indents used in MODE_Explain */ - int nIndent; /* Size of array aiIndent[] */ - int iIndent; /* Index of current op in aiIndent[] */ - EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ + sqlite3 *db; /* The database */ + u8 autoExplain; /* Automatically turn on .explain mode */ + u8 autoEQP; /* Run EXPLAIN QUERY PLAN prior to each SQL stmt */ + u8 autoEQPtest; /* autoEQP is in test mode */ + u8 autoEQPtrace; /* autoEQP is in trace mode */ + u8 scanstatsOn; /* True to display scan stats before each finalize */ + u8 openMode; /* SHELL_OPEN_NORMAL, _APPENDVFS, or _ZIPFILE */ + u8 doXdgOpen; /* Invoke start/open/xdg-open in output_reset() */ + u8 nEqpLevel; /* Depth of the EQP output graph */ + u8 eTraceType; /* SHELL_TRACE_* value for type of trace */ + u8 bSafeMode; /* True to prohibit unsafe operations */ + u8 bSafeModePersist; /* The long-term value of bSafeMode */ + ColModeOpts cmOpts; /* Option values affecting columnar mode output */ + unsigned statsOn; /* True to display memory stats before each finalize */ + unsigned mEqpLines; /* Mask of vertical lines in the EQP output graph */ + int inputNesting; /* Track nesting level of .read and other redirects */ + int outCount; /* Revert to stdout when reaching zero */ + int cnt; /* Number of records displayed so far */ + int lineno; /* Line number of last line read from in */ + int openFlags; /* Additional flags to open. (SQLITE_OPEN_NOFOLLOW) */ + FILE *in; /* Read commands from this stream */ + FILE *out; /* Write results here */ + FILE *traceOut; /* Output for sqlite3_trace() */ + int nErr; /* Number of errors seen */ + int mode; /* An output mode setting */ + int modePrior; /* Saved mode */ + int cMode; /* temporary output mode for the current query */ + int normalMode; /* Output mode before ".explain on" */ + int writableSchema; /* True if PRAGMA writable_schema=ON */ + int showHeader; /* True to show column names in List or Column mode */ + int nCheck; /* Number of ".check" commands run */ + unsigned nProgress; /* Number of progress callbacks encountered */ + unsigned mxProgress; /* Maximum progress callbacks before failing */ + unsigned flgProgress; /* Flags for the progress callback */ + unsigned shellFlgs; /* Various flags */ + unsigned priorShFlgs; /* Saved copy of flags */ + sqlite3_int64 szMax; /* --maxsize argument to .open */ + char *zDestTable; /* Name of destination table when MODE_Insert */ + char *zTempFile; /* Temporary file that might need deleting */ + char zTestcase[30]; /* Name of current test case */ + char colSeparator[20]; /* Column separator character for several modes */ + char rowSeparator[20]; /* Row separator character for MODE_Ascii */ + char colSepPrior[20]; /* Saved column separator */ + char rowSepPrior[20]; /* Saved row separator */ + int *colWidth; /* Requested width of each column in columnar modes */ + int *actualWidth; /* Actual width of each column */ + int nWidth; /* Number of slots in colWidth[] and actualWidth[] */ + char nullValue[20]; /* The text to print when a NULL comes back from + ** the database */ + char outfile[FILENAME_MAX]; /* Filename for *out */ + sqlite3_stmt *pStmt; /* Current statement if any. */ + FILE *pLog; /* Write log output here */ + struct AuxDb { /* Storage space for auxiliary database connections */ + sqlite3 *db; /* Connection pointer */ + const char *zDbFilename; /* Filename used to open the connection */ + char *zFreeOnClose; /* Free this memory allocation on close */ #if defined(SQLITE_ENABLE_SESSION) - int nSession; /* Number of active sessions */ - OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */ + int nSession; /* Number of active sessions */ + OpenSession aSession[4]; /* Array of sessions. [0] is in focus. */ +#endif + } aAuxDb[5], /* Array of all database connections */ + *pAuxDb; /* Currently active database connection */ + int *aiIndent; /* Array of indents used in MODE_Explain */ + int nIndent; /* Size of array aiIndent[] */ + int iIndent; /* Index of current op in aiIndent[] */ + char *zNonce; /* Nonce for temporary safe-mode escapes */ + EQPGraph sGraph; /* Information for the graphical EXPLAIN QUERY PLAN */ + ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ +#ifdef SQLITE_SHELL_FIDDLE + struct { + const char * zInput; /* Input string from wasm/JS proxy */ + const char * zPos; /* Cursor pos into zInput */ + const char * zDefaultDbName; /* Default name for db file */ + } wasm; #endif - ExpertInfo expert; /* Valid if previous command was ".expert OPT..." */ }; int do_meta_command_r(char *zLine, struct ShellState *p); \ No newline at end of file From f4cd002585ebab15c5b74dae2e939b65a6db9634 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 8 Feb 2026 12:23:47 +0100 Subject: [PATCH 456/469] Fix database dump and recover (cherry picked from commit e488fadfc67e0265aa3fba226fb5e346b4fd8857) # Conflicts: # Database.cpp --- Database.cpp | 1643 +++++++++++++++++++++++++------------------------- 1 file changed, 827 insertions(+), 816 deletions(-) diff --git a/Database.cpp b/Database.cpp index 385a1669a..bbeb9b39c 100644 --- a/Database.cpp +++ b/Database.cpp @@ -1,816 +1,827 @@ -/************************************************************************* -* 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 . -**************************************************************************/ -#ifndef NO_SQLITE - -#if defined(_WIN32) || defined(WIN32) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include "vld.h" -#ifndef BDBPLUGIN -#include "Server.h" -#else -#ifdef LINUX -#include "bdbplugin/config.h" -#include DB_HEADER -#else -#include -#endif -#include "Interface/Server.h" -#endif -#include "Query.h" -#ifdef USE_SYSTEM_SQLITE -#include -#else -#include "sqlite/sqlite3.h" -#endif -#include "Interface/File.h" -#include -extern "C" -{ - #include "sqlite/shell.h" -} -#include "Database.h" -#include "stringtools.h" -#include - - -namespace -{ - size_t get_sqlite_cache_size() - { - std::string cache_size_str = Server->getServerParameter("sqlite_cache_size"); - - if(!cache_size_str.empty()) - { - return atoi(cache_size_str.c_str()); - } - else - { - return 2*1024; //2MB - } - } - - void errorLogCallback(void *pArg, int iErrCode, const char *zMsg) - { - switch (iErrCode) - { - case SQLITE_LOCKED: - case SQLITE_BUSY: - case SQLITE_SCHEMA: - return; - case SQLITE_NOTICE_RECOVER_ROLLBACK: - case SQLITE_NOTICE_RECOVER_WAL: - Server->Log("SQLite: "+ std::string(zMsg) + " code: " + convert(iErrCode), LL_INFO); - break; - default: - Server->Log("SQLite: " + std::string(zMsg) + " errorcode: " + convert(iErrCode), LL_WARNING); - break; - } - } -} - - -struct UnlockNotification { - bool fired; - ICondition* cond; - IMutex *mutex; -}; - -static void unlock_notify_cb(void **apArg, int nArg) -{ - for(int i=0; imutex); - p->fired = true; - p->cond->notify_all(); - } -} - -CDatabase::~CDatabase() -{ -#ifndef NDEBUG - db_thread_id = Server->getThreadID(); -#endif - destroyAllQueries(); - for(std::map::iterator iter=prepared_queries.begin();iter!=prepared_queries.end();++iter) - { - CQuery *q=(CQuery*)iter->second; - delete q; - } - prepared_queries.clear(); - - sqlite3_close(db); -} - -bool CDatabase::Open(std::string pFile, const std::vector > &attach, - size_t allocation_chunk_size, ISharedMutex* p_single_user_mutex, IMutex* p_lock_mutex, - int* p_lock_count, ICondition *p_unlock_cond, const str_map& p_params) -{ - single_user_mutex = p_single_user_mutex; - lock_mutex = p_lock_mutex; - lock_count = p_lock_count; - unlock_cond = p_unlock_cond; - params = p_params; -#ifndef NDEBUG - db_thread_id = Server->getThreadID(); -#endif - - attached_dbs=attach; - in_transaction=false; - if( sqlite3_open(pFile.c_str(), &db) ) - { - Server->Log("Could not open db ["+pFile+"]"); - sqlite3_close(db); - db = NULL; - return false; - } - else - { - str_map::const_iterator it = params.find("synchronous"); - if (it != params.end()) - { - Write("PRAGMA synchronous="+it->second); - } - else - { - Write("PRAGMA synchronous=NORMAL"); - } - Write("PRAGMA foreign_keys = ON"); - Write("PRAGMA threads = 2"); - - it = params.find("wal_autocheckpoint"); - if (it != params.end()) - { - Write("PRAGMA wal_autocheckpoint=" + it->second); - - if (watoi(it->second)<=0) - { - int enable = 1; - sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &enable); -#ifdef SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE - int was_enabled = 0; - sqlite3_db_config(db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &was_enabled); -#endif - } - } - - it = params.find("page_size"); - if (it != params.end()) - { - Write("PRAGMA page_size=" + it->second); - } - else - { - Write("PRAGMA page_size=4096"); - } - - it = params.find("mmap_size"); - if (it != params.end()) - { - Write("PRAGMA mmap_size=" + it->second); - } - - if(allocation_chunk_size!=std::string::npos) - { - int chunk_size = static_cast(allocation_chunk_size); - sqlite3_file_control(db, NULL, SQLITE_FCNTL_CHUNK_SIZE, &chunk_size); - } - - static size_t sqlite_cache_size = get_sqlite_cache_size(); - Write("PRAGMA cache_size = -"+convert(sqlite_cache_size)); - - sqlite3_busy_timeout(db, c_sqlite_busy_timeout_default); - -#if defined(_DEBUG) || (!defined(_WIN32) && !defined(NDEBUG)) - if (Server->getRandomNumber() % 2 == 0) - { - Write("PRAGMA reverse_unordered_selects = ON"); - } -#endif - - AttachDBs(); - - return true; - } -} - -void CDatabase::initMutex(void) -{ - sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, NULL); -} - -void CDatabase::destroyMutex(void) -{ -} - -db_results CDatabase::Read(std::string pQuery) -{ - assert_thread_id(); - //Server->Log("SQL Query(Read): "+pQuery, LL_DEBUG); - IQuery *q=Prepare(pQuery, false); - if(q!=NULL) - { - db_results ret=q->Read(); - delete ((CQuery*)q); - return ret; - } - return db_results(); -} - -bool CDatabase::Write(std::string pQuery) -{ - assert_thread_id(); - //Server->Log("SQL Query(Write): "+pQuery, LL_DEBUG); - IQuery *q=Prepare(pQuery, false); - if(q!=NULL) - { - bool b=q->Write(); - delete ((CQuery*)q); - return b; - } - else - { - return false; - } -} - -//ToDo: Cache Writings - -bool CDatabase::BeginReadTransaction() -{ - assert_thread_id(); - - if (write_lock.get() == NULL) - { - transaction_read_lock.reset(new IScopedReadLock(single_user_mutex)); - } - - in_transaction = true; - if(Write("BEGIN")) - { - return true; - } - else - { - in_transaction = false; - return false; - } -} - -bool CDatabase::BeginWriteTransaction() -{ - assert_thread_id(); - - if (write_lock.get() == NULL) - { - transaction_read_lock.reset(new IScopedReadLock(single_user_mutex)); - } - - in_transaction = true; - if(Write("BEGIN IMMEDIATE;")) - { - return true; - } - else - { - in_transaction = false; - return false; - } -} - -bool CDatabase::EndTransaction(void) -{ - assert_thread_id(); - - bool ret = Write("END;"); - in_transaction=false; - transaction_read_lock.reset(); - IScopedLock lock(lock_mutex); - bool waited=false; - while(*lock_count>0) - { - unlock_cond->wait(&lock); - waited=true; - } - if(waited) - { - Server->wait(50); - } - return ret; -} - -bool CDatabase::RollbackTransaction() -{ - assert_thread_id(); - - bool ret = Write("ROLLBACK;"); - in_transaction = false; - transaction_read_lock.reset(); - IScopedLock lock(lock_mutex); - bool waited = false; - while (*lock_count>0) - { - unlock_cond->wait(&lock); - waited = true; - } - if (waited) - { - Server->wait(50); - } - return ret; -} - -IQuery* CDatabase::Prepare(std::string pQuery, bool autodestroy) -{ - assert_thread_id(); - - IScopedReadLock lock(NULL); - - if (!in_transaction && write_lock.get()==NULL) - { - lock.relock(single_user_mutex); - } - - int prepare_tries = 0; -#ifdef SQLITE_PREPARE_RETRIES - prepare_tries = SQLITE_PREPARE_RETRIES; -#endif - - sqlite3_stmt *prepared_statement; - const char* tail; - int err; - bool transaction_lock=false; - while((err=sqlite3_prepare_v2(db, pQuery.c_str(), (int)pQuery.size(), &prepared_statement, &tail) )==SQLITE_LOCKED - || err==SQLITE_BUSY - || err==SQLITE_PROTOCOL - || (err!=SQLITE_OK && prepare_tries>0) ) - { - --prepare_tries; - - if(err==SQLITE_LOCKED) - { - if(!transaction_lock && LockForTransaction()) - { - transaction_lock=true; - if(!WaitForUnlock()) - Server->Log("DATABASE DEADLOCKED in CDatabase::Prepare", LL_ERROR); - } - } - else if(err== SQLITE_BUSY - || err==SQLITE_PROTOCOL) - { - if(!transaction_lock) - { - if(!isInTransaction() && LockForTransaction()) - { - transaction_lock=true; - } - sqlite3_busy_timeout(db, 10000); - } - else - { - Server->Log("DATABASE BUSY in CDatabase::Prepare", LL_ERROR); - } - } - else - { - Server->Log("Error preparing Query [" + pQuery + "]: " + sqlite3_errmsg(db)+". Retrying in 1s...", LL_ERROR); - Server->wait(1000); - } - } - - if(transaction_lock) - { - UnlockForTransaction(); - sqlite3_busy_timeout(db, 50); - } - - if( err!=SQLITE_OK ) - { - Server->Log("Error preparing Query ["+pQuery+"]: "+sqlite3_errmsg(db),LL_ERROR); - - if(err==SQLITE_IOERR) - { - Server->setFailBit(IServer::FAIL_DATABASE_IOERR); - } - if(err==SQLITE_CORRUPT) - { - Server->setFailBit(IServer::FAIL_DATABASE_CORRUPTED); - } - if (err ==SQLITE_FULL) - { - Server->setFailBit(IServer::FAIL_DATABASE_FULL); - } - - return NULL; - } - CQuery *q=new CQuery(pQuery, prepared_statement, this); - if( autodestroy ) - { - queries.push_back(q); - } - - return q; -} - -IQuery* CDatabase::Prepare(int id, std::string pQuery) -{ - assert_thread_id(); - - IScopedReadLock lock(NULL); - - if (!in_transaction && write_lock.get()==NULL) - { - lock.relock(single_user_mutex); - } - - std::map::iterator iter=prepared_queries.find(id); - if( iter!=prepared_queries.end() ) - { - iter->second->Reset(); - return iter->second; - } - else - { - IQuery *q=Prepare(pQuery, false); - prepared_queries.insert(std::pair(id, q) ); - return q; - } -} - -void CDatabase::destroyQuery(IQuery *q) -{ - assert_thread_id(); - - if(q==NULL) - { - return; - } - - for(size_t i=0;icreateMutex(); - un.cond=Server->createCondition(); - - rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); - - if( rc==SQLITE_OK ) - { - IScopedLock lock(un.mutex); - if( !un.fired ) - { - un.cond->wait(&lock); - } - } - - Server->destroy(un.mutex); - Server->destroy(un.cond); - - return rc==SQLITE_OK; -#else - return false; -#endif -} - -sqlite3 *CDatabase::getDatabase(void) -{ - return db; -} - -bool CDatabase::LockForTransaction(void) -{ - lock_mutex->Lock(); - ++*lock_count; - return true; -} - -void CDatabase::UnlockForTransaction(void) -{ - --*lock_count; - unlock_cond->notify_all(); - lock_mutex->Unlock(); -} - -bool CDatabase::isInTransaction(void) -{ - return in_transaction; -} - -bool CDatabase::Import(const std::string &pFile) -{ - IFile *file=Server->openFile(pFile, MODE_READ); - if(file==NULL) - return false; - - unsigned int r; - char buf[4096]; - std::string query; - int state=0; - do - { - r=file->Read(buf, 4096); - for(unsigned int i=0;i0); - - Server->destroy(file); - return true; -} - -bool CDatabase::Dump(const std::string &pFile) -{ - assert_thread_id(); - - const char* db_fn = sqlite3_db_filename(db, NULL); - if (db_fn == NULL) - return false; - - ShellState cd = {}; - cd.openMode = 1; - cd.zDbFilename = db_fn; - cd.out=fopen(pFile.c_str(), "wb"); - if(cd.out==0) - { - return false; - } - - std::string cmd = ".dump"; - int rc = do_meta_command_r(&cmd[0], &cd); - - fclose(cd.out); - - if (cd.db != 0) - sqlite3_close(cd.db); - - return rc == SQLITE_OK; -} - -bool CDatabase::Recover(const std::string & pFile) -{ - const char* db_fn = sqlite3_db_filename(db, NULL); - if (db_fn == NULL) - return false; - - ShellState cd = {}; - cd.openMode = 1; - cd.zDbFilename = db_fn; - cd.out = fopen(pFile.c_str(), "wb"); - if (cd.out == 0) - { - return false; - } - - std::string cmd = ".recover"; - int rc = do_meta_command_r(&cmd[0], &cd); - - fclose(cd.out); - - if (cd.db != 0) - sqlite3_close(cd.db); - - return rc==SQLITE_OK; -} - -std::string CDatabase::getEngineName(void) -{ - #ifndef BDBPLUGIN - return "sqlite"; - #else - return "bdb"; - #endif -} - -void CDatabase::AttachDBs(void) -{ - for(size_t i=0;ibackupProgress(done*page_size, total*page_size); - - } while( rc==SQLITE_OK || rc==SQLITE_BUSY || rc==SQLITE_PROTOCOL || rc==SQLITE_LOCKED ); - - /* Release resources allocated by backup_init(). */ - (void)sqlite3_backup_finish(pBackup); - } - else - { - Server->Log("Opening backup connection failed", LL_ERROR); - } - rc = sqlite3_errcode(pBackupDB); - if(rc!=0) - { - Server->Log("Database backup failed with error code: "+convert(rc)+" err: "+sqlite3_errmsg(pBackupDB), LL_ERROR); - } - } - - /* Close the database connection opened on database file zFilename - ** and return the result of this function. */ - (void)sqlite3_close(pBackupDB); - return rc==0; -} - -bool CDatabase::Backup(const std::string &pFile, IBackupProgress* progress) -{ - std::string path=ExtractFilePath(pFile); - bool b=backup_db(pFile, "main", progress); - if(!b) - return false; - - for(size_t i=0;igetThreadID() == db_thread_id); -} -#endif //NO_SQLITE +/************************************************************************* +* 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 . +**************************************************************************/ +#ifndef NO_SQLITE + +#if defined(_WIN32) || defined(WIN32) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include "vld.h" +#ifndef BDBPLUGIN +#include "Server.h" +#else +#ifdef LINUX +#include "bdbplugin/config.h" +#include DB_HEADER +#else +#include +#endif +#include "Interface/Server.h" +#endif +#include "Query.h" +#ifdef USE_SYSTEM_SQLITE +#include +#else +#include "sqlite/sqlite3.h" +#endif +#include "Interface/File.h" +#include +extern "C" +{ + #include "sqlite/shell.h" +} +#include "Database.h" +#include "stringtools.h" +#include + + +namespace +{ + size_t get_sqlite_cache_size() + { + std::string cache_size_str = Server->getServerParameter("sqlite_cache_size"); + + if(!cache_size_str.empty()) + { + return atoi(cache_size_str.c_str()); + } + else + { + return 2*1024; //2MB + } + } + + void errorLogCallback(void *pArg, int iErrCode, const char *zMsg) + { + switch (iErrCode) + { + case SQLITE_LOCKED: + case SQLITE_BUSY: + case SQLITE_SCHEMA: + return; + case SQLITE_NOTICE_RECOVER_ROLLBACK: + case SQLITE_NOTICE_RECOVER_WAL: + Server->Log("SQLite: "+ std::string(zMsg) + " code: " + convert(iErrCode), LL_INFO); + break; + default: + Server->Log("SQLite: " + std::string(zMsg) + " errorcode: " + convert(iErrCode), LL_WARNING); + break; + } + } +} + + +struct UnlockNotification { + bool fired; + ICondition* cond; + IMutex *mutex; +}; + +static void unlock_notify_cb(void **apArg, int nArg) +{ + for(int i=0; imutex); + p->fired = true; + p->cond->notify_all(); + } +} + +CDatabase::~CDatabase() +{ +#ifndef NDEBUG + db_thread_id = Server->getThreadID(); +#endif + destroyAllQueries(); + for(std::map::iterator iter=prepared_queries.begin();iter!=prepared_queries.end();++iter) + { + CQuery *q=(CQuery*)iter->second; + delete q; + } + prepared_queries.clear(); + + sqlite3_close(db); +} + +bool CDatabase::Open(std::string pFile, const std::vector > &attach, + size_t allocation_chunk_size, ISharedMutex* p_single_user_mutex, IMutex* p_lock_mutex, + int* p_lock_count, ICondition *p_unlock_cond, const str_map& p_params) +{ + single_user_mutex = p_single_user_mutex; + lock_mutex = p_lock_mutex; + lock_count = p_lock_count; + unlock_cond = p_unlock_cond; + params = p_params; +#ifndef NDEBUG + db_thread_id = Server->getThreadID(); +#endif + + attached_dbs=attach; + in_transaction=false; + if( sqlite3_open(pFile.c_str(), &db) ) + { + Server->Log("Could not open db ["+pFile+"]"); + sqlite3_close(db); + db = NULL; + return false; + } + else + { + str_map::const_iterator it = params.find("synchronous"); + if (it != params.end()) + { + Write("PRAGMA synchronous="+it->second); + } + else + { + Write("PRAGMA synchronous=NORMAL"); + } + Write("PRAGMA foreign_keys = ON"); + Write("PRAGMA threads = 2"); + + it = params.find("wal_autocheckpoint"); + if (it != params.end()) + { + Write("PRAGMA wal_autocheckpoint=" + it->second); + + if (watoi(it->second)<=0) + { + int enable = 1; + sqlite3_file_control(db, NULL, SQLITE_FCNTL_PERSIST_WAL, &enable); +#ifdef SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE + int was_enabled = 0; + sqlite3_db_config(db, SQLITE_DBCONFIG_NO_CKPT_ON_CLOSE, 1, &was_enabled); +#endif + } + } + + it = params.find("page_size"); + if (it != params.end()) + { + Write("PRAGMA page_size=" + it->second); + } + else + { + Write("PRAGMA page_size=4096"); + } + + it = params.find("mmap_size"); + if (it != params.end()) + { + Write("PRAGMA mmap_size=" + it->second); + } + + if(allocation_chunk_size!=std::string::npos) + { + int chunk_size = static_cast(allocation_chunk_size); + sqlite3_file_control(db, NULL, SQLITE_FCNTL_CHUNK_SIZE, &chunk_size); + } + + static size_t sqlite_cache_size = get_sqlite_cache_size(); + Write("PRAGMA cache_size = -"+convert(sqlite_cache_size)); + + sqlite3_busy_timeout(db, c_sqlite_busy_timeout_default); + +#if defined(_DEBUG) || (!defined(_WIN32) && !defined(NDEBUG)) + if (Server->getRandomNumber() % 2 == 0) + { + Write("PRAGMA reverse_unordered_selects = ON"); + } +#endif + + AttachDBs(); + + return true; + } +} + +void CDatabase::initMutex(void) +{ + sqlite3_config(SQLITE_CONFIG_LOG, errorLogCallback, NULL); +} + +void CDatabase::destroyMutex(void) +{ +} + +db_results CDatabase::Read(std::string pQuery) +{ + assert_thread_id(); + //Server->Log("SQL Query(Read): "+pQuery, LL_DEBUG); + IQuery *q=Prepare(pQuery, false); + if(q!=NULL) + { + db_results ret=q->Read(); + delete ((CQuery*)q); + return ret; + } + return db_results(); +} + +bool CDatabase::Write(std::string pQuery) +{ + assert_thread_id(); + //Server->Log("SQL Query(Write): "+pQuery, LL_DEBUG); + IQuery *q=Prepare(pQuery, false); + if(q!=NULL) + { + bool b=q->Write(); + delete ((CQuery*)q); + return b; + } + else + { + return false; + } +} + +//ToDo: Cache Writings + +bool CDatabase::BeginReadTransaction() +{ + assert_thread_id(); + + if (write_lock.get() == NULL) + { + transaction_read_lock.reset(new IScopedReadLock(single_user_mutex)); + } + + in_transaction = true; + if(Write("BEGIN")) + { + return true; + } + else + { + in_transaction = false; + return false; + } +} + +bool CDatabase::BeginWriteTransaction() +{ + assert_thread_id(); + + if (write_lock.get() == NULL) + { + transaction_read_lock.reset(new IScopedReadLock(single_user_mutex)); + } + + in_transaction = true; + if(Write("BEGIN IMMEDIATE;")) + { + return true; + } + else + { + in_transaction = false; + return false; + } +} + +bool CDatabase::EndTransaction(void) +{ + assert_thread_id(); + + bool ret = Write("END;"); + in_transaction=false; + transaction_read_lock.reset(); + IScopedLock lock(lock_mutex); + bool waited=false; + while(*lock_count>0) + { + unlock_cond->wait(&lock); + waited=true; + } + if(waited) + { + Server->wait(50); + } + return ret; +} + +bool CDatabase::RollbackTransaction() +{ + assert_thread_id(); + + bool ret = Write("ROLLBACK;"); + in_transaction = false; + transaction_read_lock.reset(); + IScopedLock lock(lock_mutex); + bool waited = false; + while (*lock_count>0) + { + unlock_cond->wait(&lock); + waited = true; + } + if (waited) + { + Server->wait(50); + } + return ret; +} + +IQuery* CDatabase::Prepare(std::string pQuery, bool autodestroy) +{ + assert_thread_id(); + + IScopedReadLock lock(NULL); + + if (!in_transaction && write_lock.get()==NULL) + { + lock.relock(single_user_mutex); + } + + int prepare_tries = 0; +#ifdef SQLITE_PREPARE_RETRIES + prepare_tries = SQLITE_PREPARE_RETRIES; +#endif + + sqlite3_stmt *prepared_statement; + const char* tail; + int err; + bool transaction_lock=false; + while((err=sqlite3_prepare_v2(db, pQuery.c_str(), (int)pQuery.size(), &prepared_statement, &tail) )==SQLITE_LOCKED + || err==SQLITE_BUSY + || err==SQLITE_PROTOCOL + || (err!=SQLITE_OK && prepare_tries>0) ) + { + --prepare_tries; + + if(err==SQLITE_LOCKED) + { + if(!transaction_lock && LockForTransaction()) + { + transaction_lock=true; + if(!WaitForUnlock()) + Server->Log("DATABASE DEADLOCKED in CDatabase::Prepare", LL_ERROR); + } + } + else if(err== SQLITE_BUSY + || err==SQLITE_PROTOCOL) + { + if(!transaction_lock) + { + if(!isInTransaction() && LockForTransaction()) + { + transaction_lock=true; + } + sqlite3_busy_timeout(db, 10000); + } + else + { + Server->Log("DATABASE BUSY in CDatabase::Prepare", LL_ERROR); + } + } + else + { + Server->Log("Error preparing Query [" + pQuery + "]: " + sqlite3_errmsg(db)+". Retrying in 1s...", LL_ERROR); + Server->wait(1000); + } + } + + if(transaction_lock) + { + UnlockForTransaction(); + sqlite3_busy_timeout(db, 50); + } + + if( err!=SQLITE_OK ) + { + Server->Log("Error preparing Query ["+pQuery+"]: "+sqlite3_errmsg(db),LL_ERROR); + + if(err==SQLITE_IOERR) + { + Server->setFailBit(IServer::FAIL_DATABASE_IOERR); + } + if(err==SQLITE_CORRUPT) + { + Server->setFailBit(IServer::FAIL_DATABASE_CORRUPTED); + } + if (err ==SQLITE_FULL) + { + Server->setFailBit(IServer::FAIL_DATABASE_FULL); + } + + return NULL; + } + CQuery *q=new CQuery(pQuery, prepared_statement, this); + if( autodestroy ) + { + queries.push_back(q); + } + + return q; +} + +IQuery* CDatabase::Prepare(int id, std::string pQuery) +{ + assert_thread_id(); + + IScopedReadLock lock(NULL); + + if (!in_transaction && write_lock.get()==NULL) + { + lock.relock(single_user_mutex); + } + + std::map::iterator iter=prepared_queries.find(id); + if( iter!=prepared_queries.end() ) + { + iter->second->Reset(); + return iter->second; + } + else + { + IQuery *q=Prepare(pQuery, false); + prepared_queries.insert(std::pair(id, q) ); + return q; + } +} + +void CDatabase::destroyQuery(IQuery *q) +{ + assert_thread_id(); + + if(q==NULL) + { + return; + } + + for(size_t i=0;icreateMutex(); + un.cond=Server->createCondition(); + + rc = sqlite3_unlock_notify(db, unlock_notify_cb, (void *)&un); + + if( rc==SQLITE_OK ) + { + IScopedLock lock(un.mutex); + if( !un.fired ) + { + un.cond->wait(&lock); + } + } + + Server->destroy(un.mutex); + Server->destroy(un.cond); + + return rc==SQLITE_OK; +#else + return false; +#endif +} + +sqlite3 *CDatabase::getDatabase(void) +{ + return db; +} + +bool CDatabase::LockForTransaction(void) +{ + lock_mutex->Lock(); + ++*lock_count; + return true; +} + +void CDatabase::UnlockForTransaction(void) +{ + --*lock_count; + unlock_cond->notify_all(); + lock_mutex->Unlock(); +} + +bool CDatabase::isInTransaction(void) +{ + return in_transaction; +} + +bool CDatabase::Import(const std::string &pFile) +{ + IFile *file=Server->openFile(pFile, MODE_READ); + if(file==NULL) + return false; + + unsigned int r; + char buf[4096]; + std::string query; + int state=0; + do + { + r=file->Read(buf, 4096); + for(unsigned int i=0;i0); + + Server->destroy(file); + return true; +} + +namespace +{ + void shell_state_init(ShellState& cd) + { + cd.openMode = 1; + cd.normalMode = cd.cMode = cd.mode = 2; + cd.autoExplain = 1; + cd.pAuxDb = &cd.aAuxDb[0]; + } +} + +bool CDatabase::Dump(const std::string &pFile) +{ + assert_thread_id(); + + const char* db_fn = sqlite3_db_filename(db, NULL); + if (db_fn == NULL) + return false; + + ShellState cd = {}; + shell_state_init(cd); + cd.zDbFilename = db_fn; + cd.out=fopen(pFile.c_str(), "wb"); + if(cd.out==0) + { + return false; + } + + std::string cmd = ".dump"; + int rc = do_meta_command_r(&cmd[0], &cd); + + fclose(cd.out); + + if (cd.db != 0) + sqlite3_close(cd.db); + + return rc == SQLITE_OK; +} + +bool CDatabase::Recover(const std::string & pFile) +{ + const char* db_fn = sqlite3_db_filename(db, NULL); + if (db_fn == NULL) + return false; + + ShellState cd = {}; + shell_state_init(cd); + cd.zDbFilename = db_fn; + cd.out = fopen(pFile.c_str(), "wb"); + if (cd.out == 0) + { + return false; + } + + std::string cmd = ".recover"; + int rc = do_meta_command_r(&cmd[0], &cd); + + fclose(cd.out); + + if (cd.db != 0) + sqlite3_close(cd.db); + + return rc==SQLITE_OK; +} + +std::string CDatabase::getEngineName(void) +{ + #ifndef BDBPLUGIN + return "sqlite"; + #else + return "bdb"; + #endif +} + +void CDatabase::AttachDBs(void) +{ + for(size_t i=0;ibackupProgress(done*page_size, total*page_size); + + } while( rc==SQLITE_OK || rc==SQLITE_BUSY || rc==SQLITE_PROTOCOL || rc==SQLITE_LOCKED ); + + /* Release resources allocated by backup_init(). */ + (void)sqlite3_backup_finish(pBackup); + } + else + { + Server->Log("Opening backup connection failed", LL_ERROR); + } + rc = sqlite3_errcode(pBackupDB); + if(rc!=0) + { + Server->Log("Database backup failed with error code: "+convert(rc)+" err: "+sqlite3_errmsg(pBackupDB), LL_ERROR); + } + } + + /* Close the database connection opened on database file zFilename + ** and return the result of this function. */ + (void)sqlite3_close(pBackupDB); + return rc==0; +} + +bool CDatabase::Backup(const std::string &pFile, IBackupProgress* progress) +{ + std::string path=ExtractFilePath(pFile); + bool b=backup_db(pFile, "main", progress); + if(!b) + return false; + + for(size_t i=0;igetThreadID() == db_thread_id); +} +#endif //NO_SQLITE From 67548ecdde81ea43c40392aa19bcfb1d2698d746 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Sun, 8 Feb 2026 12:30:03 +0100 Subject: [PATCH 457/469] Correctly init db filename (cherry picked from commit 5bc170df8544c114671a406599a2d337e06f9d92) # Conflicts: # Database.cpp --- Database.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Database.cpp b/Database.cpp index bbeb9b39c..83f9ebbb9 100644 --- a/Database.cpp +++ b/Database.cpp @@ -593,12 +593,13 @@ bool CDatabase::Import(const std::string &pFile) namespace { - void shell_state_init(ShellState& cd) + void shell_state_init(ShellState& cd, const char* db_fn) { cd.openMode = 1; cd.normalMode = cd.cMode = cd.mode = 2; cd.autoExplain = 1; cd.pAuxDb = &cd.aAuxDb[0]; + cd.aAuxDb->zDbFilename = db_fn; } } @@ -611,8 +612,7 @@ bool CDatabase::Dump(const std::string &pFile) return false; ShellState cd = {}; - shell_state_init(cd); - cd.zDbFilename = db_fn; + shell_state_init(cd, db_fn); cd.out=fopen(pFile.c_str(), "wb"); if(cd.out==0) { @@ -637,8 +637,7 @@ bool CDatabase::Recover(const std::string & pFile) return false; ShellState cd = {}; - shell_state_init(cd); - cd.zDbFilename = db_fn; + shell_state_init(cd, db_fn); cd.out = fopen(pFile.c_str(), "wb"); if (cd.out == 0) { From c768942be8e6127be0a02a705d864bbe350e5485 Mon Sep 17 00:00:00 2001 From: Martin Raiber Date: Mon, 20 Apr 2026 19:48:46 +0200 Subject: [PATCH 458/469] Update SQLite to 3.53.0 --- sqlite/shell.c | 44832 ++++++++++++++++++++++++----------------- sqlite/shell.h | 127 +- sqlite/sqlite3.c | 45520 +++++++++++++++++++++++++++++------------- sqlite/sqlite3.h | 1989 +- sqlite/sqlite3ext.h | 26 + 5 files changed, 60163 insertions(+), 32331 deletions(-) diff --git a/sqlite/shell.c b/sqlite/shell.c index f13f4d9c6..d8ae6aa44 100644 --- a/sqlite/shell.c +++ b/sqlite/shell.c @@ -1,21 +1,45 @@ -/* DO NOT EDIT! -** This file is automatically generated by the script in the canonical -** SQLite source tree at tool/mkshellc.tcl. That script combines source -** code from various constituent source files of SQLite into this single -** "shell.c" file used to implement the SQLite command-line shell. -** -** Most of the code found below comes from the "src/shell.c.in" file in -** the canonical SQLite source tree. That main file contains "INCLUDE" -** lines that specify other files in the canonical source tree that are -** inserted to getnerate this complete program source file. -** -** The code from multiple files is combined into this single "shell.c" -** source file to help make the command-line program easier to compile. +/* +** This is the amalgamated source code to the "sqlite3" or "sqlite3.exe" +** command-line shell (CLI) for SQLite. This file is automatically +** generated by the tool/mkshellc.tcl script from the following sources: +** +** ext/expert/sqlite3expert.c +** ext/expert/sqlite3expert.h +** ext/intck/sqlite3intck.c +** ext/intck/sqlite3intck.h +** ext/misc/appendvfs.c +** ext/misc/base64.c +** ext/misc/base85.c +** ext/misc/completion.c +** ext/misc/decimal.c +** ext/misc/fileio.c +** ext/misc/ieee754.c +** ext/misc/memtrace.c +** ext/misc/pcachetrace.c +** ext/misc/regexp.c +** ext/misc/series.c +** ext/misc/sha1.c +** ext/misc/shathree.c +** ext/misc/sqlar.c +** ext/misc/sqlite3_stdio.c +** ext/misc/sqlite3_stdio.h +** ext/misc/stmtrand.c +** ext/misc/uint.c +** ext/misc/vfstrace.c +** ext/misc/windirent.h +** ext/misc/zipfile.c +** ext/qrf/qrf.c +** ext/qrf/qrf.h +** ext/recover/dbdata.c +** ext/recover/sqlite3recover.c +** ext/recover/sqlite3recover.h +** src/shell.c.in ** ** To modify this program, get a copy of the canonical SQLite source tree, -** edit the src/shell.c.in" and/or some of the other files that are included -** by "src/shell.c.in", then rerun the tool/mkshellc.tcl script. +** edit the src/shell.c.in file and/or some of the other files that are +** listed above, then rerun the command "make shell.c". */ +/************************* Begin src/shell.c.in ******************/ /* ** 2001 September 15 ** @@ -27,7 +51,7 @@ ** May you share freely, never taking more than you give. ** ************************************************************************* -** This file contains code to implement the "sqlite" command line +** This file contains code to implement the "sqlite3" command line ** utility for accessing SQLite databases. */ #if (defined(_WIN32) || defined(WIN32)) && !defined(_CRT_SECURE_NO_WARNINGS) @@ -37,6 +61,22 @@ typedef unsigned int u32; typedef unsigned short int u16; +/* +** Limit input nesting via .read or any other input redirect. +** It's not too expensive, so a generous allowance can be made. +*/ +#define MAX_INPUT_NESTING 25 + +/* +** Used to prevent warnings about unused parameters +*/ +#define UNUSED_PARAMETER(x) (void)(x) + +/* +** Number of elements in an array +*/ +#define ArraySize(X) (int)(sizeof(X)/sizeof(X[0])) + /* ** Optionally #include a user-defined header, whereby compilation options ** may be set prior to where they take effect, but after platform setup. @@ -49,14 +89,6 @@ typedef unsigned short int u16; # include SHELL_STRINGIFY(SQLITE_CUSTOM_INCLUDE) #endif -/* -** Determine if we are dealing with WinRT, which provides only a subset of -** the full Win32 API. -*/ -#if !defined(SQLITE_OS_WINRT) -# define SQLITE_OS_WINRT 0 -#endif - /* ** If SQLITE_SHELL_FIDDLE is defined then the shell is modified ** somewhat for use as a WASM module in a web browser. This flag @@ -65,6 +97,10 @@ typedef unsigned short int u16; ** and this build mode rewires the user input subsystem to account for ** that. */ +#if defined(SQLITE_SHELL_FIDDLE) +# undef SQLITE_OMIT_LOAD_EXTENSION +# define SQLITE_OMIT_LOAD_EXTENSION 1 +#endif /* ** Warning pragmas copied from msvc.h in the core. @@ -118,15 +154,17 @@ typedef unsigned short int u16; #include #include #include +#include #include "sqlite3.h" typedef sqlite3_int64 i64; typedef sqlite3_uint64 u64; typedef unsigned char u8; -#if SQLITE_USER_AUTHENTICATION -# include "sqlite3userauth.h" -#endif #include #include +#ifndef _WIN32 +# include +# include +#endif #if !defined(_WIN32) && !defined(WIN32) # include @@ -195,9 +233,6 @@ typedef unsigned char u8; #endif #if defined(_WIN32) || defined(WIN32) -# if SQLITE_OS_WINRT -# define SQLITE_OMIT_POPEN 1 -# else # include # include # define isatty(h) _isatty(h) @@ -210,11 +245,8 @@ typedef unsigned char u8; # ifndef strdup # define strdup _strdup # endif -# undef popen -# define popen _popen # undef pclose # define pclose _pclose -# endif #else /* Make sure isatty() has a prototype. */ extern int isatty(int); @@ -241,2518 +273,3921 @@ typedef unsigned char u8; #define IsSpace(X) isspace((unsigned char)X) #define IsDigit(X) isdigit((unsigned char)X) #define ToLower(X) (char)tolower((unsigned char)X) +#define IsAlnum(X) isalnum((unsigned char)X) +#define IsAlpha(X) isalpha((unsigned char)X) #if defined(_WIN32) || defined(WIN32) -#if SQLITE_OS_WINRT -#include -#endif #undef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #include /* string conversion routines only needed on Win32 */ extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); -extern char *sqlite3_win32_mbcs_to_utf8_v2(const char *, int); -extern char *sqlite3_win32_utf8_to_mbcs_v2(const char *, int); extern LPWSTR sqlite3_win32_utf8_to_unicode(const char *zText); #endif -/* On Windows, we normally run with output mode of TEXT so that \n characters -** are automatically translated into \r\n. However, this behavior needs -** to be disabled in some cases (ex: when generating CSV output and when -** rendering quoted strings that contain \n characters). The following -** routines take care of that. +/************************* Begin ext/misc/sqlite3_stdio.h ******************/ +/* +** 2024-09-24 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This header file contains definitions of interfaces that provide +** cross-platform I/O for UTF-8 content. +** +** On most platforms, the interfaces definitions in this file are +** just #defines. For example sqlite3_fopen() is a macro that resolves +** to the standard fopen() in the C-library. +** +** But Windows does not have a standard C-library, at least not one that +** can handle UTF-8. So for windows build, the interfaces resolve to new +** C-language routines contained in the separate sqlite3_stdio.c source file. +** +** So on all non-Windows platforms, simply #include this header file and +** use the interfaces defined herein. Then to run your application on Windows, +** also link in the accompanying sqlite3_stdio.c source file when compiling +** to get compatible interfaces. */ -#if (defined(_WIN32) || defined(WIN32)) && !SQLITE_OS_WINRT -static void setBinaryMode(FILE *file, int isOutput){ - if( isOutput ) fflush(file); - _setmode(_fileno(file), _O_BINARY); -} -static void setTextMode(FILE *file, int isOutput){ - if( isOutput ) fflush(file); - _setmode(_fileno(file), _O_TEXT); -} -#else -# define setBinaryMode(X,Y) -# define setTextMode(X,Y) -#endif +#ifndef _SQLITE3_STDIO_H_ +#define _SQLITE3_STDIO_H_ 1 +#ifdef _WIN32 +/**** Definitions For Windows ****/ +#include +#include +#include -/* True if the timer is enabled */ -static int enableTimer = 0; +FILE *sqlite3_fopen(const char *zFilename, const char *zMode); +FILE *sqlite3_popen(const char *zCommand, const char *type); +char *sqlite3_fgets(char *s, int size, FILE *stream); +int sqlite3_fputs(const char *s, FILE *stream); +int sqlite3_fprintf(FILE *stream, const char *format, ...); +int sqlite3_vfprintf(FILE *stream, const char *format, va_list); +void sqlite3_fsetmode(FILE *stream, int mode); -/* A version of strcmp() that works with NULL values */ -static int cli_strcmp(const char *a, const char *b){ - if( a==0 ) a = ""; - if( b==0 ) b = ""; - return strcmp(a,b); -} -static int cli_strncmp(const char *a, const char *b, size_t n){ - if( a==0 ) a = ""; - if( b==0 ) b = ""; - return strncmp(a,b,n); -} -/* Return the current wall-clock time */ -static sqlite3_int64 timeOfDay(void){ - static sqlite3_vfs *clockVfs = 0; - sqlite3_int64 t; - if( clockVfs==0 ) clockVfs = sqlite3_vfs_find(0); - if( clockVfs==0 ) return 0; /* Never actually happens */ - if( clockVfs->iVersion>=2 && clockVfs->xCurrentTimeInt64!=0 ){ - clockVfs->xCurrentTimeInt64(clockVfs, &t); - }else{ - double r; - clockVfs->xCurrentTime(clockVfs, &r); - t = (sqlite3_int64)(r*86400000.0); - } - return t; -} +#else +/**** Definitions For All Other Platforms ****/ +#include +#define sqlite3_fopen fopen +#define sqlite3_popen popen +#define sqlite3_fgets fgets +#define sqlite3_fputs fputs +#define sqlite3_fprintf fprintf +#define sqlite3_vfprintf vfprintf +#define sqlite3_fsetmode(F,X) /*no-op*/ -#if !defined(_WIN32) && !defined(WIN32) && !defined(__minux) -#include -#include +#endif +#endif /* _SQLITE3_STDIO_H_ */ -/* VxWorks does not support getrusage() as far as we can determine */ -#if defined(_WRS_KERNEL) || defined(__RTP__) -struct rusage { - struct timeval ru_utime; /* user CPU time used */ - struct timeval ru_stime; /* system CPU time used */ -}; -#define getrusage(A,B) memset(B,0,sizeof(*B)) +/************************* End ext/misc/sqlite3_stdio.h ********************/ +/************************* Begin ext/misc/sqlite3_stdio.c ******************/ +/* +** 2024-09-24 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** Implementation of standard I/O interfaces for UTF-8 that are missing +** on Windows. +*/ +#ifdef _WIN32 /* This file is a no-op on all platforms except Windows */ +#ifndef _SQLITE3_STDIO_H_ +/* #include "sqlite3_stdio.h" */ +#endif +#undef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +/* #include "sqlite3.h" */ +#include +#include +#include +#include + +/* +** If the SQLITE_U8TEXT_ONLY option is defined, then use O_U8TEXT +** when appropriate on all output. (Sometimes use O_BINARY when +** rendering ASCII text in cases where NL-to-CRLF expansion would +** not be correct.) +** +** If the SQLITE_U8TEXT_STDIO option is defined, then use O_U8TEXT +** when appropriate when writing to stdout or stderr. Use O_BINARY +** or O_TEXT (depending on things like the .mode and the .crlf setting +** in the CLI, or other context clues in other applications) for all +** other output channels. +** +** The default behavior, if neither of the above is defined is to +** use O_U8TEXT when writing to the Windows console (or anything +** else for which _isatty() returns true) and to use O_BINARY or O_TEXT +** for all other output channels. +** +** The SQLITE_USE_W32_FOR_CONSOLE_IO macro is also available. If +** defined, it forces the use of Win32 APIs for all console I/O, both +** input and output. This is necessary for some non-Microsoft run-times +** that implement stdio differently from Microsoft/Visual-Studio. +*/ +#if defined(SQLITE_U8TEXT_ONLY) +# define UseWtextForOutput(fd) 1 +# define UseWtextForInput(fd) 1 +# define IsConsole(fd) _isatty(_fileno(fd)) +#elif defined(SQLITE_U8TEXT_STDIO) +# define UseWtextForOutput(fd) ((fd)==stdout || (fd)==stderr) +# define UseWtextForInput(fd) ((fd)==stdin) +# define IsConsole(fd) _isatty(_fileno(fd)) +#else +# define UseWtextForOutput(fd) _isatty(_fileno(fd)) +# define UseWtextForInput(fd) _isatty(_fileno(fd)) +# define IsConsole(fd) 1 #endif -/* Saved resource information for the beginning of an operation */ -static struct rusage sBegin; /* CPU time at start */ -static sqlite3_int64 iBegin; /* Wall-clock time at start */ +/* +** Global variables determine if simulated O_BINARY mode is to be +** used for stdout or other, respectively. Simulated O_BINARY mode +** means the mode is usually O_BINARY, but switches to O_U8TEXT for +** unicode characters U+0080 or greater (any character that has a +** multi-byte representation in UTF-8). This is the only way we +** have found to render Unicode characters on a Windows console while +** at the same time avoiding undesirable \n to \r\n translation. +*/ +static int simBinaryStdout = 0; +static int simBinaryOther = 0; + /* -** Begin timing an operation +** Determine if simulated binary mode should be used for output to fd */ -static void beginTimer(void){ - if( enableTimer ){ - getrusage(RUSAGE_SELF, &sBegin); - iBegin = timeOfDay(); +static int UseBinaryWText(FILE *fd){ + if( fd==stdout || fd==stderr ){ + return simBinaryStdout; + }else{ + return simBinaryOther; } } -/* Return the difference of two time_structs in seconds */ -static double timeDiff(struct timeval *pStart, struct timeval *pEnd){ - return (pEnd->tv_usec - pStart->tv_usec)*0.000001 + - (double)(pEnd->tv_sec - pStart->tv_sec); -} /* -** Print the timing results. +** Work-alike for the fopen() routine from the standard C library. */ -static void endTimer(void){ - if( enableTimer ){ - sqlite3_int64 iEnd = timeOfDay(); - struct rusage sEnd; - getrusage(RUSAGE_SELF, &sEnd); - printf("Run Time: real %.3f user %f sys %f\n", - (iEnd - iBegin)*0.001, - timeDiff(&sBegin.ru_utime, &sEnd.ru_utime), - timeDiff(&sBegin.ru_stime, &sEnd.ru_stime)); +FILE *sqlite3_fopen(const char *zFilename, const char *zMode){ + FILE *fp = 0; + wchar_t *b1, *b2; + int sz1, sz2; + + sz1 = (int)strlen(zFilename); + sz2 = (int)strlen(zMode); + b1 = sqlite3_malloc64( (sz1+1)*sizeof(b1[0]) ); + b2 = sqlite3_malloc64( (sz2+1)*sizeof(b1[0]) ); + if( b1 && b2 ){ + sz1 = MultiByteToWideChar(CP_UTF8, 0, zFilename, sz1, b1, sz1); + b1[sz1] = 0; + sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); + b2[sz2] = 0; + fp = _wfopen(b1, b2); } + sqlite3_free(b1); + sqlite3_free(b2); + simBinaryOther = 0; + return fp; } -#define BEGIN_TIMER beginTimer() -#define END_TIMER endTimer() -#define HAS_TIMER 1 -#elif (defined(_WIN32) || defined(WIN32)) +/* +** Work-alike for the popen() routine from the standard C library. +*/ +FILE *sqlite3_popen(const char *zCommand, const char *zMode){ + FILE *fp = 0; + wchar_t *b1, *b2; + int sz1, sz2; -/* Saved resource information for the beginning of an operation */ -static HANDLE hProcess; -static FILETIME ftKernelBegin; -static FILETIME ftUserBegin; -static sqlite3_int64 ftWallBegin; -typedef BOOL (WINAPI *GETPROCTIMES)(HANDLE, LPFILETIME, LPFILETIME, - LPFILETIME, LPFILETIME); -static GETPROCTIMES getProcessTimesAddr = NULL; + sz1 = (int)strlen(zCommand); + sz2 = (int)strlen(zMode); + b1 = sqlite3_malloc64( (sz1+1)*sizeof(b1[0]) ); + b2 = sqlite3_malloc64( (sz2+1)*sizeof(b1[0]) ); + if( b1 && b2 ){ + sz1 = MultiByteToWideChar(CP_UTF8, 0, zCommand, sz1, b1, sz1); + b1[sz1] = 0; + sz2 = MultiByteToWideChar(CP_UTF8, 0, zMode, sz2, b2, sz2); + b2[sz2] = 0; + fp = _wpopen(b1, b2); + } + sqlite3_free(b1); + sqlite3_free(b2); + return fp; +} /* -** Check to see if we have timer support. Return 1 if necessary -** support found (or found previously). +** Work-alike for fgets() from the standard C library. */ -static int hasTimer(void){ - if( getProcessTimesAddr ){ - return 1; - } else { -#if !SQLITE_OS_WINRT - /* GetProcessTimes() isn't supported in WIN95 and some other Windows - ** versions. See if the version we are running on has it, and if it - ** does, save off a pointer to it and the current process handle. +char *sqlite3_fgets(char *buf, int sz, FILE *in){ + if( UseWtextForInput(in) ){ + /* When reading from the command-prompt in Windows, it is necessary + ** to use _O_WTEXT input mode to read UTF-16 characters, then translate + ** that into UTF-8. Otherwise, non-ASCII characters all get translated + ** into '?'. */ - hProcess = GetCurrentProcess(); - if( hProcess ){ - HINSTANCE hinstLib = LoadLibrary(TEXT("Kernel32.dll")); - if( NULL != hinstLib ){ - getProcessTimesAddr = - (GETPROCTIMES) GetProcAddress(hinstLib, "GetProcessTimes"); - if( NULL != getProcessTimesAddr ){ - return 1; - } - FreeLibrary(hinstLib); + wchar_t *b1 = sqlite3_malloc64( sz*sizeof(wchar_t) ); + if( b1==0 ) return 0; +#ifdef SQLITE_USE_W32_FOR_CONSOLE_IO + DWORD nRead = 0; + if( IsConsole(in) + && ReadConsoleW(GetStdHandle(STD_INPUT_HANDLE), b1, sz-1, &nRead, 0) + ){ + b1[nRead] = 0; + }else +#endif + { + _setmode(_fileno(in), IsConsole(in) ? _O_WTEXT : _O_U8TEXT); + if( fgetws(b1, sz/4, in)==0 ){ + sqlite3_free(b1); + return 0; } } -#endif + WideCharToMultiByte(CP_UTF8, 0, b1, -1, buf, sz, 0, 0); + sqlite3_free(b1); + return buf; + }else{ + /* Reading from a file or other input source, just read bytes without + ** any translation. */ + return fgets(buf, sz, in); } - return 0; } /* -** Begin timing an operation +** Send ASCII text as O_BINARY. But for Unicode characters U+0080 and +** greater, switch to O_U8TEXT. */ -static void beginTimer(void){ - if( enableTimer && getProcessTimesAddr ){ - FILETIME ftCreation, ftExit; - getProcessTimesAddr(hProcess,&ftCreation,&ftExit, - &ftKernelBegin,&ftUserBegin); - ftWallBegin = timeOfDay(); +static void piecemealOutput(wchar_t *b1, int sz, FILE *out){ + int i; + wchar_t c; + while( sz>0 ){ + for(i=0; i=0x80; i++){} + if( i>0 ){ + c = b1[i]; + b1[i] = 0; + fflush(out); + _setmode(_fileno(out), _O_U8TEXT); + fputws(b1, out); + fflush(out); + b1 += i; + b1[0] = c; + sz -= i; + }else{ + fflush(out); + _setmode(_fileno(out), _O_TEXT); + _setmode(_fileno(out), _O_BINARY); + fwrite(&b1[0], 1, 1, out); + for(i=1; i +/* #include "sqlite3.h" */ /* -** On Windows systems we have to know if standard output is a console -** in order to translate UTF-8 into MBCS. The following variable is -** true if translation is required. -*/ -static int stdout_is_console = 1; +** Specification used by clients to define the output format they want +*/ +typedef struct sqlite3_qrf_spec sqlite3_qrf_spec; +struct sqlite3_qrf_spec { + unsigned char iVersion; /* Version number of this structure */ + unsigned char eStyle; /* Formatting style. "box", "csv", etc... */ + unsigned char eEsc; /* How to escape control characters in text */ + unsigned char eText; /* Quoting style for text */ + unsigned char eTitle; /* Quating style for the text of column names */ + unsigned char eBlob; /* Quoting style for BLOBs */ + unsigned char bTitles; /* True to show column names */ + unsigned char bWordWrap; /* Try to wrap on word boundaries */ + unsigned char bTextJsonb; /* Render JSONB blobs as JSON text */ + unsigned char eDfltAlign; /* Default alignment, no covered by aAlignment */ + unsigned char eTitleAlign; /* Alignment for column headers */ + unsigned char bSplitColumn; /* Wrap single-column output into many columns */ + unsigned char bBorder; /* Show outer border in Box and Table styles */ + short int nWrap; /* Wrap columns wider than this */ + short int nScreenWidth; /* Maximum overall table width */ + short int nLineLimit; /* Maximum number of lines for any row */ + short int nTitleLimit; /* Maximum number of characters in a title */ + unsigned int nMultiInsert; /* Add rows to one INSERT until size exceeds */ + int nCharLimit; /* Maximum number of characters in a cell */ + int nWidth; /* Number of entries in aWidth[] */ + int nAlign; /* Number of entries in aAlignment[] */ + short int *aWidth; /* Column widths */ + unsigned char *aAlign; /* Column alignments */ + char *zColumnSep; /* Alternative column separator */ + char *zRowSep; /* Alternative row separator */ + char *zTableName; /* Output table name */ + char *zNull; /* Rendering of NULL */ + char *(*xRender)(void*,sqlite3_value*); /* Render a value */ + int (*xWrite)(void*,const char*,sqlite3_int64); /* Write output */ + void *pRenderArg; /* First argument to the xRender callback */ + void *pWriteArg; /* First argument to the xWrite callback */ + char **pzOutput; /* Storage location for output string */ + /* Additional fields may be added in the future */ +}; /* -** The following is the open SQLite database. We make a pointer -** to this database a static variable so that it can be accessed -** by the SIGINT handler to interrupt database processing. +** Interfaces */ -static sqlite3 *globalDb = 0; +int sqlite3_format_query_result( + sqlite3_stmt *pStmt, /* SQL statement to run */ + const sqlite3_qrf_spec *pSpec, /* Result format specification */ + char **pzErr /* OUT: Write error message here */ +); /* -** True if an interrupt (Control-C) has been received. +** Range of values for sqlite3_qrf_spec.aWidth[] entries and for +** sqlite3_qrf_spec.mxColWidth and .nScreenWidth +*/ +#define QRF_MAX_WIDTH 10000 +#define QRF_MIN_WIDTH 0 + +/* +** Output styles: +*/ +#define QRF_STYLE_Auto 0 /* Choose a style automatically */ +#define QRF_STYLE_Box 1 /* Unicode box-drawing characters */ +#define QRF_STYLE_Column 2 /* One record per line in neat columns */ +#define QRF_STYLE_Count 3 /* Output only a count of the rows of output */ +#define QRF_STYLE_Csv 4 /* Comma-separated-value */ +#define QRF_STYLE_Eqp 5 /* Format EXPLAIN QUERY PLAN output */ +#define QRF_STYLE_Explain 6 /* EXPLAIN output */ +#define QRF_STYLE_Html 7 /* Generate an XHTML table */ +#define QRF_STYLE_Insert 8 /* Generate SQL "insert" statements */ +#define QRF_STYLE_Json 9 /* Output is a list of JSON objects */ +#define QRF_STYLE_JObject 10 /* Independent JSON objects for each row */ +#define QRF_STYLE_Line 11 /* One column per line. */ +#define QRF_STYLE_List 12 /* One record per line with a separator */ +#define QRF_STYLE_Markdown 13 /* Markdown formatting */ +#define QRF_STYLE_Off 14 /* No query output shown */ +#define QRF_STYLE_Quote 15 /* SQL-quoted, comma-separated */ +#define QRF_STYLE_Stats 16 /* EQP-like output but with performance stats */ +#define QRF_STYLE_StatsEst 17 /* EQP-like output with planner estimates */ +#define QRF_STYLE_StatsVm 18 /* EXPLAIN-like output with performance stats */ +#define QRF_STYLE_Table 19 /* MySQL-style table formatting */ + +/* +** Quoting styles for text. +** Allowed values for sqlite3_qrf_spec.eText */ -static volatile int seenInterrupt = 0; +#define QRF_TEXT_Auto 0 /* Choose text encoding automatically */ +#define QRF_TEXT_Plain 1 /* Literal text */ +#define QRF_TEXT_Sql 2 /* Quote as an SQL literal */ +#define QRF_TEXT_Csv 3 /* CSV-style quoting */ +#define QRF_TEXT_Html 4 /* HTML-style quoting */ +#define QRF_TEXT_Tcl 5 /* C/Tcl quoting */ +#define QRF_TEXT_Json 6 /* JSON quoting */ +#define QRF_TEXT_Relaxed 7 /* Relaxed SQL quoting */ /* -** This is the name of our program. It is set in main(), used -** in a number of other places, mostly for error messages. +** Quoting styles for BLOBs +** Allowed values for sqlite3_qrf_spec.eBlob */ -static char *Argv0; +#define QRF_BLOB_Auto 0 /* Determine BLOB quoting using eText */ +#define QRF_BLOB_Text 1 /* Display content exactly as it is */ +#define QRF_BLOB_Sql 2 /* Quote as an SQL literal */ +#define QRF_BLOB_Hex 3 /* Hexadecimal representation */ +#define QRF_BLOB_Tcl 4 /* "\000" notation */ +#define QRF_BLOB_Json 5 /* A JSON string */ +#define QRF_BLOB_Size 6 /* Display the blob size only */ /* -** Prompt strings. Initialized in main. Settable with -** .prompt main continue +** Control-character escape modes. +** Allowed values for sqlite3_qrf_spec.eEsc */ -#define PROMPT_LEN_MAX 20 -/* First line prompt. default: "sqlite> " */ -static char mainPrompt[PROMPT_LEN_MAX]; -/* Continuation prompt. default: " ...> " */ -static char continuePrompt[PROMPT_LEN_MAX]; +#define QRF_ESC_Auto 0 /* Choose the ctrl-char escape automatically */ +#define QRF_ESC_Off 1 /* Do not escape control characters */ +#define QRF_ESC_Ascii 2 /* Unix-style escapes. Ex: U+0007 shows ^G */ +#define QRF_ESC_Symbol 3 /* Unicode escapes. Ex: U+0007 shows U+2407 */ -/* This is variant of the standard-library strncpy() routine with the -** one change that the destination string is always zero-terminated, even -** if there is no zero-terminator in the first n-1 characters of the source -** string. +/* +** Allowed values for "boolean" fields, such as "bColumnNames", "bWordWrap", +** and "bTextJsonb". There is an extra "auto" variants so these are actually +** tri-state settings, not booleans. */ -static char *shell_strncpy(char *dest, const char *src, size_t n){ - size_t i; - for(i=0; iinParenLevel += ni; - if( ni==0 ) p->inParenLevel = 0; - p->zScannerAwaits = 0; +#ifdef __cplusplus } +#endif +#endif /* !defined(SQLITE_QRF_H) */ -/* Record that a lexeme is opened, or closed with args==0. */ -static void setLexemeOpen(struct DynaPrompt *p, char *s, char c){ - if( s!=0 || c==0 ){ - p->zScannerAwaits = s; - p->acAwait[0] = 0; - }else{ - p->acAwait[0] = c; - p->zScannerAwaits = p->acAwait; - } -} +/************************* End ext/qrf/qrf.h ********************/ +/************************* Begin ext/qrf/qrf.c ******************/ +/* +** 2025-10-20 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** Implementation of the Query Result-Format or "qrf" utility library for +** SQLite. See the README.md documentation for additional information. +*/ +#ifndef SQLITE_QRF_H +#include "qrf.h" +#endif +#include +#include +#include -/* Upon demand, derive the continuation prompt to display. */ -static char *dynamicContinuePrompt(void){ - if( continuePrompt[0]==0 - || (dynPrompt.zScannerAwaits==0 && dynPrompt.inParenLevel == 0) ){ - return continuePrompt; - }else{ - if( dynPrompt.zScannerAwaits ){ - size_t ncp = strlen(continuePrompt); - size_t ndp = strlen(dynPrompt.zScannerAwaits); - if( ndp > ncp-3 ) return continuePrompt; - strcpy(dynPrompt.dynamicPrompt, dynPrompt.zScannerAwaits); - while( ndp<3 ) dynPrompt.dynamicPrompt[ndp++] = ' '; - shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, - PROMPT_LEN_MAX-4); - }else{ - if( dynPrompt.inParenLevel>9 ){ - shell_strncpy(dynPrompt.dynamicPrompt, "(..", 4); - }else if( dynPrompt.inParenLevel<0 ){ - shell_strncpy(dynPrompt.dynamicPrompt, ")x!", 4); - }else{ - shell_strncpy(dynPrompt.dynamicPrompt, "(x.", 4); - dynPrompt.dynamicPrompt[2] = (char)('0'+dynPrompt.inParenLevel); - } - shell_strncpy(dynPrompt.dynamicPrompt+3, continuePrompt+3, PROMPT_LEN_MAX-4); - } - } - return dynPrompt.dynamicPrompt; -} -#endif /* !defined(SQLITE_OMIT_DYNAPROMPT) */ +#ifndef SQLITE_AMALGAMATION +/* typedef sqlite3_int64 i64; */ +#endif -#if SHELL_WIN_UTF8_OPT -/* Following struct is used for -utf8 operation. */ -static struct ConsoleState { - int stdinEof; /* EOF has been seen on console input */ - int infsMode; /* Input file stream mode upon shell start */ - UINT inCodePage; /* Input code page upon shell start */ - UINT outCodePage; /* Output code page upon shell start */ - HANDLE hConsoleIn; /* Console input handle */ - DWORD consoleMode; /* Console mode upon shell start */ -} conState = { 0, 0, 0, 0, INVALID_HANDLE_VALUE, 0 }; - -#ifndef _O_U16TEXT /* For build environments lacking this constant: */ -# define _O_U16TEXT 0x20000 -#endif - -/* -** Prepare console, (if known to be a WIN32 console), for UTF-8 -** input (from either typing or suitable paste operations) and for -** UTF-8 rendering. This may "fail" with a message to stderr, where -** the preparation is not done and common "code page" issues occur. -*/ -static void console_prepare(void){ - HANDLE hCI = GetStdHandle(STD_INPUT_HANDLE); - DWORD consoleMode = 0; - if( isatty(0) && GetFileType(hCI)==FILE_TYPE_CHAR - && GetConsoleMode( hCI, &consoleMode) ){ - if( !IsValidCodePage(CP_UTF8) ){ - fprintf(stderr, "Cannot use UTF-8 code page.\n"); - console_utf8 = 0; - return; - } - conState.hConsoleIn = hCI; - conState.consoleMode = consoleMode; - conState.inCodePage = GetConsoleCP(); - conState.outCodePage = GetConsoleOutputCP(); - SetConsoleCP(CP_UTF8); - SetConsoleOutputCP(CP_UTF8); - consoleMode |= ENABLE_LINE_INPUT | ENABLE_PROCESSED_INPUT; - SetConsoleMode(conState.hConsoleIn, consoleMode); - conState.infsMode = _setmode(_fileno(stdin), _O_U16TEXT); - console_utf8 = 1; - }else{ - console_utf8 = 0; - } -} - -/* -** Undo the effects of console_prepare(), if any. -*/ -static void SQLITE_CDECL console_restore(void){ - if( console_utf8 && conState.inCodePage!=0 - && conState.hConsoleIn!=INVALID_HANDLE_VALUE ){ - _setmode(_fileno(stdin), conState.infsMode); - SetConsoleCP(conState.inCodePage); - SetConsoleOutputCP(conState.outCodePage); - SetConsoleMode(conState.hConsoleIn, conState.consoleMode); - /* Avoid multiple calls. */ - conState.hConsoleIn = INVALID_HANDLE_VALUE; - conState.consoleMode = 0; - console_utf8 = 0; - } -} - -/* -** Collect input like fgets(...) with special provisions for input -** from the Windows console to get around its strange coding issues. -** Defers to plain fgets() when input is not interactive or when the -** startup option, -utf8, has not been provided or taken effect. -*/ -static char* utf8_fgets(char *buf, int ncmax, FILE *fin){ - if( fin==0 ) fin = stdin; - if( fin==stdin && stdin_is_interactive && console_utf8 ){ -# define SQLITE_IALIM 150 - wchar_t wbuf[SQLITE_IALIM]; - int lend = 0; - int noc = 0; - if( ncmax==0 || conState.stdinEof ) return 0; - buf[0] = 0; - while( noc SQLITE_IALIM*4+1 + noc) - ? SQLITE_IALIM : (ncmax-1 - noc)/4; -# undef SQLITE_IALIM - DWORD nbr = 0; - BOOL bRC = ReadConsoleW(conState.hConsoleIn, wbuf, na, &nbr, 0); - if( !bRC || (noc==0 && nbr==0) ) return 0; - if( nbr > 0 ){ - int nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, - wbuf,nbr,0,0,0,0); - if( nmb !=0 && noc+nmb <= ncmax ){ - int iseg = noc; - nmb = WideCharToMultiByte(CP_UTF8,WC_COMPOSITECHECK|WC_DEFAULTCHAR, - wbuf,nbr,buf+noc,nmb,0,0); - noc += nmb; - /* Fixup line-ends as coded by Windows for CR (or "Enter".)*/ - if( noc > 0 ){ - if( buf[noc-1]=='\n' ){ - lend = 1; - if( noc > 1 && buf[noc-2]=='\r' ){ - buf[noc-2] = '\n'; - --noc; - } - } - } - /* Check for ^Z (anywhere in line) too. */ - while( iseg < noc ){ - if( buf[iseg]==0x1a ){ - conState.stdinEof = 1; - noc = iseg; /* Chop ^Z and anything following. */ - break; - } - ++iseg; - } - }else break; /* Drop apparent garbage in. (Could assert.) */ - }else break; - } - /* If got nothing, (after ^Z chop), must be at end-of-file. */ - if( noc == 0 ) return 0; - buf[noc] = 0; - return buf; - }else{ - return fgets(buf, ncmax, fin); - } -} +/* A single line in the EQP output */ +typedef struct qrfEQPGraphRow qrfEQPGraphRow; +struct qrfEQPGraphRow { + int iEqpId; /* ID for this row */ + int iParentId; /* ID of the parent row */ + qrfEQPGraphRow *pNext; /* Next row in sequence */ + char zText[1]; /* Text to display for this row */ +}; -# define fgets(b,n,f) utf8_fgets(b,n,f) -#endif /* SHELL_WIN_UTF8_OPT */ +/* All EQP output is collected into an instance of the following */ +typedef struct qrfEQPGraph qrfEQPGraph; +struct qrfEQPGraph { + qrfEQPGraphRow *pRow; /* Linked list of all rows of the EQP output */ + qrfEQPGraphRow *pLast; /* Last element of the pRow list */ + int nWidth; /* Width of the graph */ + char zPrefix[400]; /* Graph prefix */ +}; /* -** Render output like fprintf(). Except, if the output is going to the -** console and if this is running on a Windows machine, and if the -utf8 -** option is unavailable or (available and inactive), translate the -** output from UTF-8 into MBCS for output through 8-bit stdout stream. -** (With -utf8 active, no translation is needed and must not be done.) -*/ -#if defined(_WIN32) || defined(WIN32) -void utf8_printf(FILE *out, const char *zFormat, ...){ - va_list ap; - va_start(ap, zFormat); - if( stdout_is_console && (out==stdout || out==stderr) -# if SHELL_WIN_UTF8_OPT - && !console_utf8 -# endif - ){ - char *z1 = sqlite3_vmprintf(zFormat, ap); - char *z2 = sqlite3_win32_utf8_to_mbcs_v2(z1, 0); - sqlite3_free(z1); - fputs(z2, out); - sqlite3_free(z2); - }else{ - vfprintf(out, zFormat, ap); - } - va_end(ap); -} -#elif !defined(utf8_printf) -# define utf8_printf fprintf -#endif +** Private state information. Subject to change from one release to the +** next. +*/ +typedef struct Qrf Qrf; +struct Qrf { + sqlite3_stmt *pStmt; /* The statement whose output is to be rendered */ + sqlite3 *db; /* The corresponding database connection */ + sqlite3_stmt *pJTrans; /* JSONB to JSON translator statement */ + char **pzErr; /* Write error message here, if not NULL */ + sqlite3_str *pOut; /* Accumulated output */ + int iErr; /* Error code */ + int nCol; /* Number of output columns */ + int expMode; /* Original sqlite3_stmt_isexplain() plus 1 */ + int mxWidth; /* Screen width */ + int mxHeight; /* nLineLimit */ + union { + struct { /* Content for QRF_STYLE_Line */ + int mxColWth; /* Maximum display width of any column */ + char **azCol; /* Names of output columns (MODE_Line) */ + } sLine; + qrfEQPGraph *pGraph; /* EQP graph (Eqp, Stats, and StatsEst) */ + struct { /* Content for QRF_STYLE_Explain */ + int nIndent; /* Slots allocated for aiIndent */ + int iIndent; /* Current slot */ + int *aiIndent; /* Indentation for each opcode */ + } sExpln; + unsigned int nIns; /* Bytes used for current INSERT stmt */ + } u; + sqlite3_int64 nRow; /* Number of rows handled so far */ + int *actualWidth; /* Actual width of each column */ + sqlite3_qrf_spec spec; /* Copy of the original spec */ +}; /* -** Render output like fprintf(). This should not be used on anything that -** includes string formatting (e.g. "%s"). -*/ -#if !defined(raw_printf) -# define raw_printf fprintf -#endif +** Data for substitute ctype.h functions. Used for x-platform +** consistency and so that '_' is counted as an alphabetic +** character. +** +** 0x01 - space +** 0x02 - digit +** 0x04 - alphabetic, including '_' +*/ +static const char qrfCType[] = { + 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 0, 0, 0, + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 4, + 0, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +}; +#define qrfSpace(x) ((qrfCType[(unsigned char)x]&1)!=0) +#define qrfDigit(x) ((qrfCType[(unsigned char)x]&2)!=0) +#define qrfAlpha(x) ((qrfCType[(unsigned char)x]&4)!=0) +#define qrfAlnum(x) ((qrfCType[(unsigned char)x]&6)!=0) -/* Indicate out-of-memory and exit. */ -static void shell_out_of_memory(void){ - raw_printf(stderr,"Error: out of memory\n"); - exit(1); -} +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if defined(GCC_VERSION) && GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif -/* Check a pointer to see if it is NULL. If it is NULL, exit with an -** out-of-memory error. +/* +** Set an error code and error message. */ -static void shell_check_oom(const void *p){ - if( p==0 ) shell_out_of_memory(); +static void qrfError( + Qrf *p, /* Query result state */ + int iCode, /* Error code */ + const char *zFormat, /* Message format (or NULL) */ + ... +){ + p->iErr = iCode; + if( p->pzErr!=0 ){ + sqlite3_free(*p->pzErr); + *p->pzErr = 0; + if( zFormat ){ + va_list ap; + va_start(ap, zFormat); + *p->pzErr = sqlite3_vmprintf(zFormat, ap); + va_end(ap); + } + } } /* -** Write I/O traces to the following stream. +** Out-of-memory error. */ -#ifdef SQLITE_ENABLE_IOTRACE -static FILE *iotrace = 0; -#endif +static void qrfOom(Qrf *p){ + qrfError(p, SQLITE_NOMEM, "out of memory"); +} /* -** This routine works like printf in that its first argument is a -** format string and subsequent arguments are values to be substituted -** in place of % fields. The result of formatting this string -** is written to iotrace. +** Transfer any error in pStr over into p. */ -#ifdef SQLITE_ENABLE_IOTRACE -static void SQLITE_CDECL iotracePrintf(const char *zFormat, ...){ - va_list ap; - char *z; - if( iotrace==0 ) return; - va_start(ap, zFormat); - z = sqlite3_vmprintf(zFormat, ap); - va_end(ap); - utf8_printf(iotrace, "%s", z); - sqlite3_free(z); +static void qrfStrErr(Qrf *p, sqlite3_str *pStr){ + int rc = pStr ? sqlite3_str_errcode(pStr) : 0; + if( rc ){ + qrfError(p, rc, sqlite3_errstr(rc)); + } } -#endif + /* -** Output string zUtf to stream pOut as w characters. If w is negative, -** then right-justify the text. W is the width in UTF-8 characters, not -** in bytes. This is different from the %*.*s specification in printf -** since with %*.*s the width is measured in bytes, not characters. +** Add a new entry to the EXPLAIN QUERY PLAN data */ -static void utf8_width_print(FILE *pOut, int w, const char *zUtf){ - int i; - int n; - int aw = w<0 ? -w : w; - if( zUtf==0 ) zUtf = ""; - for(i=n=0; zUtf[i]; i++){ - if( (zUtf[i]&0xc0)!=0x80 ){ - n++; - if( n==aw ){ - do{ i++; }while( (zUtf[i]&0xc0)==0x80 ); - break; - } +static void qrfEqpAppend(Qrf *p, int iEqpId, int p2, const char *zText){ + qrfEQPGraphRow *pNew; + sqlite3_int64 nText; + if( zText==0 ) return; + if( p->u.pGraph==0 ){ + p->u.pGraph = sqlite3_malloc64( sizeof(qrfEQPGraph) ); + if( p->u.pGraph==0 ){ + qrfOom(p); + return; } + memset(p->u.pGraph, 0, sizeof(qrfEQPGraph) ); } - if( n>=aw ){ - utf8_printf(pOut, "%.*s", i, zUtf); - }else if( w<0 ){ - utf8_printf(pOut, "%*s%s", aw-n, "", zUtf); + nText = strlen(zText); + pNew = sqlite3_malloc64( sizeof(*pNew) + nText ); + if( pNew==0 ){ + qrfOom(p); + return; + } + pNew->iEqpId = iEqpId; + pNew->iParentId = p2; + memcpy(pNew->zText, zText, nText+1); + pNew->pNext = 0; + if( p->u.pGraph->pLast ){ + p->u.pGraph->pLast->pNext = pNew; }else{ - utf8_printf(pOut, "%s%*s", zUtf, aw-n, ""); + p->u.pGraph->pRow = pNew; } + p->u.pGraph->pLast = pNew; } - /* -** Determines if a string is a number of not. +** Free and reset the EXPLAIN QUERY PLAN data that has been collected +** in p->u.pGraph. */ -static int isNumber(const char *z, int *realnum){ - if( *z=='-' || *z=='+' ) z++; - if( !IsDigit(*z) ){ - return 0; - } - z++; - if( realnum ) *realnum = 0; - while( IsDigit(*z) ){ z++; } - if( *z=='.' ){ - z++; - if( !IsDigit(*z) ) return 0; - while( IsDigit(*z) ){ z++; } - if( realnum ) *realnum = 1; - } - if( *z=='e' || *z=='E' ){ - z++; - if( *z=='+' || *z=='-' ) z++; - if( !IsDigit(*z) ) return 0; - while( IsDigit(*z) ){ z++; } - if( realnum ) *realnum = 1; +static void qrfEqpReset(Qrf *p){ + qrfEQPGraphRow *pRow, *pNext; + if( p->u.pGraph ){ + for(pRow = p->u.pGraph->pRow; pRow; pRow = pNext){ + pNext = pRow->pNext; + sqlite3_free(pRow); + } + sqlite3_free(p->u.pGraph); + p->u.pGraph = 0; } - return *z==0; } -/* -** Compute a string length that is limited to what can be stored in -** lower 30 bits of a 32-bit signed integer. +/* Return the next EXPLAIN QUERY PLAN line with iEqpId that occurs after +** pOld, or return the first such line if pOld is NULL */ -static int strlen30(const char *z){ - const char *z2 = z; - while( *z2 ){ z2++; } - return 0x3fffffff & (int)(z2 - z); +static qrfEQPGraphRow *qrfEqpNextRow(Qrf *p, int iEqpId, qrfEQPGraphRow *pOld){ + qrfEQPGraphRow *pRow = pOld ? pOld->pNext : p->u.pGraph->pRow; + while( pRow && pRow->iParentId!=iEqpId ) pRow = pRow->pNext; + return pRow; } -/* -** Return the length of a string in characters. Multibyte UTF8 characters -** count as a single character. +/* Render a single level of the graph that has iEqpId as its parent. Called +** recursively to render sublevels. */ -static int strlenChar(const char *z){ - int n = 0; - while( *z ){ - if( (0xc0&*(z++))!=0x80 ) n++; +static void qrfEqpRenderLevel(Qrf *p, int iEqpId){ + qrfEQPGraphRow *pRow, *pNext; + i64 n = strlen(p->u.pGraph->zPrefix); + char *z; + for(pRow = qrfEqpNextRow(p, iEqpId, 0); pRow; pRow = pNext){ + pNext = qrfEqpNextRow(p, iEqpId, pRow); + z = pRow->zText; + sqlite3_str_appendf(p->pOut, "%s%s%s\n", p->u.pGraph->zPrefix, + pNext ? "|--" : "`--", z); + if( n<(i64)sizeof(p->u.pGraph->zPrefix)-7 ){ + memcpy(&p->u.pGraph->zPrefix[n], pNext ? "| " : " ", 4); + qrfEqpRenderLevel(p, pRow->iEqpId); + p->u.pGraph->zPrefix[n] = 0; + } } - return n; } /* -** Return open FILE * if zFile exists, can be opened for read -** and is an ordinary file or a character stream source. -** Otherwise return 0. +** Render the 64-bit value N in a more human-readable format into +** pOut. +** +** + Only show the first three significant digits. +** + Append suffixes K, M, G, T, P, and E for 1e3, 1e6, ... 1e18 */ -static FILE * openChrSource(const char *zFile){ -#ifdef _WIN32 - struct _stat x = {0}; -# define STAT_CHR_SRC(mode) ((mode & (_S_IFCHR|_S_IFIFO|_S_IFREG))!=0) - /* On Windows, open first, then check the stream nature. This order - ** is necessary because _stat() and sibs, when checking a named pipe, - ** effectively break the pipe as its supplier sees it. */ - FILE *rv = fopen(zFile, "rb"); - if( rv==0 ) return 0; - if( _fstat(_fileno(rv), &x) != 0 - || !STAT_CHR_SRC(x.st_mode)){ - fclose(rv); - rv = 0; +static void qrfApproxInt64(sqlite3_str *pOut, i64 N){ + static const char aSuffix[] = { 'K', 'M', 'G', 'T', 'P', 'E' }; + int i; + if( N<0 ){ + N = N==INT64_MIN ? INT64_MAX : -N; + sqlite3_str_append(pOut, "-", 1); } - return rv; -#else - struct stat x = {0}; - int rc = stat(zFile, &x); -# define STAT_CHR_SRC(mode) (S_ISREG(mode)||S_ISFIFO(mode)||S_ISCHR(mode)) - if( rc!=0 ) return 0; - if( STAT_CHR_SRC(x.st_mode) ){ - return fopen(zFile, "rb"); - }else{ - return 0; + if( N<10000 ){ + sqlite3_str_appendf(pOut, "%4lld ", N); + return; } -#endif -#undef STAT_CHR_SRC -} - -/* -** This routine reads a line of text from FILE in, stores -** the text in memory obtained from malloc() and returns a pointer -** to the text. NULL is returned at end of file, or if malloc() -** fails. -** -** If zLine is not NULL then it is a malloced buffer returned from -** a previous call to this routine that may be reused. -*/ -static char *local_getline(char *zLine, FILE *in){ - int nLine = zLine==0 ? 0 : 100; - int n = 0; - - while( 1 ){ - if( n+100>nLine ){ - nLine = nLine*2 + 100; - zLine = realloc(zLine, nLine); - shell_check_oom(zLine); - } - if( fgets(&zLine[n], nLine - n, in)==0 ){ - if( n==0 ){ - free(zLine); - return 0; + for(i=1; i<=18; i++){ + N = (N+5)/10; + if( N<10000 ){ + int n = (int)N; + switch( i%3 ){ + case 0: + sqlite3_str_appendf(pOut, "%d.%02d", n/1000, (n%1000)/10); + break; + case 1: + sqlite3_str_appendf(pOut, "%2d.%d", n/100, (n%100)/10); + break; + case 2: + sqlite3_str_appendf(pOut, "%4d", n/10); + break; } - zLine[n] = 0; - break; - } - while( zLine[n] ) n++; - if( n>0 && zLine[n-1]=='\n' ){ - n--; - if( n>0 && zLine[n-1]=='\r' ) n--; - zLine[n] = 0; + sqlite3_str_append(pOut, &aSuffix[i/3], 1); break; } } -#if defined(_WIN32) || defined(WIN32) - /* For interactive input on Windows systems, without -utf8, - ** translate the multi-byte characterset characters into UTF-8. - ** This is the translation that predates the -utf8 option. */ - if( stdin_is_interactive && in==stdin -# if SHELL_WIN_UTF8_OPT - && !console_utf8 -# endif /* SHELL_WIN_UTF8_OPT */ - ){ - char *zTrans = sqlite3_win32_mbcs_to_utf8_v2(zLine, 0); - if( zTrans ){ - i64 nTrans = strlen(zTrans)+1; - if( nTrans>nLine ){ - zLine = realloc(zLine, nTrans); - shell_check_oom(zLine); +} + +/* +** Display and reset the EXPLAIN QUERY PLAN data +*/ +static void qrfEqpRender(Qrf *p, i64 nCycle){ + qrfEQPGraphRow *pRow; + if( p->u.pGraph!=0 && (pRow = p->u.pGraph->pRow)!=0 ){ + if( pRow->zText[0]=='-' ){ + if( pRow->pNext==0 ){ + qrfEqpReset(p); + return; } - memcpy(zLine, zTrans, nTrans); - sqlite3_free(zTrans); + sqlite3_str_appendf(p->pOut, "%s\n", pRow->zText+3); + p->u.pGraph->pRow = pRow->pNext; + sqlite3_free(pRow); + }else if( nCycle>0 ){ + int nSp = p->u.pGraph->nWidth - 2; + if( p->spec.eStyle==QRF_STYLE_StatsEst ){ + sqlite3_str_appendchar(p->pOut, nSp, ' '); + sqlite3_str_appendall(p->pOut, + "Cycles Loops (est) Rows (est)\n"); + sqlite3_str_appendchar(p->pOut, nSp, ' '); + sqlite3_str_appendall(p->pOut, + "---------- ------------ ------------\n"); + }else{ + sqlite3_str_appendchar(p->pOut, nSp, ' '); + sqlite3_str_appendall(p->pOut, + "Cycles Loops Rows \n"); + sqlite3_str_appendchar(p->pOut, nSp, ' '); + sqlite3_str_appendall(p->pOut, + "---------- ----- -----\n"); + } + sqlite3_str_appendall(p->pOut, "QUERY PLAN"); + sqlite3_str_appendchar(p->pOut, nSp - 10, ' '); + qrfApproxInt64(p->pOut, nCycle); + sqlite3_str_appendall(p->pOut, " 100%\n"); + }else{ + sqlite3_str_appendall(p->pOut, "QUERY PLAN\n"); } + p->u.pGraph->zPrefix[0] = 0; + qrfEqpRenderLevel(p, 0); + qrfEqpReset(p); } -#endif /* defined(_WIN32) || defined(WIN32) */ - return zLine; } +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS /* -** Retrieve a single line of input text. -** -** If in==0 then read from standard input and prompt before each line. -** If isContinuation is true, then a continuation prompt is appropriate. -** If isContinuation is zero, then the main prompt should be used. -** -** If zPrior is not NULL then it is a buffer from a prior call to this -** routine that can be reused. +** Helper function for qrfExpStats(). ** -** The result is stored in space obtained from malloc() and must either -** be freed by the caller or else passed back into this routine via the -** zPrior argument for reuse. */ -#ifndef SQLITE_SHELL_FIDDLE -static char *one_input_line(FILE *in, char *zPrior, int isContinuation){ - char *zPrompt; - char *zResult; - if( in!=0 ){ - zResult = local_getline(zPrior, in); - }else{ - zPrompt = isContinuation ? CONTINUATION_PROMPT : mainPrompt; -#if SHELL_USE_LOCAL_GETLINE - printf("%s", zPrompt); - fflush(stdout); - do{ - zResult = local_getline(zPrior, stdin); - zPrior = 0; - /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ - if( zResult==0 ) sqlite3_sleep(50); - }while( zResult==0 && seenInterrupt>0 ); -#else - free(zPrior); - zResult = shell_readline(zPrompt); - while( zResult==0 ){ - /* ^C trap creates a false EOF, so let "interrupt" thread catch up. */ - sqlite3_sleep(50); - if( seenInterrupt==0 ) break; - zResult = shell_readline(""); +static int qrfStatsHeight(sqlite3_stmt *p, int iEntry){ + int iPid = 0; + int ret = 1; + sqlite3_stmt_scanstatus_v2(p, iEntry, + SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid + ); + while( iPid!=0 ){ + int ii; + for(ii=0; 1; ii++){ + int iId; + int res; + res = sqlite3_stmt_scanstatus_v2(p, ii, + SQLITE_SCANSTAT_SELECTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iId + ); + if( res ) break; + if( iId==iPid ){ + sqlite3_stmt_scanstatus_v2(p, ii, + SQLITE_SCANSTAT_PARENTID, SQLITE_SCANSTAT_COMPLEX, (void*)&iPid + ); + } } - if( zResult && *zResult ) shell_add_history(zResult); -#endif + ret++; } - return zResult; + return ret; } -#endif /* !SQLITE_SHELL_FIDDLE */ +#endif /* SQLITE_ENABLE_STMT_SCANSTATUS */ -/* -** Return the value of a hexadecimal digit. Return -1 if the input -** is not a hex digit. -*/ -static int hexDigitValue(char c){ - if( c>='0' && c<='9' ) return c - '0'; - if( c>='a' && c<='f' ) return c - 'a' + 10; - if( c>='A' && c<='F' ) return c - 'A' + 10; - return -1; -} /* -** Interpret zArg as an integer value, possibly with suffixes. +** Generate ".scanstatus est" style of EQP output. */ -static sqlite3_int64 integerValue(const char *zArg){ - sqlite3_int64 v = 0; - static const struct { char *zSuffix; int iMult; } aMult[] = { - { "KiB", 1024 }, - { "MiB", 1024*1024 }, - { "GiB", 1024*1024*1024 }, - { "KB", 1000 }, - { "MB", 1000000 }, - { "GB", 1000000000 }, - { "K", 1000 }, - { "M", 1000000 }, - { "G", 1000000000 }, - }; - int i; - int isNeg = 0; - if( zArg[0]=='-' ){ - isNeg = 1; - zArg++; - }else if( zArg[0]=='+' ){ - zArg++; - } - if( zArg[0]=='0' && zArg[1]=='x' ){ - int x; - zArg += 2; - while( (x = hexDigitValue(zArg[0]))>=0 ){ - v = (v<<4) + x; - zArg++; - } - }else{ - while( IsDigit(zArg[0]) ){ - v = v*10 + zArg[0] - '0'; - zArg++; +static void qrfEqpStats(Qrf *p){ +#ifndef SQLITE_ENABLE_STMT_SCANSTATUS + qrfError(p, SQLITE_ERROR, "not available in this build"); +#else + static const int f = SQLITE_SCANSTAT_COMPLEX; + sqlite3_stmt *pS = p->pStmt; + int i = 0; + i64 nTotal = 0; + int nWidth = 0; + int prevPid = -1; /* Previous iPid */ + double rEstCum = 1.0; /* Cumulative row estimate */ + sqlite3_str *pLine = sqlite3_str_new(p->db); + sqlite3_str *pStats = sqlite3_str_new(p->db); + qrfEqpReset(p); + + for(i=0; 1; i++){ + const char *z = 0; + int n = 0; + if( sqlite3_stmt_scanstatus_v2(pS,i,SQLITE_SCANSTAT_EXPLAIN,f,(void*)&z) ){ + break; } + n = (int)strlen(z) + qrfStatsHeight(pS,i)*3; + if( n>nWidth ) nWidth = n; } - for(i=0; i=0 || nLoop>=0 || nRow>=0 ){ + int nSp = 0; + sqlite3_str_reset(pStats); + if( nCycle>=0 && nTotal>0 ){ + qrfApproxInt64(pStats, nCycle); + sqlite3_str_appendf(pStats, " %3d%%", + ((nCycle*100)+nTotal/2) / nTotal + ); + nSp = 2; + } + if( nLoop>=0 ){ + if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' '); + qrfApproxInt64(pStats, nLoop); + nSp = 2; + if( p->spec.eStyle==QRF_STYLE_StatsEst ){ + sqlite3_str_appendf(pStats, " "); + qrfApproxInt64(pStats, (i64)(rEstCum/rEst)); + } + } + if( nRow>=0 ){ + if( nSp ) sqlite3_str_appendchar(pStats, nSp, ' '); + qrfApproxInt64(pStats, nRow); + nSp = 2; + if( p->spec.eStyle==QRF_STYLE_StatsEst ){ + sqlite3_str_appendf(pStats, " "); + qrfApproxInt64(pStats, (i64)rEstCum); + } + } + sqlite3_str_appendf(pLine, + "% *s %s", -1*(nWidth-qrfStatsHeight(pS,i)*3), zo, + sqlite3_str_value(pStats) + ); + sqlite3_str_reset(pStats); + qrfEqpAppend(p, iId, iPid, sqlite3_str_value(pLine)); + sqlite3_str_reset(pLine); + }else{ + qrfEqpAppend(p, iId, iPid, zo); + } } - return isNeg? -v : v; + if( p->u.pGraph ) p->u.pGraph->nWidth = nWidth; + qrfStrErr(p, pLine); + sqlite3_free(sqlite3_str_finish(pLine)); + qrfStrErr(p, pStats); + sqlite3_free(sqlite3_str_finish(pStats)); +#endif } + /* -** A variable length string to which one can append text. +** Reset the prepared statement. */ -typedef struct ShellText ShellText; -struct ShellText { - char *z; - int n; - int nAlloc; -}; +static void qrfResetStmt(Qrf *p){ + int rc = sqlite3_reset(p->pStmt); + if( rc!=SQLITE_OK && p->iErr==SQLITE_OK ){ + qrfError(p, rc, "%s", sqlite3_errmsg(p->db)); + } +} /* -** Initialize and destroy a ShellText object +** If xWrite is defined, send all content of pOut to xWrite and +** reset pOut. */ -static void initText(ShellText *p){ - memset(p, 0, sizeof(*p)); -} -static void freeText(ShellText *p){ - free(p->z); - initText(p); -} +static void qrfWrite(Qrf *p){ + int n; + if( p->spec.xWrite && (n = sqlite3_str_length(p->pOut))>0 ){ + int rc = p->spec.xWrite(p->spec.pWriteArg, + sqlite3_str_value(p->pOut), + (sqlite3_int64)n); + sqlite3_str_reset(p->pOut); + if( rc ){ + qrfError(p, rc, "Failed to write %d bytes of output", n); + } + } +} + +/* Lookup table to estimate the number of columns consumed by a Unicode +** character. +*/ +static const struct { + unsigned char w; /* Width of the character in columns */ + int iFirst; /* First character in a span having this width */ +} aQrfUWidth[] = { + /* {1, 0x00000}, */ + {0, 0x00300}, {1, 0x00370}, {0, 0x00483}, {1, 0x00487}, {0, 0x00488}, + {1, 0x0048a}, {0, 0x00591}, {1, 0x005be}, {0, 0x005bf}, {1, 0x005c0}, + {0, 0x005c1}, {1, 0x005c3}, {0, 0x005c4}, {1, 0x005c6}, {0, 0x005c7}, + {1, 0x005c8}, {0, 0x00600}, {1, 0x00604}, {0, 0x00610}, {1, 0x00616}, + {0, 0x0064b}, {1, 0x0065f}, {0, 0x00670}, {1, 0x00671}, {0, 0x006d6}, + {1, 0x006e5}, {0, 0x006e7}, {1, 0x006e9}, {0, 0x006ea}, {1, 0x006ee}, + {0, 0x0070f}, {1, 0x00710}, {0, 0x00711}, {1, 0x00712}, {0, 0x00730}, + {1, 0x0074b}, {0, 0x007a6}, {1, 0x007b1}, {0, 0x007eb}, {1, 0x007f4}, + {0, 0x00901}, {1, 0x00903}, {0, 0x0093c}, {1, 0x0093d}, {0, 0x00941}, + {1, 0x00949}, {0, 0x0094d}, {1, 0x0094e}, {0, 0x00951}, {1, 0x00955}, + {0, 0x00962}, {1, 0x00964}, {0, 0x00981}, {1, 0x00982}, {0, 0x009bc}, + {1, 0x009bd}, {0, 0x009c1}, {1, 0x009c5}, {0, 0x009cd}, {1, 0x009ce}, + {0, 0x009e2}, {1, 0x009e4}, {0, 0x00a01}, {1, 0x00a03}, {0, 0x00a3c}, + {1, 0x00a3d}, {0, 0x00a41}, {1, 0x00a43}, {0, 0x00a47}, {1, 0x00a49}, + {0, 0x00a4b}, {1, 0x00a4e}, {0, 0x00a70}, {1, 0x00a72}, {0, 0x00a81}, + {1, 0x00a83}, {0, 0x00abc}, {1, 0x00abd}, {0, 0x00ac1}, {1, 0x00ac6}, + {0, 0x00ac7}, {1, 0x00ac9}, {0, 0x00acd}, {1, 0x00ace}, {0, 0x00ae2}, + {1, 0x00ae4}, {0, 0x00b01}, {1, 0x00b02}, {0, 0x00b3c}, {1, 0x00b3d}, + {0, 0x00b3f}, {1, 0x00b40}, {0, 0x00b41}, {1, 0x00b44}, {0, 0x00b4d}, + {1, 0x00b4e}, {0, 0x00b56}, {1, 0x00b57}, {0, 0x00b82}, {1, 0x00b83}, + {0, 0x00bc0}, {1, 0x00bc1}, {0, 0x00bcd}, {1, 0x00bce}, {0, 0x00c3e}, + {1, 0x00c41}, {0, 0x00c46}, {1, 0x00c49}, {0, 0x00c4a}, {1, 0x00c4e}, + {0, 0x00c55}, {1, 0x00c57}, {0, 0x00cbc}, {1, 0x00cbd}, {0, 0x00cbf}, + {1, 0x00cc0}, {0, 0x00cc6}, {1, 0x00cc7}, {0, 0x00ccc}, {1, 0x00cce}, + {0, 0x00ce2}, {1, 0x00ce4}, {0, 0x00d41}, {1, 0x00d44}, {0, 0x00d4d}, + {1, 0x00d4e}, {0, 0x00dca}, {1, 0x00dcb}, {0, 0x00dd2}, {1, 0x00dd5}, + {0, 0x00dd6}, {1, 0x00dd7}, {0, 0x00e31}, {1, 0x00e32}, {0, 0x00e34}, + {1, 0x00e3b}, {0, 0x00e47}, {1, 0x00e4f}, {0, 0x00eb1}, {1, 0x00eb2}, + {0, 0x00eb4}, {1, 0x00eba}, {0, 0x00ebb}, {1, 0x00ebd}, {0, 0x00ec8}, + {1, 0x00ece}, {0, 0x00f18}, {1, 0x00f1a}, {0, 0x00f35}, {1, 0x00f36}, + {0, 0x00f37}, {1, 0x00f38}, {0, 0x00f39}, {1, 0x00f3a}, {0, 0x00f71}, + {1, 0x00f7f}, {0, 0x00f80}, {1, 0x00f85}, {0, 0x00f86}, {1, 0x00f88}, + {0, 0x00f90}, {1, 0x00f98}, {0, 0x00f99}, {1, 0x00fbd}, {0, 0x00fc6}, + {1, 0x00fc7}, {0, 0x0102d}, {1, 0x01031}, {0, 0x01032}, {1, 0x01033}, + {0, 0x01036}, {1, 0x0103b}, {0, 0x01058}, + {1, 0x0105a}, {2, 0x01100}, {0, 0x01160}, {1, 0x01200}, {0, 0x0135f}, + {1, 0x01360}, {0, 0x01712}, {1, 0x01715}, {0, 0x01732}, {1, 0x01735}, + {0, 0x01752}, {1, 0x01754}, {0, 0x01772}, {1, 0x01774}, {0, 0x017b4}, + {1, 0x017b6}, {0, 0x017b7}, {1, 0x017be}, {0, 0x017c6}, {1, 0x017c7}, + {0, 0x017c9}, {1, 0x017d4}, {0, 0x017dd}, {1, 0x017de}, {0, 0x0180b}, + {1, 0x0180e}, {0, 0x018a9}, {1, 0x018aa}, {0, 0x01920}, {1, 0x01923}, + {0, 0x01927}, {1, 0x01929}, {0, 0x01932}, {1, 0x01933}, {0, 0x01939}, + {1, 0x0193c}, {0, 0x01a17}, {1, 0x01a19}, {0, 0x01b00}, {1, 0x01b04}, + {0, 0x01b34}, {1, 0x01b35}, {0, 0x01b36}, {1, 0x01b3b}, {0, 0x01b3c}, + {1, 0x01b3d}, {0, 0x01b42}, {1, 0x01b43}, {0, 0x01b6b}, {1, 0x01b74}, + {0, 0x01dc0}, {1, 0x01dcb}, {0, 0x01dfe}, {1, 0x01e00}, {0, 0x0200b}, + {1, 0x02010}, {0, 0x0202a}, {1, 0x0202f}, {0, 0x02060}, {1, 0x02064}, + {0, 0x0206a}, {1, 0x02070}, {0, 0x020d0}, {1, 0x020f0}, {2, 0x02329}, + {1, 0x0232b}, {2, 0x02e80}, {0, 0x0302a}, {2, 0x03030}, {1, 0x0303f}, + {2, 0x03040}, {0, 0x03099}, {2, 0x0309b}, {1, 0x0a4d0}, {0, 0x0a806}, + {1, 0x0a807}, {0, 0x0a80b}, {1, 0x0a80c}, {0, 0x0a825}, {1, 0x0a827}, + {2, 0x0ac00}, {1, 0x0d7a4}, {2, 0x0f900}, {1, 0x0fb00}, {0, 0x0fb1e}, + {1, 0x0fb1f}, {0, 0x0fe00}, {2, 0x0fe10}, {1, 0x0fe1a}, {0, 0x0fe20}, + {1, 0x0fe24}, {2, 0x0fe30}, {1, 0x0fe70}, {0, 0x0feff}, {2, 0x0ff00}, + {1, 0x0ff61}, {2, 0x0ffe0}, {1, 0x0ffe7}, {0, 0x0fff9}, {1, 0x0fffc}, + {0, 0x10a01}, {1, 0x10a04}, {0, 0x10a05}, {1, 0x10a07}, {0, 0x10a0c}, + {1, 0x10a10}, {0, 0x10a38}, {1, 0x10a3b}, {0, 0x10a3f}, {1, 0x10a40}, + {0, 0x1d167}, {1, 0x1d16a}, {0, 0x1d173}, {1, 0x1d183}, {0, 0x1d185}, + {1, 0x1d18c}, {0, 0x1d1aa}, {1, 0x1d1ae}, {0, 0x1d242}, {1, 0x1d245}, + {2, 0x20000}, {1, 0x2fffe}, {2, 0x30000}, {1, 0x3fffe}, {0, 0xe0001}, + {1, 0xe0002}, {0, 0xe0020}, {1, 0xe0080}, {0, 0xe0100}, {1, 0xe01f0} +}; -/* zIn is either a pointer to a NULL-terminated string in memory obtained -** from malloc(), or a NULL pointer. The string pointed to by zAppend is -** added to zIn, and the result returned in memory obtained from malloc(). -** zIn, if it was not NULL, is freed. +/* +** Return an estimate of the width, in columns, for the single Unicode +** character c. For normal characters, the answer is always 1. But the +** estimate might be 0 or 2 for zero-width and double-width characters. ** -** If the third argument, quote, is not '\0', then it is used as a -** quote character for zAppend. +** Different display devices display unicode using different widths. So +** it is impossible to know that true display width with 100% accuracy. +** Inaccuracies in the width estimates might cause columns to be misaligned. +** Unfortunately, there is nothing we can do about that. */ -static void appendText(ShellText *p, const char *zAppend, char quote){ - i64 len; - i64 i; - i64 nAppend = strlen30(zAppend); +int sqlite3_qrf_wcwidth(int c){ + int iFirst, iLast; - len = nAppend+p->n+1; - if( quote ){ - len += 2; - for(i=0; i c ){ + iLast = iMid - 1; + }else{ + return aQrfUWidth[iMid].w; } } - - if( p->z==0 || p->n+len>=p->nAlloc ){ - p->nAlloc = p->nAlloc*2 + len + 20; - p->z = realloc(p->z, p->nAlloc); - shell_check_oom(p->z); - } - - if( quote ){ - char *zCsr = p->z+p->n; - *zCsr++ = quote; - for(i=0; in = (int)(zCsr - p->z); - *zCsr = '\0'; - }else{ - memcpy(p->z+p->n, zAppend, nAppend); - p->n += nAppend; - p->z[p->n] = '\0'; - } + if( aQrfUWidth[iLast].iFirst > c ) return aQrfUWidth[iFirst].w; + return aQrfUWidth[iLast].w; } /* -** Attempt to determine if identifier zName needs to be quoted, either -** because it contains non-alphanumeric characters, or because it is an -** SQLite keyword. Be conservative in this estimate: When in doubt assume -** that quoting is required. +** Compute the value and length of a multi-byte UTF-8 character that +** begins at z[0]. Return the length. Write the Unicode value into *pU. ** -** Return '"' if quoting is required. Return 0 if no quoting is required. -*/ -static char quoteChar(const char *zName){ - int i; - if( zName==0 ) return '"'; - if( !isalpha((unsigned char)zName[0]) && zName[0]!='_' ) return '"'; - for(i=0; zName[i]; i++){ - if( !isalnum((unsigned char)zName[i]) && zName[i]!='_' ) return '"'; - } - return sqlite3_keyword_check(zName, i) ? '"' : 0; -} - -/* -** Construct a fake object name and column list to describe the structure -** of the view, virtual table, or table valued function zSchema.zName. +** This routine only works for *multi-byte* UTF-8 characters. It does +** not attempt to detect illegal characters. */ -static char *shellFakeSchema( - sqlite3 *db, /* The database connection containing the vtab */ - const char *zSchema, /* Schema of the database holding the vtab */ - const char *zName /* The name of the virtual table */ -){ - sqlite3_stmt *pStmt = 0; - char *zSql; - ShellText s; - char cQuote; - char *zDiv = "("; - int nRow = 0; - - zSql = sqlite3_mprintf("PRAGMA \"%w\".table_info=%Q;", - zSchema ? zSchema : "main", zName); - shell_check_oom(zSql); - sqlite3_prepare_v2(db, zSql, -1, &pStmt, 0); - sqlite3_free(zSql); - initText(&s); - if( zSchema ){ - cQuote = quoteChar(zSchema); - if( cQuote && sqlite3_stricmp(zSchema,"temp")==0 ) cQuote = 0; - appendText(&s, zSchema, cQuote); - appendText(&s, ".", 0); +int sqlite3_qrf_decode_utf8(const unsigned char *z, int *pU){ + if( (z[0] & 0xe0)==0xc0 && (z[1] & 0xc0)==0x80 ){ + *pU = ((z[0] & 0x1f)<<6) | (z[1] & 0x3f); + return 2; } - cQuote = quoteChar(zName); - appendText(&s, zName, cQuote); - while( sqlite3_step(pStmt)==SQLITE_ROW ){ - const char *zCol = (const char*)sqlite3_column_text(pStmt, 1); - nRow++; - appendText(&s, zDiv, 0); - zDiv = ","; - if( zCol==0 ) zCol = ""; - cQuote = quoteChar(zCol); - appendText(&s, zCol, cQuote); + if( (z[0] & 0xf0)==0xe0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 ){ + *pU = ((z[0] & 0x0f)<<12) | ((z[1] & 0x3f)<<6) | (z[2] & 0x3f); + return 3; } - appendText(&s, ")", 0); - sqlite3_finalize(pStmt); - if( nRow==0 ){ - freeText(&s); - s.z = 0; + if( (z[0] & 0xf8)==0xf0 && (z[1] & 0xc0)==0x80 && (z[2] & 0xc0)==0x80 + && (z[3] & 0xc0)==0x80 + ){ + *pU = ((z[0] & 0x0f)<<18) | ((z[1] & 0x3f)<<12) | ((z[2] & 0x3f))<<6 + | (z[3] & 0x3f); + return 4; } - return s.z; + *pU = 0; + return 1; } /* -** SQL function: strtod(X) +** Check to see if z[] is a valid VT100 escape. If it is, then +** return the number of bytes in the escape sequence. Return 0 if +** z[] is not a VT100 escape. ** -** Use the C-library strtod() function to convert string X into a double. -** Used for comparing the accuracy of SQLite's internal text-to-float conversion -** routines against the C-library. +** This routine assumes that z[0] is \033 (ESC). */ -static void shellStrtod( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - char *z = (char*)sqlite3_value_text(apVal[0]); - UNUSED_PARAMETER(nVal); - if( z==0 ) return; - sqlite3_result_double(pCtx, strtod(z,0)); +static int qrfIsVt100(const unsigned char *z){ + int i; + if( z[1]!='[' ) return 0; + i = 2; + while( z[i]>=0x30 && z[i]<=0x3f ){ i++; } + while( z[i]>=0x20 && z[i]<=0x2f ){ i++; } + if( z[i]<0x40 || z[i]>0x7e ) return 0; + return i+1; } /* -** SQL function: dtostr(X) +** Return the length of a string in display characters. ** -** Use the C-library printf() function to convert real value X into a string. -** Used for comparing the accuracy of SQLite's internal float-to-text conversion -** routines against the C-library. +** Most characters of the input string count as 1, including +** multi-byte UTF8 characters. However, zero-width unicode +** characters and VT100 escape sequences count as zero, and +** double-width characters count as two. +** +** The definition of "zero-width" and "double-width" characters +** is not precise. It depends on the output device, to some extent, +** and it varies according to the Unicode version. This routine +** makes the best guess that it can. */ -static void shellDtostr( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - double r = sqlite3_value_double(apVal[0]); - int n = nVal>=2 ? sqlite3_value_int(apVal[1]) : 26; - char z[400]; - if( n<1 ) n = 1; - if( n>350 ) n = 350; - sprintf(z, "%#+.*e", n, r); - sqlite3_result_text(pCtx, z, -1, SQLITE_TRANSIENT); +size_t sqlite3_qrf_wcswidth(const char *zIn){ + const unsigned char *z = (const unsigned char*)zIn; + size_t n = 0; + while( *z ){ + if( z[0]<' ' ){ + int k; + if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){ + z += k; + }else{ + z++; + } + }else if( (0x80&z[0])==0 ){ + n++; + z++; + }else{ + int u = 0; + int len = sqlite3_qrf_decode_utf8(z, &u); + z += len; + n += sqlite3_qrf_wcwidth(u); + } + } + return n; } - /* -** SQL function: shell_module_schema(X) +** Return the display width of the longest line of text +** in the (possibly) multi-line input string zIn[0..nByte]. +** zIn[] is not necessarily zero-terminated. Take +** into account tab characters, zero- and double-width +** characters, CR and NL, and VT100 escape codes. ** -** Return a fake schema for the table-valued function or eponymous virtual -** table X. +** Write the number of newlines into *pnNL. So, *pnNL will +** return 0 if everything fits on one line, or positive it +** it will need to be split. */ -static void shellModuleSchema( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal -){ - const char *zName; - char *zFake; - UNUSED_PARAMETER(nVal); - zName = (const char*)sqlite3_value_text(apVal[0]); - zFake = zName? shellFakeSchema(sqlite3_context_db_handle(pCtx), 0, zName) : 0; - if( zFake ){ - sqlite3_result_text(pCtx, sqlite3_mprintf("/* %s */", zFake), - -1, sqlite3_free); - free(zFake); +static int qrfDisplayWidth(const char *zIn, sqlite3_int64 nByte, int *pnNL){ + const unsigned char *z; + const unsigned char *zEnd; + int mx = 0; + int n = 0; + int nNL = 0; + if( zIn==0 ) zIn = ""; + z = (const unsigned char*)zIn; + zEnd = &z[nByte]; + while( z0 ){ + z += k; + }else{ + if( z[0]=='\t' ){ + n = (n+8)&~7; + }else if( z[0]=='\n' || z[0]=='\r' ){ + nNL++; + if( n>mx ) mx = n; + n = 0; + } + z++; + } + }else if( (0x80&z[0])==0 ){ + n++; + z++; + }else{ + int u = 0; + int len = sqlite3_qrf_decode_utf8(z, &u); + z += len; + n += sqlite3_qrf_wcwidth(u); + } } + if( mx>n ) n = mx; + if( pnNL ) *pnNL = nNL; + return n; } /* -** SQL function: shell_add_schema(S,X) +** Escape the input string if it is needed and in accordance with +** eEsc, which is either QRF_ESC_Ascii or QRF_ESC_Symbol. ** -** Add the schema name X to the CREATE statement in S and return the result. -** Examples: -** -** CREATE TABLE t1(x) -> CREATE TABLE xyz.t1(x); +** Escaping is needed if the string contains any control characters +** other than \t, \n, and \r\n ** -** Also works on -** -** CREATE INDEX -** CREATE UNIQUE INDEX -** CREATE VIEW -** CREATE TRIGGER -** CREATE VIRTUAL TABLE +** If no escaping is needed (the common case) then set *ppOut to NULL +** and return 0. If escaping is needed, write the escaped string into +** memory obtained from sqlite3_malloc64() and make *ppOut point to that +** memory and return 0. If an error occurs, return non-zero. ** -** This UDF is used by the .schema command to insert the schema name of -** attached databases into the middle of the sqlite_schema.sql field. +** The caller is responsible for freeing *ppFree if it is non-NULL in order +** to reclaim memory. */ -static void shellAddSchemaName( - sqlite3_context *pCtx, - int nVal, - sqlite3_value **apVal +static void qrfEscape( + int eEsc, /* QRF_ESC_Ascii or QRF_ESC_Symbol */ + sqlite3_str *pStr, /* String to be escaped */ + int iStart /* Begin escapding on this byte of pStr */ ){ - static const char *aPrefix[] = { - "TABLE", - "INDEX", - "UNIQUE INDEX", - "VIEW", - "TRIGGER", - "VIRTUAL TABLE" - }; - int i = 0; - const char *zIn = (const char*)sqlite3_value_text(apVal[0]); - const char *zSchema = (const char*)sqlite3_value_text(apVal[1]); - const char *zName = (const char*)sqlite3_value_text(apVal[2]); - sqlite3 *db = sqlite3_context_db_handle(pCtx); - UNUSED_PARAMETER(nVal); - if( zIn!=0 && cli_strncmp(zIn, "CREATE ", 7)==0 ){ - for(i=0; i0x1f + || c=='\t' + || c=='\n' + || (c=='\r' && zIn[i+1]=='\n') + ){ + continue; + } + if( i>0 ){ + memmove(&zOut[j], zIn, i); + j += i; + } + zIn += i+1; + i = -1; + if( eEsc==QRF_ESC_Symbol ){ + zOut[j++] = 0xe2; + zOut[j++] = 0x90; + zOut[j++] = 0x80+c; + }else{ + zOut[j++] = '^'; + zOut[j++] = 0x40+c; } } - sqlite3_result_value(pCtx, apVal[0]); } /* -** The source code for several run-time loadable extensions is inserted -** below by the ../tool/mkshellc.tcl script. Before processing that included -** code, we need to override some macros to make the included program code -** work here in the middle of this regular program. -*/ -#define SQLITE_EXTENSION_INIT1 -#define SQLITE_EXTENSION_INIT2(X) (void)(X) - -#if defined(_WIN32) && defined(_MSC_VER) -/************************* Begin test_windirent.h ******************/ -/* -** 2015 November 30 +** Determine if the string z[] can be shown as plain text. Return true +** if z[] is unambiguously text. Return false if z[] needs to be +** quoted. ** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. +** All of the following must be true in order for z[] to be relaxable: ** -************************************************************************* -** This file contains declarations for most of the opendir() family of -** POSIX functions on Win32 using the MSVCRT. +** (1) z[] does not begin or end with ' or whitespace +** (2) z[] is not the same as the NULL rendering +** (3) z[] does not looks like a numeric literal */ - -#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H) -#define SQLITE_WINDIRENT_H +static int qrfRelaxable(Qrf *p, const char *z){ + size_t i, n; + if( z[0]=='\'' || qrfSpace(z[0]) ) return 0; + if( z[0]==0 ){ + return (p->spec.zNull!=0 && p->spec.zNull[0]!=0); + } + n = strlen(z); + if( n==0 || z[n-1]=='\'' || qrfSpace(z[n-1]) ) return 0; + if( p->spec.zNull && strcmp(p->spec.zNull,z)==0 ) return 0; + i = (z[0]=='-' || z[0]=='+'); + if( strcmp(z+i,"Inf")==0 ) return 0; + if( !qrfDigit(z[i]) ) return 1; + i++; + while( qrfDigit(z[i]) ){ i++; } + if( z[i]==0 ) return 0; + if( z[i]=='.' ){ + i++; + while( qrfDigit(z[i]) ){ i++; } + if( z[i]==0 ) return 0; + } + if( z[i]=='e' || z[i]=='E' ){ + i++; + if( z[i]=='+' || z[i]=='-' ){ i++; } + if( !qrfDigit(z[i]) ) return 1; + i++; + while( qrfDigit(z[i]) ){ i++; } + } + return z[i]!=0; +} /* -** We need several data types from the Windows SDK header. +** If a field contains any character identified by a 1 in the following +** array, then the string must be quoted for CSV. */ - -#ifndef WIN32_LEAN_AND_MEAN -#define WIN32_LEAN_AND_MEAN -#endif - -#include "windows.h" - -/* -** We need several support functions from the SQLite core. -*/ - -/* #include "sqlite3.h" */ - -/* -** We need several things from the ANSI and MSVCRT headers. -*/ - -#include -#include -#include -#include -#include -#include -#include - -/* -** We may need several defines that should have been in "sys/stat.h". -*/ - -#ifndef S_ISREG -#define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) -#endif - -#ifndef S_ISDIR -#define S_ISDIR(mode) (((mode) & S_IFMT) == S_IFDIR) -#endif - -#ifndef S_ISLNK -#define S_ISLNK(mode) (0) -#endif +static const char qrfCsvQuote[] = { + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, +}; /* -** We may need to provide the "mode_t" type. +** Encode text appropriately and append it to pOut. */ - -#ifndef MODE_T_DEFINED - #define MODE_T_DEFINED - typedef unsigned short mode_t; -#endif +static void qrfEncodeText(Qrf *p, sqlite3_str *pOut, const char *zTxt){ + int iStart = sqlite3_str_length(pOut); + switch( p->spec.eText ){ + case QRF_TEXT_Relaxed: + if( qrfRelaxable(p, zTxt) ){ + sqlite3_str_appendall(pOut, zTxt); + break; + } + deliberate_fall_through; /* FALLTHRU */ + case QRF_TEXT_Sql: { + if( p->spec.eEsc==QRF_ESC_Off ){ + sqlite3_str_appendf(pOut, "%Q", zTxt); + }else{ + sqlite3_str_appendf(pOut, "%#Q", zTxt); + } + break; + } + case QRF_TEXT_Csv: { + unsigned int i; + for(i=0; zTxt[i]; i++){ + if( qrfCsvQuote[((const unsigned char*)zTxt)[i]] ){ + i = 0; + break; + } + } + if( i==0 || strstr(zTxt, p->spec.zColumnSep)!=0 ){ + sqlite3_str_appendf(pOut, "\"%w\"", zTxt); + }else{ + sqlite3_str_appendall(pOut, zTxt); + } + break; + } + case QRF_TEXT_Html: { + const unsigned char *z = (const unsigned char*)zTxt; + while( *z ){ + unsigned int i = 0; + unsigned char c; + while( (c=z[i])>'>' + || (c && c!='<' && c!='>' && c!='&' && c!='\"' && c!='\'') + ){ + i++; + } + if( i>0 ){ + sqlite3_str_append(pOut, (const char*)z, i); + } + switch( z[i] ){ + case '>': sqlite3_str_append(pOut, "<", 4); break; + case '&': sqlite3_str_append(pOut, "&", 5); break; + case '<': sqlite3_str_append(pOut, "<", 4); break; + case '"': sqlite3_str_append(pOut, """, 6); break; + case '\'': sqlite3_str_append(pOut, "'", 5); break; + default: i--; + } + z += i + 1; + } + break; + } + case QRF_TEXT_Tcl: + case QRF_TEXT_Json: { + const unsigned char *z = (const unsigned char*)zTxt; + sqlite3_str_append(pOut, "\"", 1); + while( *z ){ + unsigned int i; + for(i=0; z[i]>=0x20 && z[i]!='\\' && z[i]!='"'; i++){} + if( i>0 ){ + sqlite3_str_append(pOut, (const char*)z, i); + } + if( z[i]==0 ) break; + switch( z[i] ){ + case '"': sqlite3_str_append(pOut, "\\\"", 2); break; + case '\\': sqlite3_str_append(pOut, "\\\\", 2); break; + case '\b': sqlite3_str_append(pOut, "\\b", 2); break; + case '\f': sqlite3_str_append(pOut, "\\f", 2); break; + case '\n': sqlite3_str_append(pOut, "\\n", 2); break; + case '\r': sqlite3_str_append(pOut, "\\r", 2); break; + case '\t': sqlite3_str_append(pOut, "\\t", 2); break; + default: { + if( p->spec.eText==QRF_TEXT_Json ){ + sqlite3_str_appendf(pOut, "\\u%04x", z[i]); + }else{ + sqlite3_str_appendf(pOut, "\\%03o", z[i]); + } + break; + } + } + z += i + 1; + } + sqlite3_str_append(pOut, "\"", 1); + break; + } + default: { + sqlite3_str_appendall(pOut, zTxt); + break; + } + } + if( p->spec.eEsc!=QRF_ESC_Off ){ + qrfEscape(p->spec.eEsc, pOut, iStart); + } +} /* -** We may need to provide the "ino_t" type. +** Do a quick sanity check to see aBlob[0..nBlob-1] is valid JSONB +** return true if it is and false if it is not. +** +** False positives are possible, but not false negatives. */ +static int qrfJsonbQuickCheck(unsigned char *aBlob, int nBlob){ + unsigned char x; /* Payload size half-byte */ + int i; /* Loop counter */ + int n; /* Bytes in the payload size integer */ + sqlite3_uint64 sz; /* value of the payload size integer */ -#ifndef INO_T_DEFINED - #define INO_T_DEFINED - typedef unsigned short ino_t; -#endif + if( nBlob==0 ) return 0; + x = aBlob[0]>>4; + if( x<=11 ) return nBlob==(1+x); + n = x<14 ? x-11 : 4*(x-13); + if( nBlob<1+n ) return 0; + sz = aBlob[1]; + for(i=1; ipStmt is known to be a BLOB. Check +** to see if that BLOB is really a JSONB blob. If it is, then translate +** it into a text JSON representation and return a pointer to that text JSON. +** If the BLOB is not JSONB, then return a NULL pointer. +** +** The memory used to hold the JSON text is managed internally by the +** "p" object and is overwritten and/or deallocated upon the next call +** to this routine (with the same p argument) or when the p object is +** finailized. */ - -#ifndef NAME_MAX -# ifdef FILENAME_MAX -# define NAME_MAX (FILENAME_MAX) -# else -# define NAME_MAX (260) -# endif -#endif +static const char *qrfJsonbToJson(Qrf *p, int iCol){ + int nByte; + const void *pBlob; + int rc; + nByte = sqlite3_column_bytes(p->pStmt, iCol); + pBlob = sqlite3_column_blob(p->pStmt, iCol); + if( qrfJsonbQuickCheck((unsigned char*)pBlob, nByte)==0 ){ + return 0; + } + if( p->pJTrans==0 ){ + sqlite3 *db; + rc = sqlite3_open(":memory:",&db); + if( rc ){ + sqlite3_close(db); + return 0; + } + rc = sqlite3_prepare_v2(db, "SELECT json(?1)", -1, &p->pJTrans, 0); + if( rc ){ + sqlite3_finalize(p->pJTrans); + p->pJTrans = 0; + sqlite3_close(db); + return 0; + } + }else{ + sqlite3_reset(p->pJTrans); + } + sqlite3_bind_blob(p->pJTrans, 1, (void*)pBlob, nByte, SQLITE_STATIC); + rc = sqlite3_step(p->pJTrans); + if( rc==SQLITE_ROW ){ + return (const char*)sqlite3_column_text(p->pJTrans, 0); + }else{ + return 0; + } +} /* -** We need to define "NULL_INTPTR_T" and "BAD_INTPTR_T". +** Adjust the input string zIn[] such that it is no more than N display +** characters wide. If it is wider than that, then truncate and add +** ellipsis. Or if zIn[] contains a \r or \n, truncate at that point, +** adding ellipsis. Embedded tabs in zIn[] are converted into ordinary +** spaces. +** +** Return this display width of the modified title string. */ +static int qrfTitleLimit(char *zIn, int N){ + unsigned char *z = (unsigned char*)zIn; + int n = 0; + unsigned char *zEllipsis = 0; + while( 1 /*exit-by-break*/ ){ + if( z[0]<' ' ){ + int k; + if( z[0]==0 ){ + zEllipsis = 0; + break; + }else if( z[0]=='\033' && (k = qrfIsVt100(z))>0 ){ + z += k; + }else if( z[0]=='\t' ){ + z[0] = ' '; + }else if( z[0]=='\n' || z[0]=='\r' ){ + z[0] = ' '; + }else{ + z++; + } + }else if( (0x80&z[0])==0 ){ + if( n>=(N-3) && zEllipsis==0 ) zEllipsis = z; + if( n==N ){ z[0] = 0; break; } + n++; + z++; + }else{ + int u = 0; + int len = sqlite3_qrf_decode_utf8(z, &u); + if( n+len>(N-3) && zEllipsis==0 ) zEllipsis = z; + if( n+len>N ){ z[0] = 0; break; } + z += len; + n += sqlite3_qrf_wcwidth(u); + } + } + if( zEllipsis && N>=3 ) memcpy(zEllipsis,"...",4); + return n; +} -#ifndef NULL_INTPTR_T -# define NULL_INTPTR_T ((intptr_t)(0)) -#endif - -#ifndef BAD_INTPTR_T -# define BAD_INTPTR_T ((intptr_t)(-1)) -#endif /* -** We need to provide the necessary structures and related types. +** Render value pVal into pOut */ - -#ifndef DIRENT_DEFINED -#define DIRENT_DEFINED -typedef struct DIRENT DIRENT; -typedef DIRENT *LPDIRENT; -struct DIRENT { - ino_t d_ino; /* Sequence number, do not use. */ - unsigned d_attributes; /* Win32 file attributes. */ - char d_name[NAME_MAX + 1]; /* Name within the directory. */ -}; +static void qrfRenderValue(Qrf *p, sqlite3_str *pOut, int iCol){ +#if SQLITE_VERSION_NUMBER>=3052000 + int iStartLen = sqlite3_str_length(pOut); #endif - -#ifndef DIR_DEFINED -#define DIR_DEFINED -typedef struct DIR DIR; -typedef DIR *LPDIR; -struct DIR { - intptr_t d_handle; /* Value returned by "_findfirst". */ - DIRENT d_first; /* DIRENT constructed based on "_findfirst". */ - DIRENT d_next; /* DIRENT constructed based on "_findnext". */ -}; + if( p->spec.xRender ){ + sqlite3_value *pVal; + char *z; + pVal = sqlite3_value_dup(sqlite3_column_value(p->pStmt,iCol)); + z = p->spec.xRender(p->spec.pRenderArg, pVal); + sqlite3_value_free(pVal); + if( z ){ + sqlite3_str_appendall(pOut, z); + sqlite3_free(z); + return; + } + } + switch( sqlite3_column_type(p->pStmt,iCol) ){ + case SQLITE_INTEGER: { + sqlite3_str_appendf(pOut, "%lld", sqlite3_column_int64(p->pStmt,iCol)); + break; + } + case SQLITE_FLOAT: { + const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol); + sqlite3_str_appendall(pOut, zTxt); + break; + } + case SQLITE_BLOB: { + if( p->spec.bTextJsonb==QRF_Yes ){ + const char *zJson = qrfJsonbToJson(p, iCol); + if( zJson ){ + if( p->spec.eText==QRF_TEXT_Sql ){ + sqlite3_str_append(pOut,"jsonb(",6); + qrfEncodeText(p, pOut, zJson); + sqlite3_str_append(pOut,")",1); + }else{ + qrfEncodeText(p, pOut, zJson); + } + break; + } + } + switch( p->spec.eBlob ){ + case QRF_BLOB_Hex: + case QRF_BLOB_Sql: { + int iStart; + int nBlob = sqlite3_column_bytes(p->pStmt,iCol); + int i, j; + char *zVal; + const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol); + if( p->spec.eBlob==QRF_BLOB_Sql ){ + sqlite3_str_append(pOut, "x'", 2); + } + iStart = sqlite3_str_length(pOut); + sqlite3_str_appendchar(pOut, nBlob, ' '); + sqlite3_str_appendchar(pOut, nBlob, ' '); + if( p->spec.eBlob==QRF_BLOB_Sql ){ + sqlite3_str_appendchar(pOut, 1, '\''); + } + if( sqlite3_str_errcode(pOut) ) return; + zVal = sqlite3_str_value(pOut); + for(i=0, j=iStart; i>4)&0xf]; + zVal[j+1] = "0123456789abcdef"[(c)&0xf]; + } + break; + } + case QRF_BLOB_Tcl: + case QRF_BLOB_Json: { + int iStart; + int nBlob = sqlite3_column_bytes(p->pStmt,iCol); + int i, j; + char *zVal; + const unsigned char *a = sqlite3_column_blob(p->pStmt,iCol); + int szC = p->spec.eBlob==QRF_BLOB_Json ? 6 : 4; + sqlite3_str_append(pOut, "\"", 1); + iStart = sqlite3_str_length(pOut); + for(i=szC; i>0; i--){ + sqlite3_str_appendchar(pOut, nBlob, ' '); + } + sqlite3_str_appendchar(pOut, 1, '"'); + if( sqlite3_str_errcode(pOut) ) return; + zVal = sqlite3_str_value(pOut); + for(i=0, j=iStart; i>6)&3); + zVal[j+2] = '0' + ((c>>3)&7); + zVal[j+3] = '0' + (c&7); + }else{ + zVal[j+1] = 'u'; + zVal[j+2] = '0'; + zVal[j+3] = '0'; + zVal[j+4] = "0123456789abcdef"[(c>>4)&0xf]; + zVal[j+5] = "0123456789abcdef"[(c)&0xf]; + } + } + break; + } + case QRF_BLOB_Size: { + int nBlob = sqlite3_column_bytes(p->pStmt,iCol); + sqlite3_str_appendf(pOut, "(%d-byte blob)", nBlob); + break; + } + default: { + const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol); + qrfEncodeText(p, pOut, zTxt); + } + } + break; + } + case SQLITE_NULL: { + sqlite3_str_appendall(pOut, p->spec.zNull); + break; + } + case SQLITE_TEXT: { + const char *zTxt = (const char*)sqlite3_column_text(p->pStmt,iCol); + qrfEncodeText(p, pOut, zTxt); + break; + } + } +#if SQLITE_VERSION_NUMBER>=3052000 + if( p->spec.nCharLimit>0 + && (sqlite3_str_length(pOut) - iStartLen) > p->spec.nCharLimit + ){ + const unsigned char *z; + int ii = 0, w = 0, limit = p->spec.nCharLimit; + z = (const unsigned char*)sqlite3_str_value(pOut) + iStartLen; + if( limit<4 ) limit = 4; + while( 1 ){ + if( z[ii]<' ' ){ + int k; + if( z[ii]=='\033' && (k = qrfIsVt100(z+ii))>0 ){ + ii += k; + }else if( z[ii]==0 ){ + break; + }else{ + ii++; + } + }else if( (0x80&z[ii])==0 ){ + w++; + if( w>limit ) break; + ii++; + }else{ + int u = 0; + int len = sqlite3_qrf_decode_utf8(&z[ii], &u); + w += sqlite3_qrf_wcwidth(u); + if( w>limit ) break; + ii += len; + } + } + if( w>limit ){ + sqlite3_str_truncate(pOut, iStartLen+ii); + sqlite3_str_append(pOut, "...", 3); + } + } #endif +} -/* -** Provide a macro, for use by the implementation, to determine if a -** particular directory entry should be skipped over when searching for -** the next directory entry that should be returned by the readdir() or -** readdir_r() functions. +/* Trim spaces of the end if pOut */ - -#ifndef is_filtered -# define is_filtered(a) ((((a).attrib)&_A_HIDDEN) || (((a).attrib)&_A_SYSTEM)) +static void qrfRTrim(sqlite3_str *pOut){ +#if SQLITE_VERSION_NUMBER>=3052000 + int nByte = sqlite3_str_length(pOut); + const char *zOut = sqlite3_str_value(pOut); + while( nByte>0 && zOut[nByte-1]==' ' ){ nByte--; } + sqlite3_str_truncate(pOut, nByte); #endif +} /* -** Provide the function prototype for the POSIX compatible getenv() -** function. This function is not thread-safe. +** Store string zUtf to pOut as w characters. If w is negative, +** then right-justify the text. W is the width in display characters, not +** in bytes. Double-width unicode characters count as two characters. +** VT100 escape sequences count as zero. And so forth. */ - -extern const char *windirent_getenv(const char *name); +static void qrfWidthPrint(Qrf *p, sqlite3_str *pOut, int w, const char *zUtf){ + const unsigned char *a = (const unsigned char*)zUtf; + static const int mxW = 10000000; + unsigned char c; + int i = 0; + int n = 0; + int k; + int aw; + (void)p; + if( w<-mxW ){ + w = -mxW; + }else if( w>mxW ){ + w= mxW; + } + aw = w<0 ? -w : w; + if( a==0 ) a = (const unsigned char*)""; + while( (c = a[i])!=0 ){ + if( (c&0xc0)==0xc0 ){ + int u; + int len = sqlite3_qrf_decode_utf8(a+i, &u); + int x = sqlite3_qrf_wcwidth(u); + if( x+n>aw ){ + break; + } + i += len; + n += x; + }else if( c==0x1b && (k = qrfIsVt100(&a[i]))>0 ){ + i += k; + }else if( n>=aw ){ + break; + }else{ + n++; + i++; + } + } + if( n>=aw ){ + sqlite3_str_append(pOut, zUtf, i); + }else if( w<0 ){ + if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' '); + sqlite3_str_append(pOut, zUtf, i); + }else{ + sqlite3_str_append(pOut, zUtf, i); + if( aw>n ) sqlite3_str_appendchar(pOut, aw-n, ' '); + } +} /* -** Finally, we can provide the function prototypes for the opendir(), -** readdir(), readdir_r(), and closedir() POSIX functions. +** (*pz)[] is a line of text that is to be displayed the box or table or +** similar tabular formats. z[] contain newlines or might be too wide +** to fit in the columns so will need to be split into multiple line. +** +** This routine determines: +** +** * How many bytes of z[] should be shown on the current line. +** * How many character positions those bytes will cover. +** * The byte offset to the start of the next line. */ +static void qrfWrapLine( + const char *zIn, /* Input text to be displayed */ + int w, /* Column width in characters (not bytes) */ + int bWrap, /* True if we should do word-wrapping */ + int *pnThis, /* OUT: How many bytes of z[] for the current line */ + int *pnWide, /* OUT: How wide is the text of this line */ + int *piNext /* OUT: Offset into z[] to start of the next line */ +){ + int i; /* Input bytes consumed */ + int k; /* Bytes in a VT100 code */ + int n; /* Output column number */ + const unsigned char *z = (const unsigned char*)zIn; + unsigned char c = 0; -extern LPDIR opendir(const char *dirname); -extern LPDIRENT readdir(LPDIR dirp); -extern INT readdir_r(LPDIR dirp, LPDIRENT entry, LPDIRENT *result); -extern INT closedir(LPDIR dirp); + if( z[0]==0 ){ + *pnThis = 0; + *pnWide = 0; + *piNext = 0; + return; + } + n = 0; + for(i=0; n<=w; i++){ + c = z[i]; + if( c>=0xc0 ){ + int u; + int len = sqlite3_qrf_decode_utf8(&z[i], &u); + int wcw = sqlite3_qrf_wcwidth(u); + if( wcw+n>w ) break; + i += len-1; + n += wcw; + continue; + } + if( c>=' ' ){ + if( n==w ) break; + n++; + continue; + } + if( c==0 || c=='\n' ) break; + if( c=='\r' && z[i+1]=='\n' ){ c = z[++i]; break; } + if( c=='\t' ){ + int wcw = 8 - (n&7); + if( n+wcw>w ) break; + n += wcw; + continue; + } + if( c==0x1b && (k = qrfIsVt100(&z[i]))>0 ){ + i += k-1; + }else if( n==w ){ + break; + }else{ + n++; + } + } + if( c==0 ){ + *pnThis = i; + *pnWide = n; + *piNext = i; + return; + } + if( c=='\n' ){ + *pnThis = i; + *pnWide = n; + *piNext = i+1; + return; + } -#endif /* defined(WIN32) && defined(_MSC_VER) */ + /* If we get this far, that means the current line will end at some + ** point that is neither a "\n" or a 0x00. Figure out where that + ** split should occur + */ + if( bWrap && z[i]!=0 && !qrfSpace(z[i]) && qrfAlnum(c)==qrfAlnum(z[i]) ){ + /* Perhaps try to back up to a better place to break the line */ + for(k=i-1; k>=i/2; k--){ + if( qrfSpace(z[k]) ) break; + } + if( k=i/2; k--){ + if( qrfAlnum(z[k-1])!=qrfAlnum(z[k]) && (z[k]&0xc0)!=0x80 ) break; + } + } + if( k>=i/2 ){ + i = k; + n = qrfDisplayWidth((const char*)z, k, 0); + } + } + *pnThis = i; + *pnWide = n; + while( zIn[i]==' ' || zIn[i]=='\t' || zIn[i]=='\r' ){ i++; } + *piNext = i; +} -/************************* End test_windirent.h ********************/ -/************************* Begin test_windirent.c ******************/ /* -** 2015 November 30 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** This file contains code to implement most of the opendir() family of -** POSIX functions on Win32 using the MSVCRT. +** Append nVal bytes of text from zVal onto the end of pOut. +** Convert tab characters in zVal to the appropriate number of +** spaces. */ - -#if defined(_WIN32) && defined(_MSC_VER) -/* #include "test_windirent.h" */ +static void qrfAppendWithTabs( + sqlite3_str *pOut, /* Append text here */ + const char *zVal, /* Text to append */ + int nVal /* Use only the first nVal bytes of zVal[] */ +){ + int i = 0; + unsigned int col = 0; + unsigned char *z = (unsigned char *)zVal; + while( i0 ){ + sqlite3_str_append(pOut, (const char*)z, k); + z += k; + nVal -= k; + }else if( c=='\t' ){ + k = 8 - (col&7); + sqlite3_str_appendchar(pOut, k, ' '); + col += k; + z++; + nVal--; + }else if( c=='\r' && nVal==1 ){ + z++; + nVal--; + }else{ + char zCtrlPik[4]; + col++; + zCtrlPik[0] = 0xe2; + zCtrlPik[1] = 0x90; + zCtrlPik[2] = 0x80+c; + sqlite3_str_append(pOut, zCtrlPik, 3); + z++; + nVal--; + } + }else if( (0x80&c)==0 ){ + i++; + col++; + }else{ + int u = 0; + int len = sqlite3_qrf_decode_utf8(&z[i], &u); + i += len; + col += sqlite3_qrf_wcwidth(u); + } + } + sqlite3_str_append(pOut, (const char*)z, i); +} + +/* +** GCC does not define the offsetof() macro so we'll have to do it +** ourselves. +*/ +#ifndef offsetof +# define offsetof(ST,M) ((size_t)((char*)&((ST*)0)->M - (char*)0)) +#endif + +/* +** Data for columnar layout, collected into a single object so +** that it can be more easily passed into subroutines. +*/ +typedef struct qrfColData qrfColData; +struct qrfColData { + Qrf *p; /* The QRF instance */ + int nCol; /* Number of columns in the table */ + unsigned char bMultiRow; /* One or more cells will span multiple lines */ + unsigned char nMargin; /* Width of column margins */ + sqlite3_int64 nRow; /* Number of rows */ + sqlite3_int64 nAlloc; /* Number of cells allocated */ + sqlite3_int64 n; /* Number of cells. nCol*nRow */ + char **az; /* Content of all cells */ + int *aiWth; /* Width of each cell */ + unsigned char *abNum; /* True for each numeric cell */ + struct qrfPerCol { /* Per-column data */ + char *z; /* Cache of text for current row */ + int w; /* Computed width of this column */ + int mxW; /* Maximum natural (unwrapped) width */ + unsigned char e; /* Alignment */ + unsigned char fx; /* Width is fixed */ + unsigned char bNum; /* True if is numeric */ + } *a; /* One per column */ +}; /* -** Implementation of the POSIX getenv() function using the Win32 API. -** This function is not thread-safe. +** Output horizontally justified text into pOut. The text is the +** first nVal bytes of zVal. Include nWS bytes of whitespace, either +** split between both sides, or on the left, or on the right, depending +** on eAlign. */ -const char *windirent_getenv( - const char *name +static void qrfPrintAligned( + sqlite3_str *pOut, /* Append text here */ + struct qrfPerCol *pCol, /* Information about the text to print */ + int nVal, /* Use only the first nVal bytes of zVal[] */ + int nWS /* Whitespace for horizonal alignment */ ){ - static char value[32768]; /* Maximum length, per MSDN */ - DWORD dwSize = sizeof(value) / sizeof(char); /* Size in chars */ - DWORD dwRet; /* Value returned by GetEnvironmentVariableA() */ - - memset(value, 0, sizeof(value)); - dwRet = GetEnvironmentVariableA(name, value, dwSize); - if( dwRet==0 || dwRet>dwSize ){ - /* - ** The function call to GetEnvironmentVariableA() failed -OR- - ** the buffer is not large enough. Either way, return NULL. - */ - return 0; + unsigned char eAlign = pCol->e & QRF_ALIGN_HMASK; + if( eAlign==QRF_Auto && pCol->bNum ) eAlign = QRF_ALIGN_Right; + if( eAlign==QRF_ALIGN_Center ){ + /* Center the text */ + sqlite3_str_appendchar(pOut, nWS/2, ' '); + qrfAppendWithTabs(pOut, pCol->z, nVal); + sqlite3_str_appendchar(pOut, nWS - nWS/2, ' '); + }else if( eAlign==QRF_ALIGN_Right ){ + /* Right justify the text */ + sqlite3_str_appendchar(pOut, nWS, ' '); + qrfAppendWithTabs(pOut, pCol->z, nVal); }else{ - /* - ** The function call to GetEnvironmentVariableA() succeeded - ** -AND- the buffer contains the entire value. - */ - return value; + /* Left justify the text */ + qrfAppendWithTabs(pOut, pCol->z, nVal); + sqlite3_str_appendchar(pOut, nWS, ' '); } } /* -** Implementation of the POSIX opendir() function using the MSVCRT. +** Free all the memory allocates in the qrfColData object */ -LPDIR opendir( - const char *dirname -){ - struct _finddata_t data; - LPDIR dirp = (LPDIR)sqlite3_malloc(sizeof(DIR)); - SIZE_T namesize = sizeof(data.name) / sizeof(data.name[0]); - - if( dirp==NULL ) return NULL; - memset(dirp, 0, sizeof(DIR)); +static void qrfColDataFree(qrfColData *p){ + sqlite3_int64 i; + for(i=0; in; i++) sqlite3_free(p->az[i]); + sqlite3_free(p->az); + sqlite3_free(p->aiWth); + sqlite3_free(p->abNum); + sqlite3_free(p->a); + memset(p, 0, sizeof(*p)); +} - /* TODO: Remove this if Unix-style root paths are not used. */ - if( sqlite3_stricmp(dirname, "/")==0 ){ - dirname = windirent_getenv("SystemDrive"); - } - - memset(&data, 0, sizeof(struct _finddata_t)); - _snprintf(data.name, namesize, "%s\\*", dirname); - dirp->d_handle = _findfirst(data.name, &data); - - if( dirp->d_handle==BAD_INTPTR_T ){ - closedir(dirp); - return NULL; +/* +** Allocate space for more cells in the qrfColData object. +** Return non-zero if a memory allocation fails. +*/ +static int qrfColDataEnlarge(qrfColData *p){ + char **azData; + int *aiWth; + unsigned char *abNum; + p->nAlloc = 2*p->nAlloc + 10*p->nCol; + azData = sqlite3_realloc64(p->az, p->nAlloc*sizeof(char*)); + if( azData==0 ){ + qrfOom(p->p); + qrfColDataFree(p); + return 1; } + p->az = azData; + aiWth = sqlite3_realloc64(p->aiWth, p->nAlloc*sizeof(int)); + if( aiWth==0 ){ + qrfOom(p->p); + qrfColDataFree(p); + return 1; + } + p->aiWth = aiWth; + abNum = sqlite3_realloc64(p->abNum, p->nAlloc); + if( abNum==0 ){ + qrfOom(p->p); + qrfColDataFree(p); + return 1; + } + p->abNum = abNum; + return 0; +} - /* TODO: Remove this block to allow hidden and/or system files. */ - if( is_filtered(data) ){ -next: - - memset(&data, 0, sizeof(struct _finddata_t)); - if( _findnext(dirp->d_handle, &data)==-1 ){ - closedir(dirp); - return NULL; +/* +** Print a markdown or table-style row separator using ascii-art +*/ +static void qrfRowSeparator(sqlite3_str *pOut, qrfColData *p, char cSep){ + int i; + if( p->nCol>0 ){ + int useBorder = p->p->spec.bBorder!=QRF_No; + if( useBorder ){ + sqlite3_str_append(pOut, &cSep, 1); + } + sqlite3_str_appendchar(pOut, p->a[0].w+p->nMargin, '-'); + for(i=1; inCol; i++){ + sqlite3_str_append(pOut, &cSep, 1); + sqlite3_str_appendchar(pOut, p->a[i].w+p->nMargin, '-'); + } + if( useBorder ){ + sqlite3_str_append(pOut, &cSep, 1); } - - /* TODO: Remove this block to allow hidden and/or system files. */ - if( is_filtered(data) ) goto next; } - - dirp->d_first.d_attributes = data.attrib; - strncpy(dirp->d_first.d_name, data.name, NAME_MAX); - dirp->d_first.d_name[NAME_MAX] = '\0'; - - return dirp; + sqlite3_str_append(pOut, "\n", 1); } /* -** Implementation of the POSIX readdir() function using the MSVCRT. +** UTF8 box-drawing characters. Imagine box lines like this: +** +** 1 +** | +** 4 --+-- 2 +** | +** 3 +** +** Each box characters has between 2 and 4 of the lines leading from +** the center. The characters are here identified by the numbers of +** their corresponding lines. */ -LPDIRENT readdir( - LPDIR dirp -){ - struct _finddata_t data; +#define BOX_24 "\342\224\200" /* U+2500 --- */ +#define BOX_13 "\342\224\202" /* U+2502 | */ +#define BOX_23 "\342\224\214" /* U+250c ,- */ +#define BOX_34 "\342\224\220" /* U+2510 -, */ +#define BOX_12 "\342\224\224" /* U+2514 '- */ +#define BOX_14 "\342\224\230" /* U+2518 -' */ +#define BOX_123 "\342\224\234" /* U+251c |- */ +#define BOX_134 "\342\224\244" /* U+2524 -| */ +#define BOX_234 "\342\224\254" /* U+252c -,- */ +#define BOX_124 "\342\224\264" /* U+2534 -'- */ +#define BOX_1234 "\342\224\274" /* U+253c -|- */ - if( dirp==NULL ) return NULL; +/* Rounded corners: */ +#define BOX_R12 "\342\225\260" /* U+2570 '- */ +#define BOX_R23 "\342\225\255" /* U+256d ,- */ +#define BOX_R34 "\342\225\256" /* U+256e -, */ +#define BOX_R14 "\342\225\257" /* U+256f -' */ - if( dirp->d_first.d_ino==0 ){ - dirp->d_first.d_ino++; - dirp->d_next.d_ino++; +/* Doubled horizontal lines: */ +#define DBL_24 "\342\225\220" /* U+2550 === */ +#define DBL_123 "\342\225\236" /* U+255e |= */ +#define DBL_134 "\342\225\241" /* U+2561 =| */ +#define DBL_1234 "\342\225\252" /* U+256a =|= */ - return &dirp->d_first; +/* Draw horizontal line N characters long using unicode box +** characters +*/ +static void qrfBoxLine(sqlite3_str *pOut, int N, int bDbl){ + const char *azDash[2] = { + BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24 BOX_24, + DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 DBL_24 + };/* 0 1 2 3 4 5 6 7 8 9 */ + const int nDash = 30; + N *= 3; + while( N>nDash ){ + sqlite3_str_append(pOut, azDash[bDbl], nDash); + N -= nDash; } - -next: - - memset(&data, 0, sizeof(struct _finddata_t)); - if( _findnext(dirp->d_handle, &data)==-1 ) return NULL; - - /* TODO: Remove this block to allow hidden and/or system files. */ - if( is_filtered(data) ) goto next; - - dirp->d_next.d_ino++; - dirp->d_next.d_attributes = data.attrib; - strncpy(dirp->d_next.d_name, data.name, NAME_MAX); - dirp->d_next.d_name[NAME_MAX] = '\0'; - - return &dirp->d_next; + sqlite3_str_append(pOut, azDash[bDbl], N); } /* -** Implementation of the POSIX readdir_r() function using the MSVCRT. +** Draw a horizontal separator for a QRF_STYLE_Box table. */ -INT readdir_r( - LPDIR dirp, - LPDIRENT entry, - LPDIRENT *result +static void qrfBoxSeparator( + sqlite3_str *pOut, + qrfColData *p, + const char *zSep1, + const char *zSep2, + const char *zSep3, + int bDbl ){ - struct _finddata_t data; - - if( dirp==NULL ) return EBADF; - - if( dirp->d_first.d_ino==0 ){ - dirp->d_first.d_ino++; - dirp->d_next.d_ino++; - - entry->d_ino = dirp->d_first.d_ino; - entry->d_attributes = dirp->d_first.d_attributes; - strncpy(entry->d_name, dirp->d_first.d_name, NAME_MAX); - entry->d_name[NAME_MAX] = '\0'; - - *result = entry; - return 0; - } - -next: - - memset(&data, 0, sizeof(struct _finddata_t)); - if( _findnext(dirp->d_handle, &data)==-1 ){ - *result = NULL; - return ENOENT; + int i; + if( p->nCol>0 ){ + int useBorder = p->p->spec.bBorder!=QRF_No; + if( useBorder ){ + sqlite3_str_appendall(pOut, zSep1); + } + qrfBoxLine(pOut, p->a[0].w+p->nMargin, bDbl); + for(i=1; inCol; i++){ + sqlite3_str_appendall(pOut, zSep2); + qrfBoxLine(pOut, p->a[i].w+p->nMargin, bDbl); + } + if( useBorder ){ + sqlite3_str_appendall(pOut, zSep3); + } } - - /* TODO: Remove this block to allow hidden and/or system files. */ - if( is_filtered(data) ) goto next; - - entry->d_ino = (ino_t)-1; /* not available */ - entry->d_attributes = data.attrib; - strncpy(entry->d_name, data.name, NAME_MAX); - entry->d_name[NAME_MAX] = '\0'; - - *result = entry; - return 0; + sqlite3_str_append(pOut, "\n", 1); } /* -** Implementation of the POSIX closedir() function using the MSVCRT. +** Load into pData the default alignment for the body of a table. */ -INT closedir( - LPDIR dirp -){ - INT result = 0; - - if( dirp==NULL ) return EINVAL; - - if( dirp->d_handle!=NULL_INTPTR_T && dirp->d_handle!=BAD_INTPTR_T ){ - result = _findclose(dirp->d_handle); +static void qrfLoadAlignment(qrfColData *pData, Qrf *p){ + sqlite3_int64 i; + for(i=0; inCol; i++){ + pData->a[i].e = p->spec.eDfltAlign; + if( ispec.nAlign ){ + unsigned char ax = p->spec.aAlign[i]; + if( (ax & QRF_ALIGN_HMASK)!=0 ){ + pData->a[i].e = (ax & QRF_ALIGN_HMASK) | + (pData->a[i].e & QRF_ALIGN_VMASK); + } + }else if( ispec.nWidth ){ + if( p->spec.aWidth[i]<0 ){ + pData->a[i].e = QRF_ALIGN_Right | + (pData->a[i].e & QRF_ALIGN_VMASK); + } + } } - - sqlite3_free(dirp); - return result; } -#endif /* defined(WIN32) && defined(_MSC_VER) */ - -/************************* End test_windirent.c ********************/ -#define dirent DIRENT -#endif -/************************* Begin ../ext/misc/memtrace.c ******************/ /* -** 2019-01-21 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file implements an extension that uses the SQLITE_CONFIG_MALLOC -** mechanism to add a tracing layer on top of SQLite. If this extension -** is registered prior to sqlite3_initialize(), it will cause all memory -** allocation activities to be logged on standard output, or to some other -** FILE specified by the initializer. +** If the single column in pData->a[] with pData->n entries can be +** laid out as nCol columns with a 2-space gap between each such +** that all columns fit within nSW, then return a pointer to an array +** of integers which is the width of each column from left to right. ** -** This file needs to be compiled into the application that uses it. +** If the layout is not possible, return a NULL pointer. ** -** This extension is used to implement the --memtrace option of the -** command-line shell. +** Space to hold the returned array is from sqlite_malloc64(). */ -#include -#include -#include - -/* The original memory allocation routines */ -static sqlite3_mem_methods memtraceBase; -static FILE *memtraceOut; - -/* Methods that trace memory allocations */ -static void *memtraceMalloc(int n){ - if( memtraceOut ){ - fprintf(memtraceOut, "MEMTRACE: allocate %d bytes\n", - memtraceBase.xRoundup(n)); +static int *qrfValidLayout( + qrfColData *pData, /* Collected query results */ + Qrf *p, /* On which to report an OOM */ + int nCol, /* Attempt this many columns */ + int nSW /* Screen width */ +){ + int i; /* Loop counter */ + int nr; /* Number of rows */ + int w = 0; /* Width of the current column */ + int t; /* Total width of all columns */ + int *aw; /* Array of individual column widths */ + + aw = sqlite3_malloc64( sizeof(int)*nCol ); + if( aw==0 ){ + qrfOom(p); + return 0; } - return memtraceBase.xMalloc(n); -} -static void memtraceFree(void *p){ - if( p==0 ) return; - if( memtraceOut ){ - fprintf(memtraceOut, "MEMTRACE: free %d bytes\n", memtraceBase.xSize(p)); + nr = (pData->n + nCol - 1)/nCol; + for(i=0; in; i++){ + if( (i%nr)==0 ){ + if( i>0 ) aw[i/nr-1] = w; + w = pData->aiWth[i]; + }else if( pData->aiWth[i]>w ){ + w = pData->aiWth[i]; + } } - memtraceBase.xFree(p); -} -static void *memtraceRealloc(void *p, int n){ - if( p==0 ) return memtraceMalloc(n); - if( n==0 ){ - memtraceFree(p); + aw[nCol-1] = w; + for(t=i=0; inSW ){ + sqlite3_free(aw); return 0; } - if( memtraceOut ){ - fprintf(memtraceOut, "MEMTRACE: resize %d -> %d bytes\n", - memtraceBase.xSize(p), memtraceBase.xRoundup(n)); + return aw; +} + +/* +** The output is single-column and the bSplitColumn flag is set. +** Check to see if the single-column output can be split into multiple +** columns that appear side-by-side. Adjust pData appropriately. +*/ +static void qrfSplitColumn(qrfColData *pData, Qrf *p){ + int nCol = 1; + int *aw = 0; + char **az = 0; + int *aiWth = 0; + unsigned char *abNum = 0; + int nColNext = 2; + int w; + struct qrfPerCol *a = 0; + sqlite3_int64 nRow = 1; + sqlite3_int64 i; + while( 1/*exit-by-break*/ ){ + int *awNew = qrfValidLayout(pData, p, nColNext, p->spec.nScreenWidth); + if( awNew==0 ) break; + sqlite3_free(aw); + aw = awNew; + nCol = nColNext; + nRow = (pData->n + nCol - 1)/nCol; + if( nRow==1 ) break; + nColNext++; + while( (pData->n + nColNext - 1)/nColNext == nRow ) nColNext++; + } + if( nCol==1 ){ + sqlite3_free(aw); + return; /* Cannot do better than 1 column */ + } + az = sqlite3_malloc64( nRow*nCol*sizeof(char*) ); + if( az==0 ){ + qrfOom(p); + return; } - return memtraceBase.xRealloc(p, n); -} -static int memtraceSize(void *p){ - return memtraceBase.xSize(p); -} -static int memtraceRoundup(int n){ - return memtraceBase.xRoundup(n); -} -static int memtraceInit(void *p){ - return memtraceBase.xInit(p); -} -static void memtraceShutdown(void *p){ - memtraceBase.xShutdown(p); -} - -/* The substitute memory allocator */ -static sqlite3_mem_methods ersaztMethods = { - memtraceMalloc, - memtraceFree, - memtraceRealloc, - memtraceSize, - memtraceRoundup, - memtraceInit, - memtraceShutdown, - 0 -}; + aiWth = sqlite3_malloc64( nRow*nCol*sizeof(int) ); + if( aiWth==0 ){ + sqlite3_free(az); + qrfOom(p); + return; + } + a = sqlite3_malloc64( nCol*sizeof(struct qrfPerCol) ); + if( a==0 ){ + sqlite3_free(az); + sqlite3_free(aiWth); + qrfOom(p); + return; + } + abNum = sqlite3_malloc64( nRow*nCol ); + if( abNum==0 ){ + sqlite3_free(az); + sqlite3_free(aiWth); + sqlite3_free(a); + qrfOom(p); + return; + } + for(i=0; in; i++){ + sqlite3_int64 j = (i%nRow)*nCol + (i/nRow); + az[j] = pData->az[i]; + abNum[j]= pData->abNum[i]; + pData->az[i] = 0; + aiWth[j] = pData->aiWth[i]; + } + while( ia[0].e; + } + sqlite3_free(pData->az); + sqlite3_free(pData->aiWth); + sqlite3_free(pData->a); + sqlite3_free(pData->abNum); + sqlite3_free(aw); + pData->az = az; + pData->aiWth = aiWth; + pData->a = a; + pData->abNum = abNum; + pData->nCol = nCol; + pData->n = pData->nAlloc = nRow*nCol; + for(i=w=0; inMargin = (p->spec.nScreenWidth - w)/(nCol - 1); + if( pData->nMargin>5 ) pData->nMargin = 5; +} + +/* +** Adjust the layout for the screen width restriction +*/ +static void qrfRestrictScreenWidth(qrfColData *pData, Qrf *p){ + int sepW; /* Width of all box separators and margins */ + int sumW; /* Total width of data area over all columns */ + int targetW; /* Desired total data area */ + int i; /* Loop counters */ + int nCol; /* Number of columns */ + + pData->nMargin = 2; /* Default to normal margins */ + if( p->spec.nScreenWidth==0 ) return; + if( p->spec.eStyle==QRF_STYLE_Column ){ + sepW = pData->nCol*2 - 2; + }else{ + sepW = pData->nCol*3 + 1; + if( p->spec.bBorder==QRF_No ) sepW -= 2; + } + nCol = pData->nCol; + for(i=sumW=0; ia[i].w; + if( p->spec.nScreenWidth >= sumW+sepW ) return; -/* Begin tracing memory allocations to out. */ -int sqlite3MemTraceActivate(FILE *out){ - int rc = SQLITE_OK; - if( memtraceBase.xMalloc==0 ){ - rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memtraceBase); - if( rc==SQLITE_OK ){ - rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &ersaztMethods); - } + /* First thing to do is reduce the separation between columns */ + pData->nMargin = 0; + if( p->spec.eStyle==QRF_STYLE_Column ){ + sepW = pData->nCol - 1; + }else{ + sepW = pData->nCol + 1; + if( p->spec.bBorder==QRF_No ) sepW -= 2; } - memtraceOut = out; - return rc; -} + targetW = p->spec.nScreenWidth - sepW; -/* Deactivate memory tracing */ -int sqlite3MemTraceDeactivate(void){ - int rc = SQLITE_OK; - if( memtraceBase.xMalloc!=0 ){ - rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memtraceBase); - if( rc==SQLITE_OK ){ - memset(&memtraceBase, 0, sizeof(memtraceBase)); +#define MIN_SQUOZE 8 +#define MIN_EX_SQUOZE 16 + /* Reduce the width of the widest eligible column. A column is + ** eligible for narrowing if: + ** + ** * It is not a fixed-width column (a[0].fx is false) + ** * The current width is more than MIN_SQUOZE + ** * Either: + ** + The current width is more then MIN_EX_SQUOZE, or + ** + The current width is more than half the max width (a[].mxW) + ** + ** Keep making reductions until either no more reductions are + ** possible or until the size target is reached. + */ + while( sumW > targetW ){ + int gain, w; + int ix = -1; + int mx = 0; + for(i=0; ia[i].fx==0 + && (w = pData->a[i].w)>mx + && w>MIN_SQUOZE + && (w>MIN_EX_SQUOZE || w*2>pData->a[i].mxW) + ){ + ix = i; + mx = w; + } + } + if( ix<0 ) break; + if( mx>=MIN_SQUOZE*2 ){ + gain = mx/2; + }else{ + gain = mx - MIN_SQUOZE; + } + if( sumW - gain < targetW ){ + gain = sumW - targetW; } + sumW -= gain; + pData->a[ix].w -= gain; + pData->bMultiRow = 1; } - memtraceOut = 0; - return rc; } -/************************* End ../ext/misc/memtrace.c ********************/ -/************************* Begin ../ext/misc/pcachetrace.c ******************/ /* -** 2023-06-21 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file implements an extension that uses the SQLITE_CONFIG_PCACHE2 -** mechanism to add a tracing layer on top of pluggable page cache of -** SQLite. If this extension is registered prior to sqlite3_initialize(), -** it will cause all page cache activities to be logged on standard output, -** or to some other FILE specified by the initializer. -** -** This file needs to be compiled into the application that uses it. -** -** This extension is used to implement the --pcachetrace option of the -** command-line shell. +** Columnar modes require that the entire query be evaluated first, with +** results written into memory, so that we can compute appropriate column +** widths. */ -#include -#include -#include +static void qrfColumnar(Qrf *p){ + sqlite3_int64 i, j; /* Loop counters */ + const char *colSep = 0; /* Column separator text */ + const char *rowSep = 0; /* Row terminator text */ + const char *rowStart = 0; /* Row start text */ + int szColSep, szRowSep, szRowStart; /* Size in bytes of previous 3 */ + int rc; /* Result code */ + int nColumn = p->nCol; /* Number of columns */ + int bWW; /* True to do word-wrap */ + sqlite3_str *pStr; /* Temporary rendering */ + qrfColData data; /* Columnar layout data */ + int bRTrim; /* Trim trailing space */ -/* The original page cache routines */ -static sqlite3_pcache_methods2 pcacheBase; -static FILE *pcachetraceOut; + rc = sqlite3_step(p->pStmt); + if( rc!=SQLITE_ROW || nColumn==0 ){ + return; /* No output */ + } -/* Methods that trace pcache activity */ -static int pcachetraceInit(void *pArg){ - int nRes; - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p)\n", pArg); + /* Initialize the data container */ + memset(&data, 0, sizeof(data)); + data.nCol = p->nCol; + data.p = p; + data.a = sqlite3_malloc64( nColumn*sizeof(struct qrfPerCol) ); + if( data.a==0 ){ + qrfOom(p); + return; } - nRes = pcacheBase.xInit(pArg); - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xInit(%p) -> %d\n", pArg, nRes); + memset(data.a, 0, nColumn*sizeof(struct qrfPerCol) ); + if( qrfColDataEnlarge(&data) ) return; + assert( data.az!=0 ); + + /* Load the column header names and all cell content into data */ + if( p->spec.bTitles==QRF_Yes ){ + unsigned char saved_eText = p->spec.eText; + p->spec.eText = p->spec.eTitle; + memset(data.abNum, 0, nColumn); + for(i=0; ipStmt,i); + int nNL = 0; + int n, w; + pStr = sqlite3_str_new(p->db); + qrfEncodeText(p, pStr, z ? z : ""); + n = sqlite3_str_length(pStr); + qrfStrErr(p, pStr); + z = data.az[data.n] = sqlite3_str_finish(pStr); + if( p->spec.nTitleLimit ){ + nNL = 0; + data.aiWth[data.n] = w = qrfTitleLimit(data.az[data.n], + p->spec.nTitleLimit ); + }else{ + data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL); + } + data.n++; + if( w>data.a[i].mxW ) data.a[i].mxW = w; + if( nNL ) data.bMultiRow = 1; + } + p->spec.eText = saved_eText; + p->nRow++; } - return nRes; -} -static void pcachetraceShutdown(void *pArg){ - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg); + do{ + if( data.n+nColumn > data.nAlloc ){ + if( qrfColDataEnlarge(&data) ) return; + } + for(i=0; ipStmt,i); + pStr = sqlite3_str_new(p->db); + qrfRenderValue(p, pStr, i); + n = sqlite3_str_length(pStr); + qrfStrErr(p, pStr); + z = data.az[data.n] = sqlite3_str_finish(pStr); + data.abNum[data.n] = eType==SQLITE_INTEGER || eType==SQLITE_FLOAT; + data.aiWth[data.n] = w = qrfDisplayWidth(z, n, &nNL); + data.n++; + if( w>data.a[i].mxW ) data.a[i].mxW = w; + if( nNL ) data.bMultiRow = 1; + } + p->nRow++; + }while( sqlite3_step(p->pStmt)==SQLITE_ROW && p->iErr==SQLITE_OK ); + if( p->iErr ){ + qrfColDataFree(&data); + return; } - pcacheBase.xShutdown(pArg); -} -static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){ - sqlite3_pcache *pRes; - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n", - szPage, szExtra, bPurge); - } - pRes = pcacheBase.xCreate(szPage, szExtra, bPurge); - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n", - szPage, szExtra, bPurge, pRes); - } - return pRes; -} -static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){ - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize); - } - pcacheBase.xCachesize(p, nCachesize); -} -static int pcachetracePagecount(sqlite3_pcache *p){ - int nRes; - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p); - } - nRes = pcacheBase.xPagecount(p); - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes); - } - return nRes; -} -static sqlite3_pcache_page *pcachetraceFetch( - sqlite3_pcache *p, - unsigned key, - int crFg -){ - sqlite3_pcache_page *pRes; - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg); - } - pRes = pcacheBase.xFetch(p, key, crFg); - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n", - p, key, crFg, pRes); - } - return pRes; -} -static void pcachetraceUnpin( - sqlite3_pcache *p, - sqlite3_pcache_page *pPg, - int bDiscard -){ - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n", - p, pPg, bDiscard); - } - pcacheBase.xUnpin(p, pPg, bDiscard); -} -static void pcachetraceRekey( - sqlite3_pcache *p, - sqlite3_pcache_page *pPg, - unsigned oldKey, - unsigned newKey -){ - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n", - p, pPg, oldKey, newKey); + + /* Compute the width and alignment of every column */ + if( p->spec.bTitles==QRF_No ){ + qrfLoadAlignment(&data, p); + }else{ + unsigned char e; + if( p->spec.eTitleAlign==QRF_Auto ){ + e = QRF_ALIGN_Center; + }else{ + e = p->spec.eTitleAlign; + } + for(i=0; ispec.nWidth ){ + w = p->spec.aWidth[i]; + if( w==(-32768) ){ + w = 0; + if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){ + data.a[i].e |= QRF_ALIGN_Right; + } + }else if( w<0 ){ + w = -w; + if( p->spec.nAlign>i && (p->spec.aAlign[i] & QRF_ALIGN_HMASK)==0 ){ + data.a[i].e |= QRF_ALIGN_Right; + } + } + if( w ) data.a[i].fx = 1; + } + if( w==0 ){ + w = data.a[i].mxW; + if( p->spec.nWrap>0 && w>p->spec.nWrap ){ + w = p->spec.nWrap; + data.bMultiRow = 1; + } + }else if( (data.bMultiRow==0 || w==1) && data.a[i].mxW>w ){ + data.bMultiRow = 1; + if( w==1 ){ + /* If aiWth[j] is 2 or more, then there might be a double-wide + ** character somewhere. So make the column width at least 2. */ + w = 2; + } + } + data.a[i].w = w; } - pcacheBase.xTruncate(p, n); -} -static void pcachetraceDestroy(sqlite3_pcache *p){ - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p); + + if( nColumn==1 + && data.n>1 + && p->spec.bSplitColumn==QRF_Yes + && p->spec.eStyle==QRF_STYLE_Column + && p->spec.bTitles==QRF_No + && p->spec.nScreenWidth>data.a[0].w+3 + ){ + /* Attempt to convert single-column tables into multi-column by + ** verticle wrapping, if the screen is wide enough and if the + ** bSplitColumn flag is set. */ + qrfSplitColumn(&data, p); + nColumn = data.nCol; + }else{ + /* Adjust the column widths due to screen width restrictions */ + qrfRestrictScreenWidth(&data, p); + } + + /* Draw the line across the top of the table. Also initialize + ** the row boundary and column separator texts. */ + switch( p->spec.eStyle ){ + case QRF_STYLE_Box: + if( data.nMargin ){ + rowStart = BOX_13 " "; + colSep = " " BOX_13 " "; + rowSep = " " BOX_13 "\n"; + }else{ + rowStart = BOX_13; + colSep = BOX_13; + rowSep = BOX_13 "\n"; + } + if( p->spec.bBorder==QRF_No){ + rowStart += 3; + rowSep = "\n"; + }else{ + qrfBoxSeparator(p->pOut, &data, BOX_R23, BOX_234, BOX_R34, 0); + } + break; + case QRF_STYLE_Table: + if( data.nMargin ){ + rowStart = "| "; + colSep = " | "; + rowSep = " |\n"; + }else{ + rowStart = "|"; + colSep = "|"; + rowSep = "|\n"; + } + if( p->spec.bBorder==QRF_No ){ + rowStart += 1; + rowSep = "\n"; + }else{ + qrfRowSeparator(p->pOut, &data, '+'); + } + break; + case QRF_STYLE_Column: { + static const char zSpace[] = " "; + rowStart = ""; + if( data.nMargin<2 ){ + colSep = " "; + }else if( data.nMargin<=5 ){ + colSep = &zSpace[5-data.nMargin]; + }else{ + colSep = zSpace; + } + rowSep = "\n"; + break; + } + default: /*case QRF_STYLE_Markdown:*/ + if( data.nMargin ){ + rowStart = "| "; + colSep = " | "; + rowSep = " |\n"; + }else{ + rowStart = "|"; + colSep = "|"; + rowSep = "|\n"; + } + break; } - pcacheBase.xDestroy(p); -} -static void pcachetraceShrink(sqlite3_pcache *p){ - if( pcachetraceOut ){ - fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p); + szRowStart = (int)strlen(rowStart); + szRowSep = (int)strlen(rowSep); + szColSep = (int)strlen(colSep); + + bWW = (p->spec.bWordWrap==QRF_Yes && data.bMultiRow); + if( p->spec.eStyle==QRF_STYLE_Column + || (p->spec.bBorder==QRF_No + && (p->spec.eStyle==QRF_STYLE_Box || p->spec.eStyle==QRF_STYLE_Table) + ) + ){ + bRTrim = 1; + }else{ + bRTrim = 0; } - pcacheBase.xShrink(p); -} + for(i=0; ipOut)==SQLITE_OK; i+=nColumn){ + int bMore; + int nRow = 0; -/* The substitute pcache methods */ -static sqlite3_pcache_methods2 ersaztPcacheMethods = { - 0, - 0, - pcachetraceInit, - pcachetraceShutdown, - pcachetraceCreate, - pcachetraceCachesize, - pcachetracePagecount, - pcachetraceFetch, - pcachetraceUnpin, - pcachetraceRekey, - pcachetraceTruncate, - pcachetraceDestroy, - pcachetraceShrink -}; + /* Draw a single row of the table. This might be the title line + ** (if there is a title line) or a row in the body of the table. + ** The column number will be j. The row number is i/nColumn. + */ + for(j=0; jpOut, rowStart, szRowStart); + bMore = 0; + for(j=0; jpOut, &data.a[j], nThis, nWS); + data.a[j].z += iNext; + if( data.a[j].z[0]!=0 ){ + bMore = 1; + } + if( jpOut, colSep, szColSep); + }else{ + if( bRTrim ) qrfRTrim(p->pOut); + sqlite3_str_append(p->pOut, rowSep, szRowSep); + } + } + }while( bMore && ++nRow < p->mxHeight ); + if( bMore ){ + /* This row was terminated by nLineLimit. Show ellipsis. */ + sqlite3_str_append(p->pOut, rowStart, szRowStart); + for(j=0; jpOut, data.a[j].w, ' '); + }else{ + int nE = 3; + if( nE>data.a[j].w ) nE = data.a[j].w; + data.a[j].z = "..."; + qrfPrintAligned(p->pOut, &data.a[j], nE, data.a[j].w-nE); + } + if( jpOut, colSep, szColSep); + }else{ + if( bRTrim ) qrfRTrim(p->pOut); + sqlite3_str_append(p->pOut, rowSep, szRowSep); + } + } + } -/* Begin tracing memory allocations to out. */ -int sqlite3PcacheTraceActivate(FILE *out){ - int rc = SQLITE_OK; - if( pcacheBase.xFetch==0 ){ - rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase); - if( rc==SQLITE_OK ){ - rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods); + /* Draw either (1) the separator between the title line and the body + ** of the table, or (2) separators between individual rows of the table + ** body. isTitleDataSeparator will be true if we are doing (1). + */ + if( (i==0 || data.bMultiRow) && i+nColumnspec.bTitles==QRF_Yes); + if( isTitleDataSeparator ){ + qrfLoadAlignment(&data, p); + } + switch( p->spec.eStyle ){ + case QRF_STYLE_Table: { + if( isTitleDataSeparator || data.bMultiRow ){ + qrfRowSeparator(p->pOut, &data, '+'); + } + break; + } + case QRF_STYLE_Box: { + if( isTitleDataSeparator ){ + qrfBoxSeparator(p->pOut, &data, DBL_123, DBL_1234, DBL_134, 1); + }else if( data.bMultiRow ){ + qrfBoxSeparator(p->pOut, &data, BOX_123, BOX_1234, BOX_134, 0); + } + break; + } + case QRF_STYLE_Markdown: { + if( isTitleDataSeparator ){ + qrfRowSeparator(p->pOut, &data, '|'); + } + break; + } + case QRF_STYLE_Column: { + if( isTitleDataSeparator ){ + for(j=0; jpOut, data.a[j].w, '-'); + if( jpOut, colSep, szColSep); + }else{ + qrfRTrim(p->pOut); + sqlite3_str_append(p->pOut, rowSep, szRowSep); + } + } + }else if( data.bMultiRow ){ + qrfRTrim(p->pOut); + sqlite3_str_append(p->pOut, "\n", 1); + } + break; + } + } } } - pcachetraceOut = out; - return rc; -} -/* Deactivate memory tracing */ -int sqlite3PcacheTraceDeactivate(void){ - int rc = SQLITE_OK; - if( pcacheBase.xFetch!=0 ){ - rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase); - if( rc==SQLITE_OK ){ - memset(&pcacheBase, 0, sizeof(pcacheBase)); + /* Draw the line across the bottom of the table */ + if( p->spec.bBorder!=QRF_No ){ + switch( p->spec.eStyle ){ + case QRF_STYLE_Box: + qrfBoxSeparator(p->pOut, &data, BOX_R12, BOX_124, BOX_R14, 0); + break; + case QRF_STYLE_Table: + qrfRowSeparator(p->pOut, &data, '+'); + break; } } - pcachetraceOut = 0; - return rc; + qrfWrite(p); + + qrfColDataFree(&data); + return; } -/************************* End ../ext/misc/pcachetrace.c ********************/ -/************************* Begin ../ext/misc/shathree.c ******************/ /* -** 2017-03-08 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** +** Parameter azArray points to a zero-terminated array of strings. zStr +** points to a single nul-terminated string. Return non-zero if zStr +** is equal, according to strcmp(), to any of the strings in the array. +** Otherwise, return zero. +*/ +static int qrfStringInArray(const char *zStr, const char **azArray){ + int i; + if( zStr==0 ) return 0; + for(i=0; azArray[i]; i++){ + if( 0==strcmp(zStr, azArray[i]) ) return 1; + } + return 0; +} + +/* +** Print out an EXPLAIN with indentation. This is a two-pass algorithm. ** -** This SQLite extension implements functions that compute SHA3 hashes -** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. -** Two SQL functions are implemented: +** On the first pass, we compute aiIndent[iOp] which is the amount of +** indentation to apply to the iOp-th opcode. The output actually occurs +** on the second pass. ** -** sha3(X,SIZE) -** sha3_query(Y,SIZE) +** The indenting rules are: ** -** The sha3(X) function computes the SHA3 hash of the input X, or NULL if -** X is NULL. +** * For each "Next", "Prev", "VNext" or "VPrev" instruction, indent +** all opcodes that occur between the p2 jump destination and the opcode +** itself by 2 spaces. ** -** The sha3_query(Y) function evaluates all queries in the SQL statements of Y -** and returns a hash of their results. +** * Do the previous for "Return" instructions for when P2 is positive. +** See tag-20220407a in wherecode.c and vdbe.c. ** -** The SIZE argument is optional. If omitted, the SHA3-256 hash algorithm -** is used. If SIZE is included it must be one of the integers 224, 256, -** 384, or 512, to determine SHA3 hash variant that is computed. +** * For each "Goto", if the jump destination is earlier in the program +** and ends on one of: +** Yield SeekGt SeekLt RowSetRead Rewind +** or if the P1 parameter is one instead of zero, +** then indent all opcodes between the earlier instruction +** and "Goto" by 2 spaces. */ -/* #include "sqlite3ext.h" */ -SQLITE_EXTENSION_INIT1 -#include -#include -#include +static void qrfExplain(Qrf *p){ + int *abYield = 0; /* abYield[iOp] is rue if opcode iOp is an OP_Yield */ + int *aiIndent = 0; /* Indent the iOp-th opcode by aiIndent[iOp] */ + i64 nAlloc = 0; /* Allocated size of aiIndent[], abYield */ + int nIndent = 0; /* Number of entries in aiIndent[] */ + int iOp; /* Opcode number */ + int i; /* Column loop counter */ -#ifndef SQLITE_AMALGAMATION -/* typedef sqlite3_uint64 u64; */ -#endif /* SQLITE_AMALGAMATION */ + const char *azNext[] = { "Next", "Prev", "VPrev", "VNext", "SorterNext", + "Return", 0 }; + const char *azYield[] = { "Yield", "SeekLT", "SeekGT", "RowSetRead", + "Rewind", 0 }; + const char *azGoto[] = { "Goto", 0 }; -/****************************************************************************** -** The Hash Engine -*/ -/* -** Macros to determine whether the machine is big or little endian, -** and whether or not that determination is run-time or compile-time. -** -** For best performance, an attempt is made to guess at the byte-order -** using C-preprocessor macros. If that is unsuccessful, or if -** -DSHA3_BYTEORDER=0 is set, then byte-order is determined -** at run-time. -*/ -#ifndef SHA3_BYTEORDER -# if defined(i386) || defined(__i386__) || defined(_M_IX86) || \ - defined(__x86_64) || defined(__x86_64__) || defined(_M_X64) || \ - defined(_M_AMD64) || defined(_M_ARM) || defined(__x86) || \ - defined(__arm__) -# define SHA3_BYTEORDER 1234 -# elif defined(sparc) || defined(__ppc__) -# define SHA3_BYTEORDER 4321 -# else -# define SHA3_BYTEORDER 0 -# endif -#endif + /* The caller guarantees that the leftmost 4 columns of the statement + ** passed to this function are equivalent to the leftmost 4 columns + ** of EXPLAIN statement output. In practice the statement may be + ** an EXPLAIN, or it may be a query on the bytecode() virtual table. */ + assert( sqlite3_column_count(p->pStmt)>=4 ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 0), "addr" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 1), "opcode" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 2), "p1" ) ); + assert( 0==sqlite3_stricmp( sqlite3_column_name(p->pStmt, 3), "p2" ) ); + + for(iOp=0; SQLITE_ROW==sqlite3_step(p->pStmt) && !p->iErr; iOp++){ + int iAddr = sqlite3_column_int(p->pStmt, 0); + const char *zOp = (const char*)sqlite3_column_text(p->pStmt, 1); + int p1 = sqlite3_column_int(p->pStmt, 2); + int p2 = sqlite3_column_int(p->pStmt, 3); + + /* Assuming that p2 is an instruction address, set variable p2op to the + ** index of that instruction in the aiIndent[] array. p2 and p2op may be + ** different if the current instruction is part of a sub-program generated + ** by an SQL trigger or foreign key. */ + int p2op = (p2 + (iOp-iAddr)); + + /* Grow the aiIndent array as required */ + if( iOp>=nAlloc ){ + nAlloc += 100; + aiIndent = (int*)sqlite3_realloc64(aiIndent, nAlloc*sizeof(int)); + abYield = (int*)sqlite3_realloc64(abYield, nAlloc*sizeof(int)); + if( aiIndent==0 || abYield==0 ){ + qrfOom(p); + sqlite3_free(aiIndent); + sqlite3_free(abYield); + return; + } + } + + abYield[iOp] = qrfStringInArray(zOp, azYield); + aiIndent[iOp] = 0; + nIndent = iOp+1; + if( qrfStringInArray(zOp, azNext) && p2op>0 ){ + for(i=p2op; ipStmt); + if( p->iErr==SQLITE_OK ){ + static const int aExplainWidth[] = {4, 13, 4, 4, 4, 13, 2, 13}; + static const int aExplainMap[] = {0, 1, 2, 3, 4, 5, 6, 7 }; + static const int aScanExpWidth[] = {4,15, 6, 13, 4, 4, 4, 13, 2, 13}; + static const int aScanExpMap[] = {0, 9, 8, 1, 2, 3, 4, 5, 6, 7 }; + const int *aWidth = aExplainWidth; + const int *aMap = aExplainMap; + int nWidth = sizeof(aExplainWidth)/sizeof(int); + int iIndent = 1; + int nArg = p->nCol; + if( p->spec.eStyle==QRF_STYLE_StatsVm ){ + aWidth = aScanExpWidth; + aMap = aScanExpMap; + nWidth = sizeof(aScanExpWidth)/sizeof(int); + iIndent = 3; + } + if( nArg>nWidth ) nArg = nWidth; + + for(iOp=0; sqlite3_step(p->pStmt)==SQLITE_ROW && !p->iErr; iOp++){ + /* If this is the first row seen, print out the headers */ + if( iOp==0 ){ + for(i=0; ipStmt, aMap[i]); + qrfWidthPrint(p,p->pOut, aWidth[i], zCol); + if( i==nArg-1 ){ + sqlite3_str_append(p->pOut, "\n", 1); + }else{ + sqlite3_str_append(p->pOut, " ", 2); + } + } + for(i=0; ipOut, "%.*c", aWidth[i], '-'); + if( i==nArg-1 ){ + sqlite3_str_append(p->pOut, "\n", 1); + }else{ + sqlite3_str_append(p->pOut, " ", 2); + } + } + } + + for(i=0; ipStmt, aMap[i]); + int len; + if( i==nArg-1 ) w = 0; + if( zVal==0 ) zVal = ""; + len = (int)sqlite3_qrf_wcswidth(zVal); + if( len>w ){ + w = len; + zSep = " "; + } + if( i==iIndent && aiIndent && iOppOut, aiIndent[iOp], ' '); + } + qrfWidthPrint(p, p->pOut, w, zVal); + if( i==nArg-1 ){ + sqlite3_str_append(p->pOut, "\n", 1); + }else{ + sqlite3_str_appendall(p->pOut, zSep); + } + } + p->nRow++; + } + qrfWrite(p); + } + sqlite3_free(aiIndent); +} /* -** State structure for a SHA3 hash in progress +** Do a "scanstatus vm" style EXPLAIN listing on p->pStmt. +** +** p->pStmt is probably not an EXPLAIN query. Instead, construct a +** new query that is a bytecode() rendering of p->pStmt with extra +** columns for the "scanstatus vm" outputs, and run the results of +** that new query through the normal EXPLAIN formatting. */ -typedef struct SHA3Context SHA3Context; -struct SHA3Context { - union { - u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */ - unsigned char x[1600]; /* ... or 1600 bytes */ - } u; - unsigned nRate; /* Bytes of input accepted per Keccak iteration */ - unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */ - unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */ -}; +static void qrfScanStatusVm(Qrf *p){ + sqlite3_stmt *pOrigStmt = p->pStmt; + sqlite3_stmt *pExplain; + int rc; + static const char *zSql = + " SELECT addr, opcode, p1, p2, p3, p4, p5, comment, nexec," + " format('% 6s (%.2f%%)'," + " CASE WHEN ncycle<100_000 THEN ncycle || ' '" + " WHEN ncycle<100_000_000 THEN (ncycle/1_000) || 'K'" + " WHEN ncycle<100_000_000_000 THEN (ncycle/1_000_000) || 'M'" + " ELSE (ncycle/1000_000_000) || 'G' END," + " ncycle*100.0/(sum(ncycle) OVER ())" + " ) AS cycles" + " FROM bytecode(?1)"; + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pExplain, 0); + if( rc ){ + qrfError(p, rc, "%s", sqlite3_errmsg(p->db)); + sqlite3_finalize(pExplain); + return; + } + sqlite3_bind_pointer(pExplain, 1, pOrigStmt, "stmt-pointer", 0); + p->pStmt = pExplain; + p->nCol = 10; + qrfExplain(p); + sqlite3_finalize(pExplain); + p->pStmt = pOrigStmt; +} /* -** A single step of the Keccak mixing function for a 1600-bit state +** Attempt to determine if identifier zName needs to be quoted, either +** because it contains non-alphanumeric characters, or because it is an +** SQLite keyword. Be conservative in this estimate: When in doubt assume +** that quoting is required. +** +** Return 1 if quoting is required. Return 0 if no quoting is required. */ -static void KeccakF1600Step(SHA3Context *p){ - int i; - u64 b0, b1, b2, b3, b4; - u64 c0, c1, c2, c3, c4; - u64 d0, d1, d2, d3, d4; - static const u64 RC[] = { - 0x0000000000000001ULL, 0x0000000000008082ULL, - 0x800000000000808aULL, 0x8000000080008000ULL, - 0x000000000000808bULL, 0x0000000080000001ULL, - 0x8000000080008081ULL, 0x8000000000008009ULL, - 0x000000000000008aULL, 0x0000000000000088ULL, - 0x0000000080008009ULL, 0x000000008000000aULL, - 0x000000008000808bULL, 0x800000000000008bULL, - 0x8000000000008089ULL, 0x8000000000008003ULL, - 0x8000000000008002ULL, 0x8000000000000080ULL, - 0x000000000000800aULL, 0x800000008000000aULL, - 0x8000000080008081ULL, 0x8000000000008080ULL, - 0x0000000080000001ULL, 0x8000000080008008ULL - }; -# define a00 (p->u.s[0]) -# define a01 (p->u.s[1]) -# define a02 (p->u.s[2]) -# define a03 (p->u.s[3]) -# define a04 (p->u.s[4]) -# define a10 (p->u.s[5]) -# define a11 (p->u.s[6]) -# define a12 (p->u.s[7]) -# define a13 (p->u.s[8]) -# define a14 (p->u.s[9]) -# define a20 (p->u.s[10]) -# define a21 (p->u.s[11]) -# define a22 (p->u.s[12]) -# define a23 (p->u.s[13]) -# define a24 (p->u.s[14]) -# define a30 (p->u.s[15]) -# define a31 (p->u.s[16]) -# define a32 (p->u.s[17]) -# define a33 (p->u.s[18]) -# define a34 (p->u.s[19]) -# define a40 (p->u.s[20]) -# define a41 (p->u.s[21]) -# define a42 (p->u.s[22]) -# define a43 (p->u.s[23]) -# define a44 (p->u.s[24]) -# define ROL64(a,x) ((a<>(64-x))) - - for(i=0; i<24; i+=4){ - c0 = a00^a10^a20^a30^a40; - c1 = a01^a11^a21^a31^a41; - c2 = a02^a12^a22^a32^a42; - c3 = a03^a13^a23^a33^a43; - c4 = a04^a14^a24^a34^a44; - d0 = c4^ROL64(c1, 1); - d1 = c0^ROL64(c2, 1); - d2 = c1^ROL64(c3, 1); - d3 = c2^ROL64(c4, 1); - d4 = c3^ROL64(c0, 1); - - b0 = (a00^d0); - b1 = ROL64((a11^d1), 44); - b2 = ROL64((a22^d2), 43); - b3 = ROL64((a33^d3), 21); - b4 = ROL64((a44^d4), 14); - a00 = b0 ^((~b1)& b2 ); - a00 ^= RC[i]; - a11 = b1 ^((~b2)& b3 ); - a22 = b2 ^((~b3)& b4 ); - a33 = b3 ^((~b4)& b0 ); - a44 = b4 ^((~b0)& b1 ); - - b2 = ROL64((a20^d0), 3); - b3 = ROL64((a31^d1), 45); - b4 = ROL64((a42^d2), 61); - b0 = ROL64((a03^d3), 28); - b1 = ROL64((a14^d4), 20); - a20 = b0 ^((~b1)& b2 ); - a31 = b1 ^((~b2)& b3 ); - a42 = b2 ^((~b3)& b4 ); - a03 = b3 ^((~b4)& b0 ); - a14 = b4 ^((~b0)& b1 ); - b4 = ROL64((a40^d0), 18); - b0 = ROL64((a01^d1), 1); - b1 = ROL64((a12^d2), 6); - b2 = ROL64((a23^d3), 25); - b3 = ROL64((a34^d4), 8); - a40 = b0 ^((~b1)& b2 ); - a01 = b1 ^((~b2)& b3 ); - a12 = b2 ^((~b3)& b4 ); - a23 = b3 ^((~b4)& b0 ); - a34 = b4 ^((~b0)& b1 ); +static int qrf_need_quote(const char *zName){ + int i; + const unsigned char *z = (const unsigned char*)zName; + if( z==0 ) return 1; + if( !qrfAlpha(z[0]) ) return 1; + for(i=0; z[i]; i++){ + if( !qrfAlnum(z[i]) ) return 1; + } + return sqlite3_keyword_check(zName, i)!=0; +} - b1 = ROL64((a10^d0), 36); - b2 = ROL64((a21^d1), 10); - b3 = ROL64((a32^d2), 15); - b4 = ROL64((a43^d3), 56); - b0 = ROL64((a04^d4), 27); - a10 = b0 ^((~b1)& b2 ); - a21 = b1 ^((~b2)& b3 ); - a32 = b2 ^((~b3)& b4 ); - a43 = b3 ^((~b4)& b0 ); - a04 = b4 ^((~b0)& b1 ); +/* +** Helper function for QRF_STYLE_Json and QRF_STYLE_JObject. +** The initial "{" for a JSON object that will contain row content +** has been output. Now output all the content. +*/ +static void qrfOneJsonRow(Qrf *p){ + int i, nItem; + for(nItem=i=0; inCol; i++){ + const char *zCName; + zCName = sqlite3_column_name(p->pStmt, i); + if( nItem>0 ) sqlite3_str_append(p->pOut, ",", 1); + nItem++; + qrfEncodeText(p, p->pOut, zCName); + sqlite3_str_append(p->pOut, ":", 1); + qrfRenderValue(p, p->pOut, i); + } + qrfWrite(p); +} - b3 = ROL64((a30^d0), 41); - b4 = ROL64((a41^d1), 2); - b0 = ROL64((a02^d2), 62); - b1 = ROL64((a13^d3), 55); - b2 = ROL64((a24^d4), 39); - a30 = b0 ^((~b1)& b2 ); - a41 = b1 ^((~b2)& b3 ); - a02 = b2 ^((~b3)& b4 ); - a13 = b3 ^((~b4)& b0 ); - a24 = b4 ^((~b0)& b1 ); +/* +** Render a single row of output for non-columnar styles - any +** style that lets us render row by row as the content is received +** from the query. +*/ +static void qrfOneSimpleRow(Qrf *p){ + int i; + switch( p->spec.eStyle ){ + case QRF_STYLE_Off: + case QRF_STYLE_Count: { + /* No-op */ + break; + } + case QRF_STYLE_Json: { + if( p->nRow==0 ){ + sqlite3_str_append(p->pOut, "[{", 2); + }else{ + sqlite3_str_append(p->pOut, "},\n{", 4); + } + qrfOneJsonRow(p); + break; + } + case QRF_STYLE_JObject: { + if( p->nRow==0 ){ + sqlite3_str_append(p->pOut, "{", 1); + }else{ + sqlite3_str_append(p->pOut, "}\n{", 3); + } + qrfOneJsonRow(p); + break; + } + case QRF_STYLE_Html: { + if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){ + sqlite3_str_append(p->pOut, "", 4); + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + sqlite3_str_append(p->pOut, "\n", 5); + qrfEncodeText(p, p->pOut, zCName); + } + sqlite3_str_append(p->pOut, "\n\n", 7); + } + sqlite3_str_append(p->pOut, "", 4); + for(i=0; inCol; i++){ + sqlite3_str_append(p->pOut, "\n", 5); + qrfRenderValue(p, p->pOut, i); + } + sqlite3_str_append(p->pOut, "\n\n", 7); + qrfWrite(p); + break; + } + case QRF_STYLE_Insert: { + unsigned int mxIns = p->spec.nMultiInsert; + int szStart = sqlite3_str_length(p->pOut); + if( p->u.nIns==0 || p->u.nIns>=mxIns ){ + if( p->u.nIns ){ + sqlite3_str_append(p->pOut, ";\n", 2); + p->u.nIns = 0; + } + if( qrf_need_quote(p->spec.zTableName) ){ + sqlite3_str_appendf(p->pOut,"INSERT INTO \"%w\"",p->spec.zTableName); + }else{ + sqlite3_str_appendf(p->pOut,"INSERT INTO %s",p->spec.zTableName); + } + if( p->spec.bTitles==QRF_Yes ){ + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + if( qrf_need_quote(zCName) ){ + sqlite3_str_appendf(p->pOut, "%c\"%w\"", + i==0 ? '(' : ',', zCName); + }else{ + sqlite3_str_appendf(p->pOut, "%c%s", + i==0 ? '(' : ',', zCName); + } + } + sqlite3_str_append(p->pOut, ")", 1); + } + sqlite3_str_append(p->pOut," VALUES(", 8); + }else{ + sqlite3_str_append(p->pOut,",\n (", 5); + } + for(i=0; inCol; i++){ + if( i>0 ) sqlite3_str_append(p->pOut, ",", 1); + qrfRenderValue(p, p->pOut, i); + } + p->u.nIns += sqlite3_str_length(p->pOut) + 2 - szStart; + if( p->u.nIns>=mxIns ){ + sqlite3_str_append(p->pOut, ");\n", 3); + p->u.nIns = 0; + }else{ + sqlite3_str_append(p->pOut, ")", 1); + } + qrfWrite(p); + break; + } + case QRF_STYLE_Line: { + sqlite3_str *pVal; + int mxW; + int bWW; + int nSep; + if( p->u.sLine.azCol==0 ){ + p->u.sLine.azCol = sqlite3_malloc64( p->nCol*sizeof(char*) ); + if( p->u.sLine.azCol==0 ){ + qrfOom(p); + break; + } + p->u.sLine.mxColWth = 0; + for(i=0; inCol; i++){ + int sz; + const char *zCName = sqlite3_column_name(p->pStmt, i); + if( zCName==0 ) zCName = "unknown"; + p->u.sLine.azCol[i] = sqlite3_mprintf("%s", zCName); + if( p->spec.nTitleLimit>0 ){ + (void)qrfTitleLimit(p->u.sLine.azCol[i], p->spec.nTitleLimit); + } + sz = (int)sqlite3_qrf_wcswidth(p->u.sLine.azCol[i]); + if( sz > p->u.sLine.mxColWth ) p->u.sLine.mxColWth = sz; + } + } + if( p->nRow ) sqlite3_str_append(p->pOut, "\n", 1); + pVal = sqlite3_str_new(p->db); + nSep = (int)strlen(p->spec.zColumnSep); + mxW = p->mxWidth - (nSep + p->u.sLine.mxColWth); + bWW = p->spec.bWordWrap==QRF_Yes; + for(i=0; inCol; i++){ + const char *zVal; + int cnt = 0; + qrfWidthPrint(p, p->pOut, -p->u.sLine.mxColWth, p->u.sLine.azCol[i]); + sqlite3_str_append(p->pOut, p->spec.zColumnSep, nSep); + qrfRenderValue(p, pVal, i); + zVal = sqlite3_str_value(pVal); + if( zVal==0 ) zVal = ""; + do{ + int nThis, nWide, iNext; + qrfWrapLine(zVal, mxW, bWW, &nThis, &nWide, &iNext); + if( cnt ){ + sqlite3_str_appendchar(p->pOut,p->u.sLine.mxColWth+nSep,' '); + } + cnt++; + if( cnt>p->mxHeight ){ + zVal = "..."; + nThis = iNext = 3; + } + sqlite3_str_append(p->pOut, zVal, nThis); + sqlite3_str_append(p->pOut, "\n", 1); + zVal += iNext; + }while( zVal[0] ); + sqlite3_str_reset(pVal); + } + qrfStrErr(p, pVal); + sqlite3_free(sqlite3_str_finish(pVal)); + qrfWrite(p); + break; + } + case QRF_STYLE_Eqp: { + const char *zEqpLine = (const char*)sqlite3_column_text(p->pStmt,3); + int iEqpId = sqlite3_column_int(p->pStmt, 0); + int iParentId = sqlite3_column_int(p->pStmt, 1); + if( zEqpLine==0 ) zEqpLine = ""; + if( zEqpLine[0]=='-' ) qrfEqpRender(p, 0); + qrfEqpAppend(p, iEqpId, iParentId, zEqpLine); + break; + } + default: { /* QRF_STYLE_List */ + if( p->nRow==0 && p->spec.bTitles==QRF_Yes ){ + int saved_eText = p->spec.eText; + p->spec.eText = p->spec.eTitle; + for(i=0; inCol; i++){ + const char *zCName = sqlite3_column_name(p->pStmt, i); + if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep); + qrfEncodeText(p, p->pOut, zCName); + } + sqlite3_str_appendall(p->pOut, p->spec.zRowSep); + qrfWrite(p); + p->spec.eText = saved_eText; + } + for(i=0; inCol; i++){ + if( i>0 ) sqlite3_str_appendall(p->pOut, p->spec.zColumnSep); + qrfRenderValue(p, p->pOut, i); + } + sqlite3_str_appendall(p->pOut, p->spec.zRowSep); + qrfWrite(p); + break; + } + } + p->nRow++; +} - c0 = a00^a20^a40^a10^a30; - c1 = a11^a31^a01^a21^a41; - c2 = a22^a42^a12^a32^a02; - c3 = a33^a03^a23^a43^a13; - c4 = a44^a14^a34^a04^a24; - d0 = c4^ROL64(c1, 1); - d1 = c0^ROL64(c2, 1); - d2 = c1^ROL64(c3, 1); - d3 = c2^ROL64(c4, 1); - d4 = c3^ROL64(c0, 1); +/* +** Initialize the internal Qrf object. +*/ +static void qrfInitialize( + Qrf *p, /* State object to be initialized */ + sqlite3_stmt *pStmt, /* Query whose output to be formatted */ + const sqlite3_qrf_spec *pSpec, /* Format specification */ + char **pzErr /* Write errors here */ +){ + size_t sz; /* Size of pSpec[], based on pSpec->iVersion */ + memset(p, 0, sizeof(*p)); + p->pzErr = pzErr; + if( pSpec->iVersion>1 ){ + qrfError(p, SQLITE_ERROR, + "unusable sqlite3_qrf_spec.iVersion (%d)", + pSpec->iVersion); + return; + } + p->pStmt = pStmt; + p->db = sqlite3_db_handle(pStmt); + p->pOut = sqlite3_str_new(p->db); + if( p->pOut==0 ){ + qrfOom(p); + return; + } + p->iErr = SQLITE_OK; + p->nCol = sqlite3_column_count(p->pStmt); + p->nRow = 0; + sz = sizeof(sqlite3_qrf_spec); + memcpy(&p->spec, pSpec, sz); + if( p->spec.zNull==0 ) p->spec.zNull = ""; + p->mxWidth = p->spec.nScreenWidth; + if( p->mxWidth<=0 ) p->mxWidth = QRF_MAX_WIDTH; + p->mxHeight = p->spec.nLineLimit; + if( p->mxHeight<=0 ) p->mxHeight = 2147483647; + if( p->spec.eStyle>QRF_STYLE_Table ) p->spec.eStyle = QRF_Auto; + if( p->spec.eEsc>QRF_ESC_Symbol ) p->spec.eEsc = QRF_Auto; + if( p->spec.eText>QRF_TEXT_Relaxed ) p->spec.eText = QRF_Auto; + if( p->spec.eTitle>QRF_TEXT_Relaxed ) p->spec.eTitle = QRF_Auto; + if( p->spec.eBlob>QRF_BLOB_Size ) p->spec.eBlob = QRF_Auto; +qrf_reinit: + switch( p->spec.eStyle ){ + case QRF_Auto: { + switch( sqlite3_stmt_isexplain(pStmt) ){ + case 0: p->spec.eStyle = QRF_STYLE_Box; break; + case 1: p->spec.eStyle = QRF_STYLE_Explain; break; + default: p->spec.eStyle = QRF_STYLE_Eqp; break; + } + goto qrf_reinit; + } + case QRF_STYLE_List: { + if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = "|"; + if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n"; + break; + } + case QRF_STYLE_JObject: + case QRF_STYLE_Json: { + p->spec.eText = QRF_TEXT_Json; + p->spec.zNull = "null"; + break; + } + case QRF_STYLE_Html: { + p->spec.eText = QRF_TEXT_Html; + p->spec.zNull = "null"; + break; + } + case QRF_STYLE_Insert: { + p->spec.eText = QRF_TEXT_Sql; + p->spec.zNull = "NULL"; + if( p->spec.zTableName==0 || p->spec.zTableName[0]==0 ){ + p->spec.zTableName = "tab"; + } + p->u.nIns = 0; + break; + } + case QRF_STYLE_Line: { + if( p->spec.zColumnSep==0 ){ + p->spec.zColumnSep = ": "; + } + break; + } + case QRF_STYLE_Csv: { + p->spec.eStyle = QRF_STYLE_List; + p->spec.eText = QRF_TEXT_Csv; + p->spec.zColumnSep = ","; + p->spec.zRowSep = "\r\n"; + p->spec.zNull = ""; + break; + } + case QRF_STYLE_Quote: { + p->spec.eText = QRF_TEXT_Sql; + p->spec.zNull = "NULL"; + p->spec.zColumnSep = ","; + p->spec.zRowSep = "\n"; + break; + } + case QRF_STYLE_Eqp: { + int expMode = sqlite3_stmt_isexplain(p->pStmt); + if( expMode!=2 ){ + sqlite3_stmt_explain(p->pStmt, 2); + p->expMode = expMode+1; + } + break; + } + case QRF_STYLE_Explain: { + int expMode = sqlite3_stmt_isexplain(p->pStmt); + if( expMode!=1 ){ + sqlite3_stmt_explain(p->pStmt, 1); + p->expMode = expMode+1; + } + break; + } + } + if( p->spec.eEsc==QRF_Auto ){ + p->spec.eEsc = QRF_ESC_Ascii; + } + if( p->spec.eText==QRF_Auto ){ + p->spec.eText = QRF_TEXT_Plain; + } + if( p->spec.eTitle==QRF_Auto ){ + switch( p->spec.eStyle ){ + case QRF_STYLE_Box: + case QRF_STYLE_Column: + case QRF_STYLE_Table: + p->spec.eTitle = QRF_TEXT_Plain; + break; + default: + p->spec.eTitle = p->spec.eText; + break; + } + } + if( p->spec.eBlob==QRF_Auto ){ + switch( p->spec.eText ){ + case QRF_TEXT_Sql: p->spec.eBlob = QRF_BLOB_Sql; break; + case QRF_TEXT_Csv: p->spec.eBlob = QRF_BLOB_Tcl; break; + case QRF_TEXT_Tcl: p->spec.eBlob = QRF_BLOB_Tcl; break; + case QRF_TEXT_Json: p->spec.eBlob = QRF_BLOB_Json; break; + default: p->spec.eBlob = QRF_BLOB_Text; break; + } + } + if( p->spec.bTitles==QRF_Auto ){ + switch( p->spec.eStyle ){ + case QRF_STYLE_Box: + case QRF_STYLE_Csv: + case QRF_STYLE_Column: + case QRF_STYLE_Table: + case QRF_STYLE_Markdown: + p->spec.bTitles = QRF_Yes; + break; + default: + p->spec.bTitles = QRF_No; + break; + } + } + if( p->spec.bWordWrap==QRF_Auto ){ + p->spec.bWordWrap = QRF_Yes; + } + if( p->spec.bTextJsonb==QRF_Auto ){ + p->spec.bTextJsonb = QRF_No; + } + if( p->spec.zColumnSep==0 ) p->spec.zColumnSep = ","; + if( p->spec.zRowSep==0 ) p->spec.zRowSep = "\n"; +} - b0 = (a00^d0); - b1 = ROL64((a31^d1), 44); - b2 = ROL64((a12^d2), 43); - b3 = ROL64((a43^d3), 21); - b4 = ROL64((a24^d4), 14); - a00 = b0 ^((~b1)& b2 ); - a00 ^= RC[i+1]; - a31 = b1 ^((~b2)& b3 ); - a12 = b2 ^((~b3)& b4 ); - a43 = b3 ^((~b4)& b0 ); - a24 = b4 ^((~b0)& b1 ); +/* +** Finish rendering the results +*/ +static void qrfFinalize(Qrf *p){ + switch( p->spec.eStyle ){ + case QRF_STYLE_Count: { + sqlite3_str_appendf(p->pOut, "%lld\n", p->nRow); + break; + } + case QRF_STYLE_Json: { + if( p->nRow>0 ){ + sqlite3_str_append(p->pOut, "}]\n", 3); + } + break; + } + case QRF_STYLE_JObject: { + if( p->nRow>0 ){ + sqlite3_str_append(p->pOut, "}\n", 2); + } + break; + } + case QRF_STYLE_Insert: { + if( p->u.nIns ){ + sqlite3_str_append(p->pOut, ";\n", 2); + } + break; + } + case QRF_STYLE_Line: { + if( p->u.sLine.azCol ){ + int i; + for(i=0; inCol; i++) sqlite3_free(p->u.sLine.azCol[i]); + sqlite3_free(p->u.sLine.azCol); + } + break; + } + case QRF_STYLE_Stats: + case QRF_STYLE_StatsEst: { + i64 nCycle = 0; +#ifdef SQLITE_ENABLE_STMT_SCANSTATUS + sqlite3_stmt_scanstatus_v2(p->pStmt, -1, SQLITE_SCANSTAT_NCYCLE, + SQLITE_SCANSTAT_COMPLEX, (void*)&nCycle); +#endif + qrfEqpRender(p, nCycle); + break; + } + case QRF_STYLE_Eqp: { + qrfEqpRender(p, 0); + break; + } + } + qrfWrite(p); + qrfStrErr(p, p->pOut); + if( p->spec.pzOutput ){ + if( p->spec.pzOutput[0] ){ + sqlite3_int64 n, sz; + char *zCombined; + sz = strlen(p->spec.pzOutput[0]); + n = sqlite3_str_length(p->pOut); + zCombined = sqlite3_realloc64(p->spec.pzOutput[0], sz+n+1); + if( zCombined==0 ){ + sqlite3_free(p->spec.pzOutput[0]); + p->spec.pzOutput[0] = 0; + qrfOom(p); + }else{ + p->spec.pzOutput[0] = zCombined; + memcpy(zCombined+sz, sqlite3_str_value(p->pOut), n+1); + } + sqlite3_free(sqlite3_str_finish(p->pOut)); + }else{ + p->spec.pzOutput[0] = sqlite3_str_finish(p->pOut); + } + }else if( p->pOut ){ + sqlite3_free(sqlite3_str_finish(p->pOut)); + } + if( p->expMode>0 ){ + sqlite3_stmt_explain(p->pStmt, p->expMode-1); + } + if( p->actualWidth ){ + sqlite3_free(p->actualWidth); + } + if( p->pJTrans ){ + sqlite3 *db = sqlite3_db_handle(p->pJTrans); + sqlite3_finalize(p->pJTrans); + sqlite3_close(db); + } +} - b2 = ROL64((a40^d0), 3); - b3 = ROL64((a21^d1), 45); - b4 = ROL64((a02^d2), 61); - b0 = ROL64((a33^d3), 28); - b1 = ROL64((a14^d4), 20); - a40 = b0 ^((~b1)& b2 ); - a21 = b1 ^((~b2)& b3 ); - a02 = b2 ^((~b3)& b4 ); - a33 = b3 ^((~b4)& b0 ); - a14 = b4 ^((~b0)& b1 ); +/* +** Run the prepared statement pStmt and format the results according +** to the specification provided in pSpec. Return an error code. +** If pzErr is not NULL and if an error occurs, write an error message +** into *pzErr. +*/ +int sqlite3_format_query_result( + sqlite3_stmt *pStmt, /* Statement to evaluate */ + const sqlite3_qrf_spec *pSpec, /* Format specification */ + char **pzErr /* Write error message here */ +){ + Qrf qrf; /* The new Qrf being created */ + + if( pStmt==0 ) return SQLITE_OK; /* No-op */ + if( pSpec==0 ) return SQLITE_MISUSE; + qrfInitialize(&qrf, pStmt, pSpec, pzErr); + switch( qrf.spec.eStyle ){ + case QRF_STYLE_Box: + case QRF_STYLE_Column: + case QRF_STYLE_Markdown: + case QRF_STYLE_Table: { + /* Columnar modes require that the entire query be evaluated and the + ** results stored in memory, so that we can compute column widths */ + qrfColumnar(&qrf); + break; + } + case QRF_STYLE_Explain: { + qrfExplain(&qrf); + break; + } + case QRF_STYLE_StatsVm: { + qrfScanStatusVm(&qrf); + break; + } + case QRF_STYLE_Stats: + case QRF_STYLE_StatsEst: { + qrfEqpStats(&qrf); + break; + } + default: { + /* Non-columnar modes where the output can occur after each row + ** of result is received */ + while( qrf.iErr==SQLITE_OK && sqlite3_step(pStmt)==SQLITE_ROW ){ + qrfOneSimpleRow(&qrf); + } + break; + } + } + qrfResetStmt(&qrf); + qrfFinalize(&qrf); + return qrf.iErr; +} - b4 = ROL64((a30^d0), 18); - b0 = ROL64((a11^d1), 1); - b1 = ROL64((a42^d2), 6); - b2 = ROL64((a23^d3), 25); - b3 = ROL64((a04^d4), 8); - a30 = b0 ^((~b1)& b2 ); - a11 = b1 ^((~b2)& b3 ); - a42 = b2 ^((~b3)& b4 ); - a23 = b3 ^((~b4)& b0 ); - a04 = b4 ^((~b0)& b1 ); +/************************* End ext/qrf/qrf.c ********************/ - b1 = ROL64((a20^d0), 36); - b2 = ROL64((a01^d1), 10); - b3 = ROL64((a32^d2), 15); - b4 = ROL64((a13^d3), 56); - b0 = ROL64((a44^d4), 27); - a20 = b0 ^((~b1)& b2 ); - a01 = b1 ^((~b2)& b3 ); - a32 = b2 ^((~b3)& b4 ); - a13 = b3 ^((~b4)& b0 ); - a44 = b4 ^((~b0)& b1 ); +/* Use console I/O package as a direct INCLUDE. */ +#define SQLITE_INTERNAL_LINKAGE static - b3 = ROL64((a10^d0), 41); - b4 = ROL64((a41^d1), 2); - b0 = ROL64((a22^d2), 62); - b1 = ROL64((a03^d3), 55); - b2 = ROL64((a34^d4), 39); - a10 = b0 ^((~b1)& b2 ); - a41 = b1 ^((~b2)& b3 ); - a22 = b2 ^((~b3)& b4 ); - a03 = b3 ^((~b4)& b0 ); - a34 = b4 ^((~b0)& b1 ); +#ifdef SQLITE_SHELL_FIDDLE +/* Deselect most features from the console I/O package for Fiddle. */ +# define SQLITE_CIO_NO_REDIRECT +# define SQLITE_CIO_NO_CLASSIFY +# define SQLITE_CIO_NO_TRANSLATE +# define SQLITE_CIO_NO_SETMODE +# define SQLITE_CIO_NO_FLUSH +#endif - c0 = a00^a40^a30^a20^a10; - c1 = a31^a21^a11^a01^a41; - c2 = a12^a02^a42^a32^a22; - c3 = a43^a33^a23^a13^a03; - c4 = a24^a14^a04^a44^a34; - d0 = c4^ROL64(c1, 1); - d1 = c0^ROL64(c2, 1); - d2 = c1^ROL64(c3, 1); - d3 = c2^ROL64(c4, 1); - d4 = c3^ROL64(c0, 1); +/* +** The source code for several run-time loadable extensions is inserted +** below by the ../tool/mkshellc.tcl script. Before processing that included +** code, we need to override some macros to make the included program code +** work here in the middle of this regular program. +*/ +#define SQLITE_EXTENSION_INIT1 +#define SQLITE_EXTENSION_INIT2(X) (void)(X) - b0 = (a00^d0); - b1 = ROL64((a21^d1), 44); - b2 = ROL64((a42^d2), 43); - b3 = ROL64((a13^d3), 21); - b4 = ROL64((a34^d4), 14); - a00 = b0 ^((~b1)& b2 ); - a00 ^= RC[i+2]; - a21 = b1 ^((~b2)& b3 ); - a42 = b2 ^((~b3)& b4 ); - a13 = b3 ^((~b4)& b0 ); - a34 = b4 ^((~b0)& b1 ); +/************************* Begin ext/misc/windirent.h ******************/ +/* +** 2025-06-05 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** An implementation of opendir(), readdir(), and closedir() for Windows, +** based on the FindFirstFile(), FindNextFile(), and FindClose() APIs +** of Win32. +** +** #include this file inside any C-code module that needs to use +** opendir()/readdir()/closedir(). This file is a no-op on non-Windows +** machines. On Windows, static functions are defined that implement +** those standard interfaces. +*/ +#if defined(_WIN32) && defined(_MSC_VER) && !defined(SQLITE_WINDIRENT_H) +#define SQLITE_WINDIRENT_H - b2 = ROL64((a30^d0), 3); - b3 = ROL64((a01^d1), 45); - b4 = ROL64((a22^d2), 61); - b0 = ROL64((a43^d3), 28); - b1 = ROL64((a14^d4), 20); - a30 = b0 ^((~b1)& b2 ); - a01 = b1 ^((~b2)& b3 ); - a22 = b2 ^((~b3)& b4 ); - a43 = b3 ^((~b4)& b0 ); - a14 = b4 ^((~b0)& b1 ); +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef FILENAME_MAX +# define FILENAME_MAX (260) +#endif +#ifndef S_ISREG +#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) +#endif +#ifndef S_ISDIR +#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) +#endif +#ifndef S_ISLNK +#define S_ISLNK(m) (0) +#endif +typedef unsigned short mode_t; - b4 = ROL64((a10^d0), 18); - b0 = ROL64((a31^d1), 1); - b1 = ROL64((a02^d2), 6); - b2 = ROL64((a23^d3), 25); - b3 = ROL64((a44^d4), 8); - a10 = b0 ^((~b1)& b2 ); - a31 = b1 ^((~b2)& b3 ); - a02 = b2 ^((~b3)& b4 ); - a23 = b3 ^((~b4)& b0 ); - a44 = b4 ^((~b0)& b1 ); +/* The dirent object for Windows is abbreviated. The only field really +** usable by applications is d_name[]. +*/ +struct dirent { + int d_ino; /* Inode number (synthesized) */ + unsigned d_attributes; /* File attributes */ + char d_name[FILENAME_MAX]; /* Null-terminated filename */ +}; - b1 = ROL64((a40^d0), 36); - b2 = ROL64((a11^d1), 10); - b3 = ROL64((a32^d2), 15); - b4 = ROL64((a03^d3), 56); - b0 = ROL64((a24^d4), 27); - a40 = b0 ^((~b1)& b2 ); - a11 = b1 ^((~b2)& b3 ); - a32 = b2 ^((~b3)& b4 ); - a03 = b3 ^((~b4)& b0 ); - a24 = b4 ^((~b0)& b1 ); - - b3 = ROL64((a20^d0), 41); - b4 = ROL64((a41^d1), 2); - b0 = ROL64((a12^d2), 62); - b1 = ROL64((a33^d3), 55); - b2 = ROL64((a04^d4), 39); - a20 = b0 ^((~b1)& b2 ); - a41 = b1 ^((~b2)& b3 ); - a12 = b2 ^((~b3)& b4 ); - a33 = b3 ^((~b4)& b0 ); - a04 = b4 ^((~b0)& b1 ); - - c0 = a00^a30^a10^a40^a20; - c1 = a21^a01^a31^a11^a41; - c2 = a42^a22^a02^a32^a12; - c3 = a13^a43^a23^a03^a33; - c4 = a34^a14^a44^a24^a04; - d0 = c4^ROL64(c1, 1); - d1 = c0^ROL64(c2, 1); - d2 = c1^ROL64(c3, 1); - d3 = c2^ROL64(c4, 1); - d4 = c3^ROL64(c0, 1); - - b0 = (a00^d0); - b1 = ROL64((a01^d1), 44); - b2 = ROL64((a02^d2), 43); - b3 = ROL64((a03^d3), 21); - b4 = ROL64((a04^d4), 14); - a00 = b0 ^((~b1)& b2 ); - a00 ^= RC[i+3]; - a01 = b1 ^((~b2)& b3 ); - a02 = b2 ^((~b3)& b4 ); - a03 = b3 ^((~b4)& b0 ); - a04 = b4 ^((~b0)& b1 ); - - b2 = ROL64((a10^d0), 3); - b3 = ROL64((a11^d1), 45); - b4 = ROL64((a12^d2), 61); - b0 = ROL64((a13^d3), 28); - b1 = ROL64((a14^d4), 20); - a10 = b0 ^((~b1)& b2 ); - a11 = b1 ^((~b2)& b3 ); - a12 = b2 ^((~b3)& b4 ); - a13 = b3 ^((~b4)& b0 ); - a14 = b4 ^((~b0)& b1 ); - - b4 = ROL64((a20^d0), 18); - b0 = ROL64((a21^d1), 1); - b1 = ROL64((a22^d2), 6); - b2 = ROL64((a23^d3), 25); - b3 = ROL64((a24^d4), 8); - a20 = b0 ^((~b1)& b2 ); - a21 = b1 ^((~b2)& b3 ); - a22 = b2 ^((~b3)& b4 ); - a23 = b3 ^((~b4)& b0 ); - a24 = b4 ^((~b0)& b1 ); - - b1 = ROL64((a30^d0), 36); - b2 = ROL64((a31^d1), 10); - b3 = ROL64((a32^d2), 15); - b4 = ROL64((a33^d3), 56); - b0 = ROL64((a34^d4), 27); - a30 = b0 ^((~b1)& b2 ); - a31 = b1 ^((~b2)& b3 ); - a32 = b2 ^((~b3)& b4 ); - a33 = b3 ^((~b4)& b0 ); - a34 = b4 ^((~b0)& b1 ); +/* The internals of DIR are opaque according to standards. So it +** does not matter what we put here. */ +typedef struct DIR DIR; +struct DIR { + intptr_t d_handle; /* Handle for findfirst()/findnext() */ + struct dirent cur; /* Current entry */ +}; - b3 = ROL64((a40^d0), 41); - b4 = ROL64((a41^d1), 2); - b0 = ROL64((a42^d2), 62); - b1 = ROL64((a43^d3), 55); - b2 = ROL64((a44^d4), 39); - a40 = b0 ^((~b1)& b2 ); - a41 = b1 ^((~b2)& b3 ); - a42 = b2 ^((~b3)& b4 ); - a43 = b3 ^((~b4)& b0 ); - a44 = b4 ^((~b0)& b1 ); - } -} +/* Ignore hidden and system files */ +#define WindowsFileToIgnore(a) \ + ((((a).attrib)&_A_HIDDEN) || (((a).attrib)&_A_SYSTEM)) /* -** Initialize a new hash. iSize determines the size of the hash -** in bits and should be one of 224, 256, 384, or 512. Or iSize -** can be zero to use the default hash size of 256 bits. +** Close a previously opened directory */ -static void SHA3Init(SHA3Context *p, int iSize){ - memset(p, 0, sizeof(*p)); - if( iSize>=128 && iSize<=512 ){ - p->nRate = (1600 - ((iSize + 31)&~31)*2)/8; - }else{ - p->nRate = (1600 - 2*256)/8; +static int closedir(DIR *pDir){ + int rc = 0; + if( pDir==0 ){ + return EINVAL; } -#if SHA3_BYTEORDER==1234 - /* Known to be little-endian at compile-time. No-op */ -#elif SHA3_BYTEORDER==4321 - p->ixMask = 7; /* Big-endian */ -#else - { - static unsigned int one = 1; - if( 1==*(unsigned char*)&one ){ - /* Little endian. No byte swapping. */ - p->ixMask = 0; - }else{ - /* Big endian. Byte swap. */ - p->ixMask = 7; - } + if( pDir->d_handle!=0 && pDir->d_handle!=(-1) ){ + rc = _findclose(pDir->d_handle); } -#endif + sqlite3_free(pDir); + return rc; } /* -** Make consecutive calls to the SHA3Update function to add new content -** to the hash +** Open a new directory. The directory name should be UTF-8 encoded. +** appropriate translations happen automatically. */ -static void SHA3Update( - SHA3Context *p, - const unsigned char *aData, - unsigned int nData -){ - unsigned int i = 0; - if( aData==0 ) return; -#if SHA3_BYTEORDER==1234 - if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ - for(; i+7u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; - p->nLoaded += 8; - if( p->nLoaded>=p->nRate ){ - KeccakF1600Step(p); - p->nLoaded = 0; - } - } +static DIR *opendir(const char *zDirName){ + DIR *pDir; + wchar_t *b1; + sqlite3_int64 sz; + struct _wfinddata_t data; + + pDir = sqlite3_malloc64( sizeof(DIR) ); + if( pDir==0 ) return 0; + memset(pDir, 0, sizeof(DIR)); + memset(&data, 0, sizeof(data)); + sz = strlen(zDirName); + b1 = sqlite3_malloc64( (sz+3)*sizeof(b1[0]) ); + if( b1==0 ){ + closedir(pDir); + return NULL; } -#endif - for(; iu.x[p->nLoaded] ^= aData[i]; -#elif SHA3_BYTEORDER==4321 - p->u.x[p->nLoaded^0x07] ^= aData[i]; -#else - p->u.x[p->nLoaded^p->ixMask] ^= aData[i]; -#endif - p->nLoaded++; - if( p->nLoaded==p->nRate ){ - KeccakF1600Step(p); - p->nLoaded = 0; - } + sz = MultiByteToWideChar(CP_UTF8, 0, zDirName, sz, b1, sz); + b1[sz++] = '\\'; + b1[sz++] = '*'; + b1[sz] = 0; + if( sz+1>sizeof(data.name)/sizeof(data.name[0]) ){ + closedir(pDir); + sqlite3_free(b1); + return NULL; } -} - -/* -** After all content has been added, invoke SHA3Final() to compute -** the final hash. The function returns a pointer to the binary -** hash value. -*/ -static unsigned char *SHA3Final(SHA3Context *p){ - unsigned int i; - if( p->nLoaded==p->nRate-1 ){ - const unsigned char c1 = 0x86; - SHA3Update(p, &c1, 1); - }else{ - const unsigned char c2 = 0x06; - const unsigned char c3 = 0x80; - SHA3Update(p, &c2, 1); - p->nLoaded = p->nRate - 1; - SHA3Update(p, &c3, 1); + memcpy(data.name, b1, (sz+1)*sizeof(b1[0])); + sqlite3_free(b1); + pDir->d_handle = _wfindfirst(data.name, &data); + if( pDir->d_handle<0 ){ + closedir(pDir); + return NULL; } - for(i=0; inRate; i++){ - p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + while( WindowsFileToIgnore(data) ){ + memset(&data, 0, sizeof(data)); + if( _wfindnext(pDir->d_handle, &data)==-1 ){ + closedir(pDir); + return NULL; + } } - return &p->u.x[p->nRate]; + pDir->cur.d_ino = 0; + pDir->cur.d_attributes = data.attrib; + WideCharToMultiByte(CP_UTF8, 0, data.name, -1, + pDir->cur.d_name, FILENAME_MAX, 0, 0); + return pDir; } -/* End of the hashing logic -*****************************************************************************/ /* -** Implementation of the sha3(X,SIZE) function. +** Read the next entry from a directory. ** -** Return a BLOB which is the SIZE-bit SHA3 hash of X. The default -** size is 256. If X is a BLOB, it is hashed as is. -** For all other non-NULL types of input, X is converted into a UTF-8 string -** and the string is hashed without the trailing 0x00 terminator. The hash -** of a NULL value is NULL. +** The returned struct-dirent object is managed by DIR. It is only +** valid until the next readdir() or closedir() call. Only the +** d_name[] field is meaningful. The d_name[] value has been +** translated into UTF8. */ -static void sha3Func( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - SHA3Context cx; - int eType = sqlite3_value_type(argv[0]); - int nByte = sqlite3_value_bytes(argv[0]); - int iSize; - if( argc==1 ){ - iSize = 256; - }else{ - iSize = sqlite3_value_int(argv[1]); - if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ - sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " - "384 512", -1); - return; - } - } - if( eType==SQLITE_NULL ) return; - SHA3Init(&cx, iSize); - if( eType==SQLITE_BLOB ){ - SHA3Update(&cx, sqlite3_value_blob(argv[0]), nByte); - }else{ - SHA3Update(&cx, sqlite3_value_text(argv[0]), nByte); +static struct dirent *readdir(DIR *pDir){ + struct _wfinddata_t data; + if( pDir==0 ) return 0; + if( (pDir->cur.d_ino++)==0 ){ + return &pDir->cur; } - sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); + do{ + memset(&data, 0, sizeof(data)); + if( _wfindnext(pDir->d_handle, &data)==-1 ){ + return NULL; + } + }while( WindowsFileToIgnore(data) ); + pDir->cur.d_attributes = data.attrib; + WideCharToMultiByte(CP_UTF8, 0, data.name, -1, + pDir->cur.d_name, FILENAME_MAX, 0, 0); + return &pDir->cur; } -/* Compute a string using sqlite3_vsnprintf() with a maximum length -** of 50 bytes and add it to the hash. -*/ -static void sha3_step_vformat( - SHA3Context *p, /* Add content to this context */ - const char *zFormat, - ... -){ - va_list ap; - int n; - char zBuf[50]; - va_start(ap, zFormat); - sqlite3_vsnprintf(sizeof(zBuf),zBuf,zFormat,ap); - va_end(ap); - n = (int)strlen(zBuf); - SHA3Update(p, (unsigned char*)zBuf, n); -} +#endif /* defined(_WIN32) && defined(_MSC_VER) */ +/************************* End ext/misc/windirent.h ********************/ +/************************* Begin ext/misc/memtrace.c ******************/ /* -** Implementation of the sha3_query(SQL,SIZE) function. -** -** This function compiles and runs the SQL statement(s) given in the -** argument. The results are hashed using a SIZE-bit SHA3. The default -** size is 256. +** 2019-01-21 ** -** The format of the byte stream that is hashed is summarized as follows: +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: ** -** S: -** R -** N -** I -** F -** B: -** T: +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. ** -** is the original SQL text for each statement run and is -** the size of that text. The SQL text is UTF-8. A single R character -** occurs before the start of each row. N means a NULL value. -** I mean an 8-byte little-endian integer . F is a floating point -** number with an 8-byte little-endian IEEE floating point value . -** B means blobs of bytes. T means text rendered as -** bytes of UTF-8. The and values are expressed as an ASCII -** text integers. +************************************************************************* ** -** For each SQL statement in the X input, there is one S segment. Each -** S segment is followed by zero or more R segments, one for each row in the -** result set. After each R, there are one or more N, I, F, B, or T segments, -** one for each column in the result set. Segments are concatentated directly -** with no delimiters of any kind. +** This file implements an extension that uses the SQLITE_CONFIG_MALLOC +** mechanism to add a tracing layer on top of SQLite. If this extension +** is registered prior to sqlite3_initialize(), it will cause all memory +** allocation activities to be logged on standard output, or to some other +** FILE specified by the initializer. +** +** This file needs to be compiled into the application that uses it. +** +** This extension is used to implement the --memtrace option of the +** command-line shell. */ -static void sha3QueryFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - sqlite3 *db = sqlite3_context_db_handle(context); - const char *zSql = (const char*)sqlite3_value_text(argv[0]); - sqlite3_stmt *pStmt = 0; - int nCol; /* Number of columns in the result set */ - int i; /* Loop counter */ - int rc; - int n; - const char *z; - SHA3Context cx; - int iSize; +#include +#include +#include - if( argc==1 ){ - iSize = 256; - }else{ - iSize = sqlite3_value_int(argv[1]); - if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ - sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " - "384 512", -1); - return; - } - } - if( zSql==0 ) return; - SHA3Init(&cx, iSize); - while( zSql[0] ){ - rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql); - if( rc ){ - char *zMsg = sqlite3_mprintf("error SQL statement [%s]: %s", - zSql, sqlite3_errmsg(db)); - sqlite3_finalize(pStmt); - sqlite3_result_error(context, zMsg, -1); - sqlite3_free(zMsg); - return; - } - if( !sqlite3_stmt_readonly(pStmt) ){ - char *zMsg = sqlite3_mprintf("non-query: [%s]", sqlite3_sql(pStmt)); - sqlite3_finalize(pStmt); - sqlite3_result_error(context, zMsg, -1); - sqlite3_free(zMsg); - return; - } - nCol = sqlite3_column_count(pStmt); - z = sqlite3_sql(pStmt); - if( z ){ - n = (int)strlen(z); - sha3_step_vformat(&cx,"S%d:",n); - SHA3Update(&cx,(unsigned char*)z,n); - } +/* The original memory allocation routines */ +static sqlite3_mem_methods memtraceBase; +static FILE *memtraceOut; - /* Compute a hash over the result of the query */ - while( SQLITE_ROW==sqlite3_step(pStmt) ){ - SHA3Update(&cx,(const unsigned char*)"R",1); - for(i=0; i=1; j--){ - x[j] = u & 0xff; - u >>= 8; - } - x[0] = 'I'; - SHA3Update(&cx, x, 9); - break; - } - case SQLITE_FLOAT: { - sqlite3_uint64 u; - int j; - unsigned char x[9]; - double r = sqlite3_column_double(pStmt,i); - memcpy(&u, &r, 8); - for(j=8; j>=1; j--){ - x[j] = u & 0xff; - u >>= 8; - } - x[0] = 'F'; - SHA3Update(&cx,x,9); - break; - } - case SQLITE_TEXT: { - int n2 = sqlite3_column_bytes(pStmt, i); - const unsigned char *z2 = sqlite3_column_text(pStmt, i); - sha3_step_vformat(&cx,"T%d:",n2); - SHA3Update(&cx, z2, n2); - break; - } - case SQLITE_BLOB: { - int n2 = sqlite3_column_bytes(pStmt, i); - const unsigned char *z2 = sqlite3_column_blob(pStmt, i); - sha3_step_vformat(&cx,"B%d:",n2); - SHA3Update(&cx, z2, n2); - break; - } - } - } - } - sqlite3_finalize(pStmt); +/* Methods that trace memory allocations */ +static void *memtraceMalloc(int n){ + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: allocate %d bytes\n", + memtraceBase.xRoundup(n)); } - sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); + return memtraceBase.xMalloc(n); +} +static void memtraceFree(void *p){ + if( p==0 ) return; + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: free %d bytes\n", memtraceBase.xSize(p)); + } + memtraceBase.xFree(p); +} +static void *memtraceRealloc(void *p, int n){ + if( p==0 ) return memtraceMalloc(n); + if( n==0 ){ + memtraceFree(p); + return 0; + } + if( memtraceOut ){ + fprintf(memtraceOut, "MEMTRACE: resize %d -> %d bytes\n", + memtraceBase.xSize(p), memtraceBase.xRoundup(n)); + } + return memtraceBase.xRealloc(p, n); +} +static int memtraceSize(void *p){ + return memtraceBase.xSize(p); +} +static int memtraceRoundup(int n){ + return memtraceBase.xRoundup(n); +} +static int memtraceInit(void *p){ + return memtraceBase.xInit(p); +} +static void memtraceShutdown(void *p){ + memtraceBase.xShutdown(p); } +/* The substitute memory allocator */ +static sqlite3_mem_methods ersaztMethods = { + memtraceMalloc, + memtraceFree, + memtraceRealloc, + memtraceSize, + memtraceRoundup, + memtraceInit, + memtraceShutdown, + 0 +}; -#ifdef _WIN32 - -#endif -int sqlite3_shathree_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ +/* Begin tracing memory allocations to out. */ +int sqlite3MemTraceActivate(FILE *out){ int rc = SQLITE_OK; - SQLITE_EXTENSION_INIT2(pApi); - (void)pzErrMsg; /* Unused parameter */ - rc = sqlite3_create_function(db, "sha3", 1, - SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, - 0, sha3Func, 0, 0); - if( rc==SQLITE_OK ){ - rc = sqlite3_create_function(db, "sha3", 2, - SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, - 0, sha3Func, 0, 0); - } - if( rc==SQLITE_OK ){ - rc = sqlite3_create_function(db, "sha3_query", 1, - SQLITE_UTF8 | SQLITE_DIRECTONLY, - 0, sha3QueryFunc, 0, 0); + if( memtraceBase.xMalloc==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETMALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &ersaztMethods); + } } - if( rc==SQLITE_OK ){ - rc = sqlite3_create_function(db, "sha3_query", 2, - SQLITE_UTF8 | SQLITE_DIRECTONLY, - 0, sha3QueryFunc, 0, 0); + memtraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3MemTraceDeactivate(void){ + int rc = SQLITE_OK; + if( memtraceBase.xMalloc!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_MALLOC, &memtraceBase); + if( rc==SQLITE_OK ){ + memset(&memtraceBase, 0, sizeof(memtraceBase)); + } } + memtraceOut = 0; return rc; } -/************************* End ../ext/misc/shathree.c ********************/ -/************************* Begin ../ext/misc/uint.c ******************/ +/************************* End ext/misc/memtrace.c ********************/ +/************************* Begin ext/misc/pcachetrace.c ******************/ /* -** 2020-04-14 +** 2023-06-21 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -2761,93 +4196,180 @@ int sqlite3_shathree_init( ** May you find forgiveness for yourself and forgive others. ** May you share freely, never taking more than you give. ** -****************************************************************************** -** -** This SQLite extension implements the UINT collating sequence. -** -** UINT works like BINARY for text, except that embedded strings -** of digits compare in numeric order. +************************************************************************* ** -** * Leading zeros are handled properly, in the sense that -** they do not mess of the maginitude comparison of embedded -** strings of digits. "x00123y" is equal to "x123y". +** This file implements an extension that uses the SQLITE_CONFIG_PCACHE2 +** mechanism to add a tracing layer on top of pluggable page cache of +** SQLite. If this extension is registered prior to sqlite3_initialize(), +** it will cause all page cache activities to be logged on standard output, +** or to some other FILE specified by the initializer. ** -** * Only unsigned integers are recognized. Plus and minus -** signs are ignored. Decimal points and exponential notation -** are ignored. +** This file needs to be compiled into the application that uses it. ** -** * Embedded integers can be of arbitrary length. Comparison -** is *not* limited integers that can be expressed as a -** 64-bit machine integer. +** This extension is used to implement the --pcachetrace option of the +** command-line shell. */ -/* #include "sqlite3ext.h" */ -SQLITE_EXTENSION_INIT1 #include #include -#include +#include -/* -** Compare text in lexicographic order, except strings of digits -** compare in numeric order. -*/ -static int uintCollFunc( - void *notUsed, - int nKey1, const void *pKey1, - int nKey2, const void *pKey2 -){ - const unsigned char *zA = (const unsigned char*)pKey1; - const unsigned char *zB = (const unsigned char*)pKey2; - int i=0, j=0, x; - (void)notUsed; - while( i %d\n", pArg, nRes); + } + return nRes; +} +static void pcachetraceShutdown(void *pArg){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xShutdown(%p)\n", pArg); + } + pcacheBase.xShutdown(pArg); +} +static sqlite3_pcache *pcachetraceCreate(int szPage, int szExtra, int bPurge){ + sqlite3_pcache *pRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d)\n", + szPage, szExtra, bPurge); + } + pRes = pcacheBase.xCreate(szPage, szExtra, bPurge); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCreate(%d,%d,%d) -> %p\n", + szPage, szExtra, bPurge, pRes); + } + return pRes; +} +static void pcachetraceCachesize(sqlite3_pcache *p, int nCachesize){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xCachesize(%p, %d)\n", p, nCachesize); + } + pcacheBase.xCachesize(p, nCachesize); +} +static int pcachetracePagecount(sqlite3_pcache *p){ + int nRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p)\n", p); + } + nRes = pcacheBase.xPagecount(p); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xPagecount(%p) -> %d\n", p, nRes); + } + return nRes; +} +static sqlite3_pcache_page *pcachetraceFetch( + sqlite3_pcache *p, + unsigned key, + int crFg +){ + sqlite3_pcache_page *pRes; + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d)\n", p, key, crFg); + } + pRes = pcacheBase.xFetch(p, key, crFg); + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xFetch(%p,%u,%d) -> %p\n", + p, key, crFg, pRes); + } + return pRes; +} +static void pcachetraceUnpin( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + int bDiscard +){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xUnpin(%p, %p, %d)\n", + p, pPg, bDiscard); + } + pcacheBase.xUnpin(p, pPg, bDiscard); +} +static void pcachetraceRekey( + sqlite3_pcache *p, + sqlite3_pcache_page *pPg, + unsigned oldKey, + unsigned newKey +){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xRekey(%p, %p, %u, %u)\n", + p, pPg, oldKey, newKey); + } + pcacheBase.xRekey(p, pPg, oldKey, newKey); +} +static void pcachetraceTruncate(sqlite3_pcache *p, unsigned n){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xTruncate(%p, %u)\n", p, n); + } + pcacheBase.xTruncate(p, n); +} +static void pcachetraceDestroy(sqlite3_pcache *p){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xDestroy(%p)\n", p); + } + pcacheBase.xDestroy(p); +} +static void pcachetraceShrink(sqlite3_pcache *p){ + if( pcachetraceOut ){ + fprintf(pcachetraceOut, "PCACHETRACE: xShrink(%p)\n", p); + } + pcacheBase.xShrink(p); } -#ifdef _WIN32 +/* The substitute pcache methods */ +static sqlite3_pcache_methods2 ersaztPcacheMethods = { + 0, + 0, + pcachetraceInit, + pcachetraceShutdown, + pcachetraceCreate, + pcachetraceCachesize, + pcachetracePagecount, + pcachetraceFetch, + pcachetraceUnpin, + pcachetraceRekey, + pcachetraceTruncate, + pcachetraceDestroy, + pcachetraceShrink +}; -#endif -int sqlite3_uint_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ - SQLITE_EXTENSION_INIT2(pApi); - (void)pzErrMsg; /* Unused parameter */ - return sqlite3_create_collation(db, "uint", SQLITE_UTF8, 0, uintCollFunc); +/* Begin tracing memory allocations to out. */ +int sqlite3PcacheTraceActivate(FILE *out){ + int rc = SQLITE_OK; + if( pcacheBase.xFetch==0 ){ + rc = sqlite3_config(SQLITE_CONFIG_GETPCACHE2, &pcacheBase); + if( rc==SQLITE_OK ){ + rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &ersaztPcacheMethods); + } + } + pcachetraceOut = out; + return rc; +} + +/* Deactivate memory tracing */ +int sqlite3PcacheTraceDeactivate(void){ + int rc = SQLITE_OK; + if( pcacheBase.xFetch!=0 ){ + rc = sqlite3_config(SQLITE_CONFIG_PCACHE2, &pcacheBase); + if( rc==SQLITE_OK ){ + memset(&pcacheBase, 0, sizeof(pcacheBase)); + } + } + pcachetraceOut = 0; + return rc; } -/************************* End ../ext/misc/uint.c ********************/ -/************************* Begin ../ext/misc/decimal.c ******************/ +/************************* End ext/misc/pcachetrace.c ********************/ +/************************* Begin ext/misc/shathree.c ******************/ /* -** 2020-06-22 +** 2017-03-08 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -2858,1183 +4380,3445 @@ int sqlite3_uint_init( ** ****************************************************************************** ** -** Routines to implement arbitrary-precision decimal math. +** This SQLite extension implements functions that compute SHA3 hashes +** in the way described by the (U.S.) NIST FIPS 202 SHA-3 Standard. +** Three SQL functions are implemented: ** -** The focus here is on simplicity and correctness, not performance. +** sha3(X,SIZE) +** sha3_agg(Y,SIZE) +** sha3_query(Z,SIZE) +** +** The sha3(X) function computes the SHA3 hash of the input X, or NULL if +** X is NULL. If inputs X is text, the UTF-8 rendering of that text is +** used to compute the hash. If X is a BLOB, then the binary data of the +** blob is used to compute the hash. If X is an integer or real number, +** then that number if converted into UTF-8 text and the hash is computed +** over the text. +** +** The sha3_agg(Y) function computes the SHA3 hash of all Y inputs. Since +** order is important for the hash, it is recommended that the Y expression +** by followed by an ORDER BY clause to guarantee that the inputs occur +** in the desired order. +** +** The sha3_query(Y) function evaluates all queries in the SQL statements of Y +** and returns a hash of their results. +** +** The SIZE argument is optional. If omitted, the SHA3-256 hash algorithm +** is used. If SIZE is included it must be one of the integers 224, 256, +** 384, or 512, to determine SHA3 hash variant that is computed. +** +** Because the sha3_agg() and sha3_query() functions compute a hash over +** multiple values, the values are encode to use include type information. +** +** In sha3_agg(), the sequence of bytes that gets hashed for each input +** Y depends on the datatype of Y: +** +** typeof(Y)='null' A single "N" is hashed. (One byte) +** +** typeof(Y)='integer' The data hash is the character "I" followed +** by an 8-byte big-endian binary of the +** 64-bit signed integer. (Nine bytes total.) +** +** typeof(Y)='real' The character "F" followed by an 8-byte +** big-ending binary of the double. (Nine +** bytes total.) +** +** typeof(Y)='text' The hash is over prefix "Tnnn:" followed +** by the UTF8 encoding of the text. The "nnn" +** in the prefix is the minimum-length decimal +** representation of the octet_length of the text. +** Notice the ":" at the end of the prefix, which +** is needed to separate the prefix from the +** content in cases where the content starts +** with a digit. +** +** typeof(Y)='blob' The hash is taken over prefix "Bnnn:" followed +** by the binary content of the blob. The "nnn" +** in the prefix is the minimum-length decimal +** representation of the byte-length of the blob. +** +** According to the rules above, all of the following SELECT statements +** should return TRUE: +** +** SELECT sha3(1) = sha3('1'); +** +** SELECT sha3('hello') = sha3(x'68656c6c6f'); +** +** WITH a(x) AS (VALUES('xyzzy')) +** SELECT sha3_agg(x) = sha3('T5:xyzzy') FROM a; +** +** WITH a(x) AS (VALUES(x'010203')) +** SELECT sha3_agg(x) = sha3(x'42333a010203') FROM a; +** +** WITH a(x) AS (VALUES(0x123456)) +** SELECT sha3_agg(x) = sha3(x'490000000000123456') FROM a; +** +** WITH a(x) AS (VALUES(100.015625)) +** SELECT sha3_agg(x) = sha3(x'464059010000000000') FROM a; +** +** WITH a(x) AS (VALUES(NULL)) +** SELECT sha3_agg(x) = sha3('N') FROM a; +** +** +** In sha3_query(), individual column values are encoded as with +** sha3_agg(), but with the addition that a single "R" character is +** inserted at the start of each row. +** +** Note that sha3_agg() hashes rows for which Y is NULL. Add a FILTER +** clause if NULL rows should be excluded: +** +** SELECT sha3_agg(x ORDER BY rowid) FILTER(WHERE x NOT NULL) FROM t1; */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 #include #include -#include -#include - -/* Mark a function parameter as unused, to suppress nuisance compiler -** warnings. */ -#ifndef UNUSED_PARAMETER -# define UNUSED_PARAMETER(X) (void)(X) -#endif - +#include -/* A decimal object */ -typedef struct Decimal Decimal; -struct Decimal { - char sign; /* 0 for positive, 1 for negative */ - char oom; /* True if an OOM is encountered */ - char isNull; /* True if holds a NULL rather than a number */ - char isInit; /* True upon initialization */ - int nDigit; /* Total number of digits */ - int nFrac; /* Number of digits to the right of the decimal point */ - signed char *a; /* Array of digits. Most significant first. */ -}; +#ifndef SQLITE_AMALGAMATION +/* typedef sqlite3_uint64 u64; */ +#endif /* SQLITE_AMALGAMATION */ -/* -** Release memory held by a Decimal, but do not free the object itself. +/****************************************************************************** +** The Hash Engine */ -static void decimal_clear(Decimal *p){ - sqlite3_free(p->a); -} - /* -** Destroy a Decimal object -*/ -static void decimal_free(Decimal *p){ - if( p ){ - decimal_clear(p); - sqlite3_free(p); - } -} - -/* -** Allocate a new Decimal object initialized to the text in zIn[]. -** Return NULL if any kind of error occurs. -*/ -static Decimal *decimalNewFromText(const char *zIn, int n){ - Decimal *p = 0; - int i; - int iExp = 0; - - p = sqlite3_malloc( sizeof(*p) ); - if( p==0 ) goto new_from_text_failed; - p->sign = 0; - p->oom = 0; - p->isInit = 1; - p->isNull = 0; - p->nDigit = 0; - p->nFrac = 0; - p->a = sqlite3_malloc64( n+1 ); - if( p->a==0 ) goto new_from_text_failed; - for(i=0; isspace(zIn[i]); i++){} - if( zIn[i]=='-' ){ - p->sign = 1; - i++; - }else if( zIn[i]=='+' ){ - i++; - } - while( i='0' && c<='9' ){ - p->a[p->nDigit++] = c - '0'; - }else if( c=='.' ){ - p->nFrac = p->nDigit + 1; - }else if( c=='e' || c=='E' ){ - int j = i+1; - int neg = 0; - if( j>=n ) break; - if( zIn[j]=='-' ){ - neg = 1; - j++; - }else if( zIn[j]=='+' ){ - j++; - } - while( j='0' && zIn[j]<='9' ){ - iExp = iExp*10 + zIn[j] - '0'; - } - j++; - } - if( neg ) iExp = -iExp; - break; - } - i++; - } - if( p->nFrac ){ - p->nFrac = p->nDigit - (p->nFrac - 1); - } - if( iExp>0 ){ - if( p->nFrac>0 ){ - if( iExp<=p->nFrac ){ - p->nFrac -= iExp; - iExp = 0; - }else{ - iExp -= p->nFrac; - p->nFrac = 0; - } - } - if( iExp>0 ){ - p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); - if( p->a==0 ) goto new_from_text_failed; - memset(p->a+p->nDigit, 0, iExp); - p->nDigit += iExp; - } - }else if( iExp<0 ){ - int nExtra; - iExp = -iExp; - nExtra = p->nDigit - p->nFrac - 1; - if( nExtra ){ - if( nExtra>=iExp ){ - p->nFrac += iExp; - iExp = 0; - }else{ - iExp -= nExtra; - p->nFrac = p->nDigit - 1; - } - } - if( iExp>0 ){ - p->a = sqlite3_realloc64(p->a, p->nDigit + iExp + 1 ); - if( p->a==0 ) goto new_from_text_failed; - memmove(p->a+iExp, p->a, p->nDigit); - memset(p->a, 0, iExp); - p->nDigit += iExp; - p->nFrac += iExp; - } - } - return p; - -new_from_text_failed: - if( p ){ - if( p->a ) sqlite3_free(p->a); - sqlite3_free(p); - } - return 0; -} - -/* Forward reference */ -static Decimal *decimalFromDouble(double); - -/* -** Allocate a new Decimal object from an sqlite3_value. Return a pointer -** to the new object, or NULL if there is an error. If the pCtx argument -** is not NULL, then errors are reported on it as well. +** Macros to determine whether the machine is big or little endian, +** and whether or not that determination is run-time or compile-time. ** -** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted -** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length -** 8 bytes, the resulting double value is expanded into its decimal equivalent. -** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length, -** then NULL is returned. +** For best performance, an attempt is made to guess at the byte-order +** using C-preprocessor macros. If that is unsuccessful, or if +** -DSHA3_BYTEORDER=0 is set, then byte-order is determined +** at run-time. */ -static Decimal *decimal_new( - sqlite3_context *pCtx, /* Report error here, if not null */ - sqlite3_value *pIn, /* Construct the decimal object from this */ - int bTextOnly /* Always interpret pIn as text if true */ -){ - Decimal *p = 0; - int eType = sqlite3_value_type(pIn); - if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){ - eType = SQLITE_TEXT; - } - switch( eType ){ - case SQLITE_TEXT: - case SQLITE_INTEGER: { - const char *zIn = (const char*)sqlite3_value_text(pIn); - int n = sqlite3_value_bytes(pIn); - p = decimalNewFromText(zIn, n); - if( p==0 ) goto new_failed; - break; - } - - case SQLITE_FLOAT: { - p = decimalFromDouble(sqlite3_value_double(pIn)); - break; - } - - case SQLITE_BLOB: { - const unsigned char *x; - unsigned int i; - sqlite3_uint64 v = 0; - double r; - - if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break; - x = sqlite3_value_blob(pIn); - for(i=0; ioom ){ - sqlite3_result_error_nomem(pCtx); - return; - } - if( p->isNull ){ - sqlite3_result_null(pCtx); - return; - } - z = sqlite3_malloc( p->nDigit+4 ); - if( z==0 ){ - sqlite3_result_error_nomem(pCtx); - return; - } - i = 0; - if( p->nDigit==0 || (p->nDigit==1 && p->a[0]==0) ){ - p->sign = 0; - } - if( p->sign ){ - z[0] = '-'; - i = 1; - } - n = p->nDigit - p->nFrac; - if( n<=0 ){ - z[i++] = '0'; - } - j = 0; - while( n>1 && p->a[j]==0 ){ - j++; - n--; - } - while( n>0 ){ - z[i++] = p->a[j] + '0'; - j++; - n--; - } - if( p->nFrac ){ - z[i++] = '.'; - do{ - z[i++] = p->a[j] + '0'; - j++; - }while( jnDigit ); - } - z[i] = 0; - sqlite3_result_text(pCtx, z, i, sqlite3_free); -} +typedef struct SHA3Context SHA3Context; +struct SHA3Context { + union { + u64 s[25]; /* Keccak state. 5x5 lines of 64 bits each */ + unsigned char x[1600]; /* ... or 1600 bytes */ + } u; + unsigned nRate; /* Bytes of input accepted per Keccak iteration */ + unsigned nLoaded; /* Input bytes loaded into u.x[] so far this cycle */ + unsigned ixMask; /* Insert next input into u.x[nLoaded^ixMask]. */ + unsigned iSize; /* 224, 256, 358, or 512 */ +}; /* -** Make the given Decimal the result in an format similar to '%+#e'. -** In other words, show exponential notation with leading and trailing -** zeros omitted. +** A single step of the Keccak mixing function for a 1600-bit state */ -static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p){ - char *z; /* The output buffer */ - int i; /* Loop counter */ - int nZero; /* Number of leading zeros */ - int nDigit; /* Number of digits not counting trailing zeros */ - int nFrac; /* Digits to the right of the decimal point */ - int exp; /* Exponent value */ - signed char zero; /* Zero value */ +static void KeccakF1600Step(SHA3Context *p){ + int i; + u64 b0, b1, b2, b3, b4; + u64 c0, c1, c2, c3, c4; + u64 d0, d1, d2, d3, d4; + static const u64 RC[] = { + 0x0000000000000001ULL, 0x0000000000008082ULL, + 0x800000000000808aULL, 0x8000000080008000ULL, + 0x000000000000808bULL, 0x0000000080000001ULL, + 0x8000000080008081ULL, 0x8000000000008009ULL, + 0x000000000000008aULL, 0x0000000000000088ULL, + 0x0000000080008009ULL, 0x000000008000000aULL, + 0x000000008000808bULL, 0x800000000000008bULL, + 0x8000000000008089ULL, 0x8000000000008003ULL, + 0x8000000000008002ULL, 0x8000000000000080ULL, + 0x000000000000800aULL, 0x800000008000000aULL, + 0x8000000080008081ULL, 0x8000000000008080ULL, + 0x0000000080000001ULL, 0x8000000080008008ULL + }; +# define a00 (p->u.s[0]) +# define a01 (p->u.s[1]) +# define a02 (p->u.s[2]) +# define a03 (p->u.s[3]) +# define a04 (p->u.s[4]) +# define a10 (p->u.s[5]) +# define a11 (p->u.s[6]) +# define a12 (p->u.s[7]) +# define a13 (p->u.s[8]) +# define a14 (p->u.s[9]) +# define a20 (p->u.s[10]) +# define a21 (p->u.s[11]) +# define a22 (p->u.s[12]) +# define a23 (p->u.s[13]) +# define a24 (p->u.s[14]) +# define a30 (p->u.s[15]) +# define a31 (p->u.s[16]) +# define a32 (p->u.s[17]) +# define a33 (p->u.s[18]) +# define a34 (p->u.s[19]) +# define a40 (p->u.s[20]) +# define a41 (p->u.s[21]) +# define a42 (p->u.s[22]) +# define a43 (p->u.s[23]) +# define a44 (p->u.s[24]) +# define ROL64(a,x) ((a<>(64-x))) + + for(i=0; i<24; i+=4){ + c0 = a00^a10^a20^a30^a40; + c1 = a01^a11^a21^a31^a41; + c2 = a02^a12^a22^a32^a42; + c3 = a03^a13^a23^a33^a43; + c4 = a04^a14^a24^a34^a44; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a11^d1), 44); + b2 = ROL64((a22^d2), 43); + b3 = ROL64((a33^d3), 21); + b4 = ROL64((a44^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i]; + a11 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a20^d0), 3); + b3 = ROL64((a31^d1), 45); + b4 = ROL64((a42^d2), 61); + b0 = ROL64((a03^d3), 28); + b1 = ROL64((a14^d4), 20); + a20 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a40^d0), 18); + b0 = ROL64((a01^d1), 1); + b1 = ROL64((a12^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a34^d4), 8); + a40 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a10^d0), 36); + b2 = ROL64((a21^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a43^d3), 56); + b0 = ROL64((a04^d4), 27); + a10 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a30^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a02^d2), 62); + b1 = ROL64((a13^d3), 55); + b2 = ROL64((a24^d4), 39); + a30 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + c0 = a00^a20^a40^a10^a30; + c1 = a11^a31^a01^a21^a41; + c2 = a22^a42^a12^a32^a02; + c3 = a33^a03^a23^a43^a13; + c4 = a44^a14^a34^a04^a24; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a31^d1), 44); + b2 = ROL64((a12^d2), 43); + b3 = ROL64((a43^d3), 21); + b4 = ROL64((a24^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+1]; + a31 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a40^d0), 3); + b3 = ROL64((a21^d1), 45); + b4 = ROL64((a02^d2), 61); + b0 = ROL64((a33^d3), 28); + b1 = ROL64((a14^d4), 20); + a40 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a30^d0), 18); + b0 = ROL64((a11^d1), 1); + b1 = ROL64((a42^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a04^d4), 8); + a30 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a20^d0), 36); + b2 = ROL64((a01^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a13^d3), 56); + b0 = ROL64((a44^d4), 27); + a20 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a10^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a22^d2), 62); + b1 = ROL64((a03^d3), 55); + b2 = ROL64((a34^d4), 39); + a10 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + c0 = a00^a40^a30^a20^a10; + c1 = a31^a21^a11^a01^a41; + c2 = a12^a02^a42^a32^a22; + c3 = a43^a33^a23^a13^a03; + c4 = a24^a14^a04^a44^a34; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a21^d1), 44); + b2 = ROL64((a42^d2), 43); + b3 = ROL64((a13^d3), 21); + b4 = ROL64((a34^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+2]; + a21 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a30^d0), 3); + b3 = ROL64((a01^d1), 45); + b4 = ROL64((a22^d2), 61); + b0 = ROL64((a43^d3), 28); + b1 = ROL64((a14^d4), 20); + a30 = b0 ^((~b1)& b2 ); + a01 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a10^d0), 18); + b0 = ROL64((a31^d1), 1); + b1 = ROL64((a02^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a44^d4), 8); + a10 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a40^d0), 36); + b2 = ROL64((a11^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a03^d3), 56); + b0 = ROL64((a24^d4), 27); + a40 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a20^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a12^d2), 62); + b1 = ROL64((a33^d3), 55); + b2 = ROL64((a04^d4), 39); + a20 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + c0 = a00^a30^a10^a40^a20; + c1 = a21^a01^a31^a11^a41; + c2 = a42^a22^a02^a32^a12; + c3 = a13^a43^a23^a03^a33; + c4 = a34^a14^a44^a24^a04; + d0 = c4^ROL64(c1, 1); + d1 = c0^ROL64(c2, 1); + d2 = c1^ROL64(c3, 1); + d3 = c2^ROL64(c4, 1); + d4 = c3^ROL64(c0, 1); + + b0 = (a00^d0); + b1 = ROL64((a01^d1), 44); + b2 = ROL64((a02^d2), 43); + b3 = ROL64((a03^d3), 21); + b4 = ROL64((a04^d4), 14); + a00 = b0 ^((~b1)& b2 ); + a00 ^= RC[i+3]; + a01 = b1 ^((~b2)& b3 ); + a02 = b2 ^((~b3)& b4 ); + a03 = b3 ^((~b4)& b0 ); + a04 = b4 ^((~b0)& b1 ); + + b2 = ROL64((a10^d0), 3); + b3 = ROL64((a11^d1), 45); + b4 = ROL64((a12^d2), 61); + b0 = ROL64((a13^d3), 28); + b1 = ROL64((a14^d4), 20); + a10 = b0 ^((~b1)& b2 ); + a11 = b1 ^((~b2)& b3 ); + a12 = b2 ^((~b3)& b4 ); + a13 = b3 ^((~b4)& b0 ); + a14 = b4 ^((~b0)& b1 ); + + b4 = ROL64((a20^d0), 18); + b0 = ROL64((a21^d1), 1); + b1 = ROL64((a22^d2), 6); + b2 = ROL64((a23^d3), 25); + b3 = ROL64((a24^d4), 8); + a20 = b0 ^((~b1)& b2 ); + a21 = b1 ^((~b2)& b3 ); + a22 = b2 ^((~b3)& b4 ); + a23 = b3 ^((~b4)& b0 ); + a24 = b4 ^((~b0)& b1 ); + + b1 = ROL64((a30^d0), 36); + b2 = ROL64((a31^d1), 10); + b3 = ROL64((a32^d2), 15); + b4 = ROL64((a33^d3), 56); + b0 = ROL64((a34^d4), 27); + a30 = b0 ^((~b1)& b2 ); + a31 = b1 ^((~b2)& b3 ); + a32 = b2 ^((~b3)& b4 ); + a33 = b3 ^((~b4)& b0 ); + a34 = b4 ^((~b0)& b1 ); + + b3 = ROL64((a40^d0), 41); + b4 = ROL64((a41^d1), 2); + b0 = ROL64((a42^d2), 62); + b1 = ROL64((a43^d3), 55); + b2 = ROL64((a44^d4), 39); + a40 = b0 ^((~b1)& b2 ); + a41 = b1 ^((~b2)& b3 ); + a42 = b2 ^((~b3)& b4 ); + a43 = b3 ^((~b4)& b0 ); + a44 = b4 ^((~b0)& b1 ); + } +} + +/* +** Initialize a new hash. iSize determines the size of the hash +** in bits and should be one of 224, 256, 384, or 512. Or iSize +** can be zero to use the default hash size of 256 bits. +*/ +static void SHA3Init(SHA3Context *p, int iSize){ + memset(p, 0, sizeof(*p)); + p->iSize = iSize; + if( iSize>=128 && iSize<=512 ){ + p->nRate = (1600 - ((iSize + 31)&~31)*2)/8; + }else{ + p->nRate = (1600 - 2*256)/8; + } +#if SHA3_BYTEORDER==1234 + /* Known to be little-endian at compile-time. No-op */ +#elif SHA3_BYTEORDER==4321 + p->ixMask = 7; /* Big-endian */ +#else + { + static unsigned int one = 1; + if( 1==*(unsigned char*)&one ){ + /* Little endian. No byte swapping. */ + p->ixMask = 0; + }else{ + /* Big endian. Byte swap. */ + p->ixMask = 7; + } + } +#endif +} + +/* +** Make consecutive calls to the SHA3Update function to add new content +** to the hash +*/ +static void SHA3Update( + SHA3Context *p, + const unsigned char *aData, + unsigned int nData +){ + unsigned int i = 0; + if( aData==0 ) return; +#if SHA3_BYTEORDER==1234 + if( (p->nLoaded % 8)==0 && ((aData - (const unsigned char*)0)&7)==0 ){ + for(; i+7u.s[p->nLoaded/8] ^= *(u64*)&aData[i]; + p->nLoaded += 8; + if( p->nLoaded>=p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } + } +#endif + for(; iu.x[p->nLoaded] ^= aData[i]; +#elif SHA3_BYTEORDER==4321 + p->u.x[p->nLoaded^0x07] ^= aData[i]; +#else + p->u.x[p->nLoaded^p->ixMask] ^= aData[i]; +#endif + p->nLoaded++; + if( p->nLoaded==p->nRate ){ + KeccakF1600Step(p); + p->nLoaded = 0; + } + } +} + +/* +** After all content has been added, invoke SHA3Final() to compute +** the final hash. The function returns a pointer to the binary +** hash value. +*/ +static unsigned char *SHA3Final(SHA3Context *p){ + unsigned int i; + if( p->nLoaded==p->nRate-1 ){ + const unsigned char c1 = 0x86; + SHA3Update(p, &c1, 1); + }else{ + const unsigned char c2 = 0x06; + const unsigned char c3 = 0x80; + SHA3Update(p, &c2, 1); + p->nLoaded = p->nRate - 1; + SHA3Update(p, &c3, 1); + } + for(i=0; inRate; i++){ + p->u.x[i+p->nRate] = p->u.x[i^p->ixMask]; + } + return &p->u.x[p->nRate]; +} +/* End of the hashing logic +*****************************************************************************/ + +/* +** Implementation of the sha3(X,SIZE) function. +** +** Return a BLOB which is the SIZE-bit SHA3 hash of X. The default +** size is 256. If X is a BLOB, it is hashed as is. +** For all other non-NULL types of input, X is converted into a UTF-8 string +** and the string is hashed without the trailing 0x00 terminator. The hash +** of a NULL value is NULL. +*/ +static void sha3Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SHA3Context cx; + int eType = sqlite3_value_type(argv[0]); + int nByte = sqlite3_value_bytes(argv[0]); + int iSize; + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( eType==SQLITE_NULL ) return; + SHA3Init(&cx, iSize); + if( eType==SQLITE_BLOB ){ + SHA3Update(&cx, sqlite3_value_blob(argv[0]), nByte); + }else{ + SHA3Update(&cx, sqlite3_value_text(argv[0]), nByte); + } + sqlite3_result_blob(context, SHA3Final(&cx), iSize/8, SQLITE_TRANSIENT); +} + +/* Compute a string using sqlite3_vsnprintf() with a maximum length +** of 50 bytes and add it to the hash. +*/ +static void sha3_step_vformat( + SHA3Context *p, /* Add content to this context */ + const char *zFormat, + ... +){ + va_list ap; + int n; + char zBuf[50]; + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(zBuf),zBuf,zFormat,ap); + va_end(ap); + n = (int)strlen(zBuf); + SHA3Update(p, (unsigned char*)zBuf, n); +} + +/* +** Update a SHA3Context using a single sqlite3_value. +*/ +static void sha3UpdateFromValue(SHA3Context *p, sqlite3_value *pVal){ + switch( sqlite3_value_type(pVal) ){ + case SQLITE_NULL: { + SHA3Update(p, (const unsigned char*)"N",1); + break; + } + case SQLITE_INTEGER: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + sqlite3_int64 v = sqlite3_value_int64(pVal); + memcpy(&u, &v, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'I'; + SHA3Update(p, x, 9); + break; + } + case SQLITE_FLOAT: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + double r = sqlite3_value_double(pVal); + memcpy(&u, &r, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'F'; + SHA3Update(p,x,9); + break; + } + case SQLITE_TEXT: { + int n2 = sqlite3_value_bytes(pVal); + const unsigned char *z2 = sqlite3_value_text(pVal); + sha3_step_vformat(p,"T%d:",n2); + SHA3Update(p, z2, n2); + break; + } + case SQLITE_BLOB: { + int n2 = sqlite3_value_bytes(pVal); + const unsigned char *z2 = sqlite3_value_blob(pVal); + sha3_step_vformat(p,"B%d:",n2); + SHA3Update(p, z2, n2); + break; + } + } +} + +/* +** Implementation of the sha3_query(SQL,SIZE) function. +** +** This function compiles and runs the SQL statement(s) given in the +** argument. The results are hashed using a SIZE-bit SHA3. The default +** size is 256. +** +** The format of the byte stream that is hashed is summarized as follows: +** +** S: +** R +** N +** I +** F +** B: +** T: +** +** is the original SQL text for each statement run and is +** the size of that text. The SQL text is UTF-8. A single R character +** occurs before the start of each row. N means a NULL value. +** I mean an 8-byte little-endian integer . F is a floating point +** number with an 8-byte little-endian IEEE floating point value . +** B means blobs of bytes. T means text rendered as +** bytes of UTF-8. The and values are expressed as an ASCII +** text integers. +** +** For each SQL statement in the X input, there is one S segment. Each +** S segment is followed by zero or more R segments, one for each row in the +** result set. After each R, there are one or more N, I, F, B, or T segments, +** one for each column in the result set. Segments are concatentated directly +** with no delimiters of any kind. +*/ +static void sha3QueryFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + sqlite3_stmt *pStmt = 0; + int nCol; /* Number of columns in the result set */ + int i; /* Loop counter */ + int rc; + int n; + const char *z; + SHA3Context cx; + int iSize; + + if( argc==1 ){ + iSize = 256; + }else{ + iSize = sqlite3_value_int(argv[1]); + if( iSize!=224 && iSize!=256 && iSize!=384 && iSize!=512 ){ + sqlite3_result_error(context, "SHA3 size should be one of: 224 256 " + "384 512", -1); + return; + } + } + if( zSql==0 ) return; + SHA3Init(&cx, iSize); + while( zSql[0] ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql); + if( rc ){ + char *zMsg = sqlite3_mprintf("error SQL statement [%s]: %s", + zSql, sqlite3_errmsg(db)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + if( !sqlite3_stmt_readonly(pStmt) ){ + char *zMsg = sqlite3_mprintf("non-query: [%s]", sqlite3_sql(pStmt)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + nCol = sqlite3_column_count(pStmt); + z = sqlite3_sql(pStmt); + if( z ){ + n = (int)strlen(z); + sha3_step_vformat(&cx,"S%d:",n); + SHA3Update(&cx,(unsigned char*)z,n); + } + + /* Compute a hash over the result of the query */ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + SHA3Update(&cx,(const unsigned char*)"R",1); + for(i=0; inRate==0 ){ + int sz = 256; + if( argc==2 ){ + sz = sqlite3_value_int(argv[1]); + if( sz!=224 && sz!=384 && sz!=512 ){ + sz = 256; + } + } + SHA3Init(p, sz); + } + sha3UpdateFromValue(p, argv[0]); +} + + +/* +** xFinal function for sha3_agg(). +*/ +static void sha3AggFinal(sqlite3_context *context){ + SHA3Context *p; + p = (SHA3Context*)sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( p->iSize ){ + sqlite3_result_blob(context, SHA3Final(p), p->iSize/8, SQLITE_TRANSIENT); + } +} + + + +#ifdef _WIN32 + +#endif +int sqlite3_shathree_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "sha3", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3", 2, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha3Func, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_agg", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, 0, sha3AggStep, sha3AggFinal); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_agg", 2, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, 0, sha3AggStep, sha3AggFinal); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 1, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha3_query", 2, + SQLITE_UTF8 | SQLITE_DIRECTONLY, + 0, sha3QueryFunc, 0, 0); + } + return rc; +} + +/************************* End ext/misc/shathree.c ********************/ +/************************* Begin ext/misc/sha1.c ******************/ +/* +** 2017-01-27 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements functions that compute SHA1 hashes. +** Two SQL functions are implemented: +** +** sha1(X) +** sha1_query(Y) +** +** The sha1(X) function computes the SHA1 hash of the input X, or NULL if +** X is NULL. +** +** The sha1_query(Y) function evalutes all queries in the SQL statements of Y +** and returns a hash of their results. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +/****************************************************************************** +** The Hash Engine +*/ +/* Context for the SHA1 hash */ +typedef struct SHA1Context SHA1Context; +struct SHA1Context { + unsigned int state[5]; + unsigned int count[2]; + unsigned char buffer[64]; +}; + +#define SHA_ROT(x,l,r) ((x) << (l) | (x) >> (r)) +#define rol(x,k) SHA_ROT(x,k,32-(k)) +#define ror(x,k) SHA_ROT(x,32-(k),k) + +#define blk0le(i) (block[i] = (ror(block[i],8)&0xFF00FF00) \ + |(rol(block[i],8)&0x00FF00FF)) +#define blk0be(i) block[i] +#define blk(i) (block[i&15] = rol(block[(i+13)&15]^block[(i+8)&15] \ + ^block[(i+2)&15]^block[i&15],1)) + +/* + * (R0+R1), R2, R3, R4 are the different operations (rounds) used in SHA1 + * + * Rl0() for little-endian and Rb0() for big-endian. Endianness is + * determined at run-time. + */ +#define Rl0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0le(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define Rb0(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk0be(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define R1(v,w,x,y,z,i) \ + z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=ror(w,2); +#define R2(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=ror(w,2); +#define R3(v,w,x,y,z,i) \ + z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=ror(w,2); +#define R4(v,w,x,y,z,i) \ + z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=ror(w,2); + +/* + * Hash a single 512-bit block. This is the core of the algorithm. + */ +static void SHA1Transform(unsigned int state[5], const unsigned char buffer[64]){ + unsigned int qq[5]; /* a, b, c, d, e; */ + static int one = 1; + unsigned int block[16]; + memcpy(block, buffer, 64); + memcpy(qq,state,5*sizeof(unsigned int)); + +#define a qq[0] +#define b qq[1] +#define c qq[2] +#define d qq[3] +#define e qq[4] + + /* Copy p->state[] to working vars */ + /* + a = state[0]; + b = state[1]; + c = state[2]; + d = state[3]; + e = state[4]; + */ + + /* 4 rounds of 20 operations each. Loop unrolled. */ + if( 1 == *(unsigned char*)&one ){ + Rl0(a,b,c,d,e, 0); Rl0(e,a,b,c,d, 1); Rl0(d,e,a,b,c, 2); Rl0(c,d,e,a,b, 3); + Rl0(b,c,d,e,a, 4); Rl0(a,b,c,d,e, 5); Rl0(e,a,b,c,d, 6); Rl0(d,e,a,b,c, 7); + Rl0(c,d,e,a,b, 8); Rl0(b,c,d,e,a, 9); Rl0(a,b,c,d,e,10); Rl0(e,a,b,c,d,11); + Rl0(d,e,a,b,c,12); Rl0(c,d,e,a,b,13); Rl0(b,c,d,e,a,14); Rl0(a,b,c,d,e,15); + }else{ + Rb0(a,b,c,d,e, 0); Rb0(e,a,b,c,d, 1); Rb0(d,e,a,b,c, 2); Rb0(c,d,e,a,b, 3); + Rb0(b,c,d,e,a, 4); Rb0(a,b,c,d,e, 5); Rb0(e,a,b,c,d, 6); Rb0(d,e,a,b,c, 7); + Rb0(c,d,e,a,b, 8); Rb0(b,c,d,e,a, 9); Rb0(a,b,c,d,e,10); Rb0(e,a,b,c,d,11); + Rb0(d,e,a,b,c,12); Rb0(c,d,e,a,b,13); Rb0(b,c,d,e,a,14); Rb0(a,b,c,d,e,15); + } + R1(e,a,b,c,d,16); R1(d,e,a,b,c,17); R1(c,d,e,a,b,18); R1(b,c,d,e,a,19); + R2(a,b,c,d,e,20); R2(e,a,b,c,d,21); R2(d,e,a,b,c,22); R2(c,d,e,a,b,23); + R2(b,c,d,e,a,24); R2(a,b,c,d,e,25); R2(e,a,b,c,d,26); R2(d,e,a,b,c,27); + R2(c,d,e,a,b,28); R2(b,c,d,e,a,29); R2(a,b,c,d,e,30); R2(e,a,b,c,d,31); + R2(d,e,a,b,c,32); R2(c,d,e,a,b,33); R2(b,c,d,e,a,34); R2(a,b,c,d,e,35); + R2(e,a,b,c,d,36); R2(d,e,a,b,c,37); R2(c,d,e,a,b,38); R2(b,c,d,e,a,39); + R3(a,b,c,d,e,40); R3(e,a,b,c,d,41); R3(d,e,a,b,c,42); R3(c,d,e,a,b,43); + R3(b,c,d,e,a,44); R3(a,b,c,d,e,45); R3(e,a,b,c,d,46); R3(d,e,a,b,c,47); + R3(c,d,e,a,b,48); R3(b,c,d,e,a,49); R3(a,b,c,d,e,50); R3(e,a,b,c,d,51); + R3(d,e,a,b,c,52); R3(c,d,e,a,b,53); R3(b,c,d,e,a,54); R3(a,b,c,d,e,55); + R3(e,a,b,c,d,56); R3(d,e,a,b,c,57); R3(c,d,e,a,b,58); R3(b,c,d,e,a,59); + R4(a,b,c,d,e,60); R4(e,a,b,c,d,61); R4(d,e,a,b,c,62); R4(c,d,e,a,b,63); + R4(b,c,d,e,a,64); R4(a,b,c,d,e,65); R4(e,a,b,c,d,66); R4(d,e,a,b,c,67); + R4(c,d,e,a,b,68); R4(b,c,d,e,a,69); R4(a,b,c,d,e,70); R4(e,a,b,c,d,71); + R4(d,e,a,b,c,72); R4(c,d,e,a,b,73); R4(b,c,d,e,a,74); R4(a,b,c,d,e,75); + R4(e,a,b,c,d,76); R4(d,e,a,b,c,77); R4(c,d,e,a,b,78); R4(b,c,d,e,a,79); + + /* Add the working vars back into context.state[] */ + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + state[4] += e; + +#undef a +#undef b +#undef c +#undef d +#undef e +} + + +/* Initialize a SHA1 context */ +static void hash_init(SHA1Context *p){ + /* SHA1 initialization constants */ + p->state[0] = 0x67452301; + p->state[1] = 0xEFCDAB89; + p->state[2] = 0x98BADCFE; + p->state[3] = 0x10325476; + p->state[4] = 0xC3D2E1F0; + p->count[0] = p->count[1] = 0; +} + +/* Add new content to the SHA1 hash */ +static void hash_step( + SHA1Context *p, /* Add content to this context */ + const unsigned char *data, /* Data to be added */ + unsigned int len /* Number of bytes in data */ +){ + unsigned int i, j; + + j = p->count[0]; + if( (p->count[0] += len << 3) < j ){ + p->count[1] += (len>>29)+1; + } + j = (j >> 3) & 63; + if( (j + len) > 63 ){ + (void)memcpy(&p->buffer[j], data, (i = 64-j)); + SHA1Transform(p->state, p->buffer); + for(; i + 63 < len; i += 64){ + SHA1Transform(p->state, &data[i]); + } + j = 0; + }else{ + i = 0; + } + (void)memcpy(&p->buffer[j], &data[i], len - i); +} + +/* Compute a string using sqlite3_vsnprintf() and hash it */ +static void hash_step_vformat( + SHA1Context *p, /* Add content to this context */ + const char *zFormat, + ... +){ + va_list ap; + int n; + char zBuf[50]; + va_start(ap, zFormat); + sqlite3_vsnprintf(sizeof(zBuf),zBuf,zFormat,ap); + va_end(ap); + n = (int)strlen(zBuf); + hash_step(p, (unsigned char*)zBuf, n); +} + + +/* Add padding and compute the message digest. Render the +** message digest as lower-case hexadecimal and put it into +** zOut[]. zOut[] must be at least 41 bytes long. */ +static void hash_finish( + SHA1Context *p, /* The SHA1 context to finish and render */ + char *zOut, /* Store hex or binary hash here */ + int bAsBinary /* 1 for binary hash, 0 for hex hash */ +){ + unsigned int i; + unsigned char finalcount[8]; + unsigned char digest[20]; + static const char zEncode[] = "0123456789abcdef"; + + for (i = 0; i < 8; i++){ + finalcount[i] = (unsigned char)((p->count[(i >= 4 ? 0 : 1)] + >> ((3-(i & 3)) * 8) ) & 255); /* Endian independent */ + } + hash_step(p, (const unsigned char *)"\200", 1); + while ((p->count[0] & 504) != 448){ + hash_step(p, (const unsigned char *)"\0", 1); + } + hash_step(p, finalcount, 8); /* Should cause a SHA1Transform() */ + for (i = 0; i < 20; i++){ + digest[i] = (unsigned char)((p->state[i>>2] >> ((3-(i & 3)) * 8) ) & 255); + } + if( bAsBinary ){ + memcpy(zOut, digest, 20); + }else{ + for(i=0; i<20; i++){ + zOut[i*2] = zEncode[(digest[i]>>4)&0xf]; + zOut[i*2+1] = zEncode[digest[i] & 0xf]; + } + zOut[i*2]= 0; + } +} +/* End of the hashing logic +*****************************************************************************/ + +/* +** Two SQL functions: sha1(X) and sha1b(X). +** +** sha1(X) returns a lower-case hexadecimal rendering of the SHA1 hash +** of the argument X. If X is a BLOB, it is hashed as is. For all other +** types of input, X is converted into a UTF-8 string and the string +** is hashed without the trailing 0x00 terminator. The hash of a NULL +** value is NULL. +** +** sha1b(X) is the same except that it returns a 20-byte BLOB containing +** the binary hash instead of a hexadecimal string. +*/ +static void sha1Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + SHA1Context cx; + int eType = sqlite3_value_type(argv[0]); + int nByte = sqlite3_value_bytes(argv[0]); + const unsigned char *pData; + char zOut[44]; + + assert( argc==1 ); + if( eType==SQLITE_NULL ) return; + hash_init(&cx); + if( eType==SQLITE_BLOB ){ + pData = (const unsigned char*)sqlite3_value_blob(argv[0]); + }else{ + pData = (const unsigned char*)sqlite3_value_text(argv[0]); + } + if( pData==0 ) return; + hash_step(&cx, pData, nByte); + if( sqlite3_user_data(context)!=0 ){ + /* sha1b() - binary result */ + hash_finish(&cx, zOut, 1); + sqlite3_result_blob(context, zOut, 20, SQLITE_TRANSIENT); + }else{ + /* sha1() - hexadecimal text result */ + hash_finish(&cx, zOut, 0); + sqlite3_result_text(context, zOut, 40, SQLITE_TRANSIENT); + } +} + +/* +** Implementation of the sha1_query(SQL) function. +** +** This function compiles and runs the SQL statement(s) given in the +** argument. The results are hashed using SHA1 and that hash is returned. +** +** The original SQL text is included as part of the hash. +** +** The hash is not just a concatenation of the outputs. Each query +** is delimited and each row and value within the query is delimited, +** with all values being marked with their datatypes. +*/ +static void sha1QueryFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + sqlite3 *db = sqlite3_context_db_handle(context); + const char *zSql = (const char*)sqlite3_value_text(argv[0]); + sqlite3_stmt *pStmt = 0; + int nCol; /* Number of columns in the result set */ + int i; /* Loop counter */ + int rc; + int n; + const char *z; + SHA1Context cx; + char zOut[44]; + + assert( argc==1 ); + if( zSql==0 ) return; + hash_init(&cx); + while( zSql[0] ){ + rc = sqlite3_prepare_v2(db, zSql, -1, &pStmt, &zSql); + if( rc ){ + char *zMsg = sqlite3_mprintf("error SQL statement [%s]: %s", + zSql, sqlite3_errmsg(db)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + if( !sqlite3_stmt_readonly(pStmt) ){ + char *zMsg = sqlite3_mprintf("non-query: [%s]", sqlite3_sql(pStmt)); + sqlite3_finalize(pStmt); + sqlite3_result_error(context, zMsg, -1); + sqlite3_free(zMsg); + return; + } + nCol = sqlite3_column_count(pStmt); + z = sqlite3_sql(pStmt); + if( z==0 ) z = ""; + n = (int)strlen(z); + hash_step_vformat(&cx,"S%d:",n); + hash_step(&cx,(unsigned char*)z,n); + + /* Compute a hash over the result of the query */ + while( SQLITE_ROW==sqlite3_step(pStmt) ){ + hash_step(&cx,(const unsigned char*)"R",1); + for(i=0; i=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'I'; + hash_step(&cx, x, 9); + break; + } + case SQLITE_FLOAT: { + sqlite3_uint64 u; + int j; + unsigned char x[9]; + double r = sqlite3_column_double(pStmt,i); + memcpy(&u, &r, 8); + for(j=8; j>=1; j--){ + x[j] = u & 0xff; + u >>= 8; + } + x[0] = 'F'; + hash_step(&cx,x,9); + break; + } + case SQLITE_TEXT: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_text(pStmt, i); + hash_step_vformat(&cx,"T%d:",n2); + hash_step(&cx, z2, n2); + break; + } + case SQLITE_BLOB: { + int n2 = sqlite3_column_bytes(pStmt, i); + const unsigned char *z2 = sqlite3_column_blob(pStmt, i); + hash_step_vformat(&cx,"B%d:",n2); + hash_step(&cx, z2, n2); + break; + } + } + } + } + sqlite3_finalize(pStmt); + } + hash_finish(&cx, zOut, 0); + sqlite3_result_text(context, zOut, 40, SQLITE_TRANSIENT); +} + + +#ifdef _WIN32 + +#endif +int sqlite3_sha_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + static int one = 1; + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "sha1", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + 0, sha1Func, 0, 0); + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha1b", 1, + SQLITE_UTF8 | SQLITE_INNOCUOUS | SQLITE_DETERMINISTIC, + (void*)&one, sha1Func, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "sha1_query", 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + sha1QueryFunc, 0, 0); + } + return rc; +} + +/************************* End ext/misc/sha1.c ********************/ +/************************* Begin ext/misc/uint.c ******************/ +/* +** 2020-04-14 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements the UINT collating sequence. +** +** UINT works like BINARY for text, except that embedded strings +** of digits compare in numeric order. +** +** * Leading zeros are handled properly, in the sense that +** they do not mess of the magnitude comparison of embedded +** strings of digits. "x00123y" is equal to "x123y". +** +** * Only unsigned integers are recognized. Plus and minus +** signs are ignored. Decimal points and exponential notation +** are ignored. +** +** * Embedded integers can be of arbitrary length. Comparison +** is *not* limited integers that can be expressed as a +** 64-bit machine integer. +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include + +/* +** Compare text in lexicographic order, except strings of digits +** compare in numeric order. +*/ +static int uintCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + int i=0, j=0, x; + (void)notUsed; + while( i +#include +#include +#include + +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) +#endif + +#ifndef IsSpace +#define IsSpace(X) isspace((unsigned char)X) +#endif + +#ifndef SQLITE_DECIMAL_MAX_DIGIT +# define SQLITE_DECIMAL_MAX_DIGIT 10000000 +#endif + +/* A decimal object */ +typedef struct Decimal Decimal; +struct Decimal { + char sign; /* 0 for positive, 1 for negative */ + char oom; /* True if an OOM is encountered */ + char isNull; /* True if holds a NULL rather than a number */ + char isInit; /* True upon initialization */ + int nDigit; /* Total number of digits */ + int nFrac; /* Number of digits to the right of the decimal point */ + signed char *a; /* Array of digits. Most significant first. */ +}; + +/* +** Release memory held by a Decimal, but do not free the object itself. +*/ +static void decimal_clear(Decimal *p){ + sqlite3_free(p->a); +} + +/* +** Destroy a Decimal object +*/ +static void decimal_free(Decimal *p){ + if( p ){ + decimal_clear(p); + sqlite3_free(p); + } +} + +/* +** Allocate a new Decimal object initialized to the text in zIn[]. +** Return NULL if any kind of error occurs. +*/ +static Decimal *decimalNewFromText(const char *zIn, int n){ + Decimal *p = 0; + int i; + int iExp = 0; + + if( zIn==0 ) goto new_from_text_failed; + p = sqlite3_malloc64( sizeof(*p) ); + if( p==0 ) goto new_from_text_failed; + p->sign = 0; + p->oom = 0; + p->isInit = 1; + p->isNull = 0; + p->nDigit = 0; + p->nFrac = 0; + p->a = sqlite3_malloc64( n+1 ); + if( p->a==0 ) goto new_from_text_failed; + for(i=0; IsSpace(zIn[i]); i++){} + if( zIn[i]=='-' ){ + p->sign = 1; + i++; + }else if( zIn[i]=='+' ){ + i++; + } + while( i='0' && c<='9' ){ + p->a[p->nDigit++] = c - '0'; + }else if( c=='.' ){ + p->nFrac = p->nDigit + 1; + }else if( c=='e' || c=='E' ){ + int j = i+1; + int neg = 0; + if( j>=n ) break; + if( zIn[j]=='-' ){ + neg = 1; + j++; + }else if( zIn[j]=='+' ){ + j++; + } + while( j='0' && zIn[j]<='9' ){ + iExp = iExp*10 + zIn[j] - '0'; + } + j++; + } + if( neg ) iExp = -iExp; + break; + } + i++; + } + if( p->nFrac ){ + p->nFrac = p->nDigit - (p->nFrac - 1); + } + if( iExp>0 ){ + if( p->nFrac>0 ){ + if( iExp<=p->nFrac ){ + p->nFrac -= iExp; + iExp = 0; + }else{ + iExp -= p->nFrac; + p->nFrac = 0; + } + } + if( iExp>0 ){ + signed char *a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit + + (sqlite3_int64)iExp + 1 ); + if( a==0 ) goto new_from_text_failed; + p->a = a; + memset(p->a+p->nDigit, 0, iExp); + p->nDigit += iExp; + } + }else if( iExp<0 ){ + int nExtra; + iExp = -iExp; + nExtra = p->nDigit - p->nFrac - 1; + if( nExtra ){ + if( nExtra>=iExp ){ + p->nFrac += iExp; + iExp = 0; + }else{ + iExp -= nExtra; + p->nFrac = p->nDigit - 1; + } + } + if( iExp>0 ){ + signed char *a = sqlite3_realloc64(p->a, (sqlite3_int64)p->nDigit + + (sqlite3_int64)iExp + 1 ); + if( a==0 ) goto new_from_text_failed; + p->a = a; + memmove(p->a+iExp, p->a, p->nDigit); + memset(p->a, 0, iExp); + p->nDigit += iExp; + p->nFrac += iExp; + } + } + if( p->sign ){ + for(i=0; inDigit && p->a[i]==0; i++){} + if( i>=p->nDigit ) p->sign = 0; + } + if( p->nDigit>SQLITE_DECIMAL_MAX_DIGIT ) goto new_from_text_failed; + return p; + +new_from_text_failed: + if( p ){ + if( p->a ) sqlite3_free(p->a); + sqlite3_free(p); + } + return 0; +} + +/* Forward reference */ +static Decimal *decimalFromDouble(double); + +/* +** Allocate a new Decimal object from an sqlite3_value. Return a pointer +** to the new object, or NULL if there is an error. If the pCtx argument +** is not NULL, then errors are reported on it as well. +** +** If the pIn argument is SQLITE_TEXT or SQLITE_INTEGER, it is converted +** directly into a Decimal. For SQLITE_FLOAT or for SQLITE_BLOB of length +** 8 bytes, the resulting double value is expanded into its decimal equivalent. +** If pIn is NULL or if it is a BLOB that is not exactly 8 bytes in length, +** then NULL is returned. +*/ +static Decimal *decimal_new( + sqlite3_context *pCtx, /* Report error here, if not null */ + sqlite3_value *pIn, /* Construct the decimal object from this */ + int bTextOnly /* Always interpret pIn as text if true */ +){ + Decimal *p = 0; + int eType = sqlite3_value_type(pIn); + if( bTextOnly && (eType==SQLITE_FLOAT || eType==SQLITE_BLOB) ){ + eType = SQLITE_TEXT; + } + switch( eType ){ + case SQLITE_TEXT: + case SQLITE_INTEGER: { + const char *zIn = (const char*)sqlite3_value_text(pIn); + int n = sqlite3_value_bytes(pIn); + p = decimalNewFromText(zIn, n); + if( p==0 ) goto new_failed; + break; + } + + case SQLITE_FLOAT: { + p = decimalFromDouble(sqlite3_value_double(pIn)); + break; + } + + case SQLITE_BLOB: { + const unsigned char *x; + unsigned int i; + sqlite3_uint64 v = 0; + double r; + + if( sqlite3_value_bytes(pIn)!=sizeof(r) ) break; + x = sqlite3_value_blob(pIn); + for(i=0; ioom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + z = sqlite3_malloc64( (sqlite3_int64)p->nDigit+4 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + i = 0; + if( p->nDigit==0 || (p->nDigit==1 && p->a[0]==0) ){ + p->sign = 0; + } + if( p->sign ){ + z[0] = '-'; + i = 1; + } + n = p->nDigit - p->nFrac; + if( n<=0 ){ + z[i++] = '0'; + } + j = 0; + while( n>1 && p->a[j]==0 ){ + j++; + n--; + } + while( n>0 ){ + z[i++] = p->a[j] + '0'; + j++; + n--; + } + if( p->nFrac ){ + z[i++] = '.'; + do{ + z[i++] = p->a[j] + '0'; + j++; + }while( jnDigit ); + } + z[i] = 0; + sqlite3_result_text(pCtx, z, i, sqlite3_free); +} + +/* +** Round a decimal value to N significant digits. N must be positive. +*/ +static void decimal_round(Decimal *p, int N){ + int i; + int nZero; + if( N<1 ) return; + if( p==0 ) return; + if( p->nDigit<=N ) return; + for(nZero=0; nZeronDigit && p->a[nZero]==0; nZero++){} + N += nZero; + if( p->nDigit<=N ) return; + if( p->a[N]>4 ){ + p->a[N-1]++; + for(i=N-1; i>0 && p->a[i]>9; i--){ + p->a[i] = 0; + p->a[i-1]++; + } + if( p->a[0]>9 ){ + p->a[0] = 1; + p->nFrac--; + } + } + memset(&p->a[N], 0, p->nDigit - N); +} + +/* +** Make the given Decimal the result in an format similar to '%+#e'. +** In other words, show exponential notation with leading and trailing +** zeros omitted. +*/ +static void decimal_result_sci(sqlite3_context *pCtx, Decimal *p, int N){ + char *z; /* The output buffer */ + int i; /* Loop counter */ + int nZero; /* Number of leading zeros */ + int nDigit; /* Number of digits not counting trailing zeros */ + int nFrac; /* Digits to the right of the decimal point */ + int exp; /* Exponent value */ + signed char zero; /* Zero value */ signed char *a; /* Array of digits */ - if( p==0 || p->oom ){ - sqlite3_result_error_nomem(pCtx); - return; + if( p==0 || p->oom ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( p->isNull ){ + sqlite3_result_null(pCtx); + return; + } + if( N<1 ) N = 0; + for(nDigit=p->nDigit; nDigit>N && p->a[nDigit-1]==0; nDigit--){} + for(nZero=0; nZeroa[nZero]==0; nZero++){} + nFrac = p->nFrac + (nDigit - p->nDigit); + nDigit -= nZero; + z = sqlite3_malloc64( (sqlite3_int64)nDigit+20 ); + if( z==0 ){ + sqlite3_result_error_nomem(pCtx); + return; + } + if( nDigit==0 ){ + zero = 0; + a = &zero; + nDigit = 1; + nFrac = 0; + }else{ + a = &p->a[nZero]; + } + if( p->sign && nDigit>0 ){ + z[0] = '-'; + }else{ + z[0] = '+'; + } + z[1] = a[0]+'0'; + z[2] = '.'; + if( nDigit==1 ){ + z[3] = '0'; + i = 4; + }else{ + for(i=1; iisNull==0 +** pB!=0 +** pB->isNull==0 +*/ +static int decimal_cmp(Decimal *pA, Decimal *pB){ + int nASig, nBSig, rc, n; + while( pA->nFrac>0 && pA->a[pA->nDigit-1]==0 ){ + pA->nDigit--; + pA->nFrac--; + } + while( pB->nFrac>0 && pB->a[pB->nDigit-1]==0 ){ + pB->nDigit--; + pB->nFrac--; + } + if( pA->sign!=pB->sign ){ + return pA->sign ? -1 : +1; + } + if( pA->sign ){ + Decimal *pTemp = pA; + pA = pB; + pB = pTemp; + } + nASig = pA->nDigit - pA->nFrac; + nBSig = pB->nDigit - pB->nFrac; + if( nASig!=nBSig ){ + return nASig - nBSig; + } + n = pA->nDigit; + if( n>pB->nDigit ) n = pB->nDigit; + rc = memcmp(pA->a, pB->a, n); + if( rc==0 ){ + rc = pA->nDigit - pB->nDigit; + } + return rc; +} + +/* +** SQL Function: decimal_cmp(X, Y) +** +** Return negative, zero, or positive if X is less then, equal to, or +** greater than Y. +*/ +static void decimalCmpFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = 0, *pB = 0; + int rc; + + UNUSED_PARAMETER(argc); + pA = decimal_new(context, argv[0], 1); + if( pA==0 || pA->isNull ) goto cmp_done; + pB = decimal_new(context, argv[1], 1); + if( pB==0 || pB->isNull ) goto cmp_done; + rc = decimal_cmp(pA, pB); + if( rc<0 ) rc = -1; + else if( rc>0 ) rc = +1; + sqlite3_result_int(context, rc); +cmp_done: + decimal_free(pA); + decimal_free(pB); +} + +/* +** Expand the Decimal so that it has a least nDigit digits and nFrac +** digits to the right of the decimal point. +*/ +static void decimal_expand(Decimal *p, int nDigit, int nFrac){ + int nAddSig; + int nAddFrac; + signed char *a; + if( p==0 ) return; + nAddFrac = nFrac - p->nFrac; + nAddSig = (nDigit - p->nDigit) - nAddFrac; + if( nAddFrac==0 && nAddSig==0 ) return; + if( nDigit+1>SQLITE_DECIMAL_MAX_DIGIT ){ p->oom = 1; return; } + a = sqlite3_realloc64(p->a, nDigit+1); + if( a==0 ){ + p->oom = 1; + return; + } + p->a = a; + if( nAddSig ){ + memmove(p->a+nAddSig, p->a, p->nDigit); + memset(p->a, 0, nAddSig); + p->nDigit += nAddSig; + } + if( nAddFrac ){ + memset(p->a+p->nDigit, 0, nAddFrac); + p->nDigit += nAddFrac; + p->nFrac += nAddFrac; + } +} + +/* +** Add the value pB into pA. A := A + B. +** +** Both pA and pB might become denormalized by this routine. +*/ +static void decimal_add(Decimal *pA, Decimal *pB){ + int nSig, nFrac, nDigit; + int i, rc; + if( pA==0 ){ + return; + } + if( pA->oom || pB==0 || pB->oom ){ + pA->oom = 1; + return; + } + if( pA->isNull || pB->isNull ){ + pA->isNull = 1; + return; + } + nSig = pA->nDigit - pA->nFrac; + if( nSig && pA->a[0]==0 ) nSig--; + if( nSignDigit-pB->nFrac ){ + nSig = pB->nDigit - pB->nFrac; + } + nFrac = pA->nFrac; + if( nFracnFrac ) nFrac = pB->nFrac; + nDigit = nSig + nFrac + 1; + decimal_expand(pA, nDigit, nFrac); + decimal_expand(pB, nDigit, nFrac); + if( pA->oom || pB->oom ){ + pA->oom = 1; + }else{ + if( pA->sign==pB->sign ){ + int carry = 0; + for(i=nDigit-1; i>=0; i--){ + int x = pA->a[i] + pB->a[i] + carry; + if( x>=10 ){ + carry = 1; + pA->a[i] = x - 10; + }else{ + carry = 0; + pA->a[i] = x; + } + } + }else{ + signed char *aA, *aB; + int borrow = 0; + rc = memcmp(pA->a, pB->a, nDigit); + if( rc<0 ){ + aA = pB->a; + aB = pA->a; + pA->sign = !pA->sign; + }else{ + aA = pA->a; + aB = pB->a; + } + for(i=nDigit-1; i>=0; i--){ + int x = aA[i] - aB[i] - borrow; + if( x<0 ){ + pA->a[i] = x+10; + borrow = 1; + }else{ + pA->a[i] = x; + borrow = 0; + } + } + } + } +} + +/* +** Multiply A by B. A := A * B +** +** All significant digits after the decimal point are retained. +** Trailing zeros after the decimal point are omitted as long as +** the number of digits after the decimal point is no less than +** either the number of digits in either input. +*/ +static void decimalMul(Decimal *pA, Decimal *pB){ + signed char *acc = 0; + int i, j, k; + int minFrac; + sqlite3_int64 sumDigit; + + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + sumDigit = pA->nDigit; + sumDigit += pB->nDigit; + sumDigit += 2; + if( sumDigit>SQLITE_DECIMAL_MAX_DIGIT ){ pA->oom = 1; return; } + acc = sqlite3_malloc64( sumDigit ); + if( acc==0 ){ + pA->oom = 1; + goto mul_end; + } + memset(acc, 0, pA->nDigit + pB->nDigit + 2); + minFrac = pA->nFrac; + if( pB->nFracnFrac; + for(i=pA->nDigit-1; i>=0; i--){ + signed char f = pA->a[i]; + int carry = 0, x; + for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ + x = acc[k] + f*pB->a[j] + carry; + acc[k] = x%10; + carry = x/10; + } + x = acc[k] + carry; + acc[k] = x%10; + acc[k-1] += x/10; + } + sqlite3_free(pA->a); + pA->a = acc; + acc = 0; + pA->nDigit += pB->nDigit + 2; + pA->nFrac += pB->nFrac; + pA->sign ^= pB->sign; + while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ + pA->nFrac--; + pA->nDigit--; + } + +mul_end: + sqlite3_free(acc); +} + +/* +** Create a new Decimal object that contains an integer power of 2. +*/ +static Decimal *decimalPow2(int N){ + Decimal *pA = 0; /* The result to be returned */ + Decimal *pX = 0; /* Multiplier */ + if( N<-20000 || N>20000 ) goto pow2_fault; + pA = decimalNewFromText("1.0", 3); + if( pA==0 || pA->oom ) goto pow2_fault; + if( N==0 ) return pA; + if( N>0 ){ + pX = decimalNewFromText("2.0", 3); + }else{ + N = -N; + pX = decimalNewFromText("0.5", 3); + } + if( pX==0 || pX->oom ) goto pow2_fault; + while( 1 /* Exit by break */ ){ + if( N & 1 ){ + decimalMul(pA, pX); + if( pA->oom ) goto pow2_fault; + } + N >>= 1; + if( N==0 ) break; + decimalMul(pX, pX); + } + decimal_free(pX); + return pA; + +pow2_fault: + decimal_free(pA); + decimal_free(pX); + return 0; +} + +/* +** Use an IEEE754 binary64 ("double") to generate a new Decimal object. +*/ +static Decimal *decimalFromDouble(double r){ + sqlite3_int64 m, a; + int e; + int isNeg; + Decimal *pA; + Decimal *pX; + char zNum[100]; + if( r<0.0 ){ + isNeg = 1; + r = -r; + }else{ + isNeg = 0; + } + memcpy(&a,&r,sizeof(a)); + if( a==0 || a==(sqlite3_int64)0x8000000000000000LL){ + e = 0; + m = 0; + }else{ + e = a>>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + e = e - 1075; + if( e>971 ){ + return 0; /* A NaN or an Infinity */ + } + } + + /* At this point m is the integer significand and e is the exponent */ + sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m); + pA = decimalNewFromText(zNum, (int)strlen(zNum)); + pX = decimalPow2(e); + decimalMul(pA, pX); + decimal_free(pX); + return pA; +} + +/* +** SQL Function: decimal(X) +** OR: decimal_exp(X) +** +** Convert input X into decimal and then back into text. +** +** If X is originally a float, then a full decimal expansion of that floating +** point value is done. Or if X is an 8-byte blob, it is interpreted +** as a float and similarly expanded. +** +** The decimal_exp(X) function returns the result in exponential notation. +** decimal(X) returns a complete decimal, without the e+NNN at the end. +*/ +static void decimalFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p = decimal_new(context, argv[0], 0); + int N; + if( argc==2 ){ + N = sqlite3_value_int(argv[1]); + if( N>0 ) decimal_round(p, N); + }else{ + N = 0; + } + if( p ){ + if( sqlite3_user_data(context)!=0 ){ + decimal_result_sci(context, p, N); + }else{ + decimal_result(context, p); + } + decimal_free(p); + } +} + +/* +** Compare text in decimal order. +*/ +static int decimalCollFunc( + void *notUsed, + int nKey1, const void *pKey1, + int nKey2, const void *pKey2 +){ + const unsigned char *zA = (const unsigned char*)pKey1; + const unsigned char *zB = (const unsigned char*)pKey2; + Decimal *pA = decimalNewFromText((const char*)zA, nKey1); + Decimal *pB = decimalNewFromText((const char*)zB, nKey2); + int rc; + UNUSED_PARAMETER(notUsed); + if( pA==0 || pB==0 ){ + rc = 0; + }else{ + rc = decimal_cmp(pA, pB); + } + decimal_free(pA); + decimal_free(pB); + return rc; +} + + +/* +** SQL Function: decimal_add(X, Y) +** decimal_sub(X, Y) +** +** Return the sum or difference of X and Y. +*/ +static void decimalAddFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); + UNUSED_PARAMETER(argc); + decimal_add(pA, pB); + decimal_result(context, pA); + decimal_free(pA); + decimal_free(pB); +} +static void decimalSubFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); + UNUSED_PARAMETER(argc); + if( pB ){ + pB->sign = !pB->sign; + decimal_add(pA, pB); + decimal_result(context, pA); + } + decimal_free(pA); + decimal_free(pB); +} + +/* Aggregate function: decimal_sum(X) +** +** Works like sum() except that it uses decimal arithmetic for unlimited +** precision. +*/ +static void decimalSumStep( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( !p->isInit ){ + p->isInit = 1; + p->a = sqlite3_malloc64(2); + if( p->a==0 ){ + p->oom = 1; + }else{ + p->a[0] = 0; + } + p->nDigit = 1; + p->nFrac = 0; + } + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 1); + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumInverse( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *p; + Decimal *pArg; + UNUSED_PARAMETER(argc); + p = sqlite3_aggregate_context(context, sizeof(*p)); + if( p==0 ) return; + if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; + pArg = decimal_new(context, argv[0], 1); + if( pArg ) pArg->sign = !pArg->sign; + decimal_add(p, pArg); + decimal_free(pArg); +} +static void decimalSumValue(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); +} +static void decimalSumFinalize(sqlite3_context *context){ + Decimal *p = sqlite3_aggregate_context(context, 0); + if( p==0 ) return; + decimal_result(context, p); + decimal_clear(p); +} + +/* +** SQL Function: decimal_mul(X, Y) +** +** Return the product of X and Y. +*/ +static void decimalMulFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + Decimal *pA = decimal_new(context, argv[0], 1); + Decimal *pB = decimal_new(context, argv[1], 1); + UNUSED_PARAMETER(argc); + if( pA==0 || pA->oom || pA->isNull + || pB==0 || pB->oom || pB->isNull + ){ + goto mul_end; + } + decimalMul(pA, pB); + if( pA->oom ){ + goto mul_end; + } + decimal_result(context, pA); + +mul_end: + decimal_free(pA); + decimal_free(pB); +} + +/* +** SQL Function: decimal_pow2(N) +** +** Return the N-th power of 2. N must be an integer. +*/ +static void decimalPow2Func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){ + Decimal *pA = decimalPow2(sqlite3_value_int(argv[0])); + decimal_result_sci(context, pA, 0); + decimal_free(pA); } - if( p->isNull ){ - sqlite3_result_null(pCtx); - return; +} + +#ifdef _WIN32 + +#endif +int sqlite3_decimal_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + static const struct { + const char *zFuncName; + int nArg; + int iArg; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "decimal", 1, 0, decimalFunc }, + { "decimal", 2, 0, decimalFunc }, + { "decimal_exp", 1, 1, decimalFunc }, + { "decimal_exp", 2, 1, decimalFunc }, + { "decimal_cmp", 2, 0, decimalCmpFunc }, + { "decimal_add", 2, 0, decimalAddFunc }, + { "decimal_sub", 2, 0, decimalSubFunc }, + { "decimal_mul", 2, 0, decimalMulFunc }, + { "decimal_pow2", 1, 0, decimalPow2Func }, + }; + unsigned int i; + (void)pzErrMsg; /* Unused parameter */ + + SQLITE_EXTENSION_INIT2(pApi); + + for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){ + rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0); } - for(nDigit=p->nDigit; nDigit>0 && p->a[nDigit-1]==0; nDigit--){} - for(nZero=0; nZeroa[nZero]==0; nZero++){} - nFrac = p->nFrac + (nDigit - p->nDigit); - nDigit -= nZero; - z = sqlite3_malloc( nDigit+20 ); - if( z==0 ){ - sqlite3_result_error_nomem(pCtx); - return; + if( rc==SQLITE_OK ){ + rc = sqlite3_create_window_function(db, "decimal_sum", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, 0, + decimalSumStep, decimalSumFinalize, + decimalSumValue, decimalSumInverse, 0); } - if( nDigit==0 ){ - zero = 0; - a = &zero; - nDigit = 1; - nFrac = 0; - }else{ - a = &p->a[nZero]; + if( rc==SQLITE_OK ){ + rc = sqlite3_create_collation(db, "decimal", SQLITE_UTF8, + 0, decimalCollFunc); } - if( p->sign && nDigit>0 ){ - z[0] = '-'; - }else{ - z[0] = '+'; + return rc; +} + +/************************* End ext/misc/decimal.c ********************/ +/************************* Begin ext/misc/base64.c ******************/ +/* +** 2022-11-18 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This is a SQLite extension for converting in either direction +** between a (binary) blob and base64 text. Base64 can transit a +** sane USASCII channel unmolested. It also plays nicely in CSV or +** written as TCL brace-enclosed literals or SQL string literals, +** and can be used unmodified in XML-like documents. +** +** This is an independent implementation of conversions specified in +** RFC 4648, done on the above date by the author (Larry Brasfield) +** who thereby has the right to put this into the public domain. +** +** The conversions meet RFC 4648 requirements, provided that this +** C source specifies that line-feeds are included in the encoded +** data to limit visible line lengths to 72 characters and to +** terminate any encoded blob having non-zero length. +** +** Length limitations are not imposed except that the runtime +** SQLite string or blob length limits are respected. Otherwise, +** any length binary sequence can be represented and recovered. +** Generated base64 sequences, with their line-feeds included, +** can be concatenated; the result converted back to binary will +** be the concatenation of the represented binary sequences. +** +** This SQLite3 extension creates a function, base64(x), which +** either: converts text x containing base64 to a returned blob; +** or converts a blob x to returned text containing base64. An +** error will be thrown for other input argument types. +** +** This code relies on UTF-8 encoding only with respect to the +** meaning of the first 128 (7-bit) codes matching that of USASCII. +** It will fail miserably if somehow made to try to convert EBCDIC. +** Because it is table-driven, it could be enhanced to handle that, +** but the world and SQLite have moved on from that anachronism. +** +** To build the extension: +** Set shell variable SQDIR= +** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c +** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c +** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll +*/ + +#include + +/* #include "sqlite3ext.h" */ + +#ifndef deliberate_fall_through +/* Quiet some compilers about some of our intentional code. */ +# if GCC_VERSION>=7000000 +# define deliberate_fall_through __attribute__((fallthrough)); +# else +# define deliberate_fall_through +# endif +#endif + +SQLITE_EXTENSION_INIT1; + +#define PC 0x80 /* pad character */ +#define WS 0x81 /* whitespace */ +#define ND 0x82 /* Not above or digit-value */ +#define PAD_CHAR '=' + +#ifndef U8_TYPEDEF +/* typedef unsigned char u8; */ +#define U8_TYPEDEF +#endif + +/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */ +static const u8 b64DigitValues[128] = { + /* HT LF VT FF CR */ + ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, + /* US */ + ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, + /*sp + / */ + WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63, + /* 0 1 5 9 = */ + 52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND, + /* A O */ + ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, + /* P Z */ + 15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND, + /* a o */ + ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, + /* p z */ + 41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND +}; + +static const char b64Numerals[64+1] += "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +#define BX_DV_PROTO(c) \ + ((((u8)(c))<0x80)? (u8)(b64DigitValues[(u8)(c)]) : 0x80) +#define IS_BX_DIGIT(bdp) (((u8)(bdp))<0x80) +#define IS_BX_WS(bdp) ((bdp)==WS) +#define IS_BX_PAD(bdp) ((bdp)==PC) +#define BX_NUMERAL(dv) (b64Numerals[(u8)(dv)]) +/* Width of base64 lines. Should be an integer multiple of 4. */ +#define B64_DARK_MAX 72 + +/* Encode a byte buffer into base64 text with linefeeds appended to limit +** encoded group lengths to B64_DARK_MAX or to terminate the last group. +*/ +static char* toBase64( u8 *pIn, int nbIn, char *pOut ){ + int nCol = 0; + while( nbIn >= 3 ){ + /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */ + pOut[0] = BX_NUMERAL(pIn[0]>>2); + pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f); + pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6)); + pOut[3] = BX_NUMERAL(pIn[2]&0x3f); + pOut += 4; + nbIn -= 3; + pIn += 3; + if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){ + *pOut++ = '\n'; + nCol = 0; + } + } + if( nbIn > 0 ){ + signed char nco = nbIn+1; + int nbe; + unsigned long qv = *pIn++; + for( nbe=1; nbe<3; ++nbe ){ + qv <<= 8; + if( nbe=0; --nbe ){ + char ce = (nbe>= 6; + pOut[nbe] = ce; + } + pOut += 4; + *pOut++ = '\n'; + } + *pOut = 0; + return pOut; +} + +/* Skip over text which is not base64 numeral(s). */ +static char * skipNonB64( char *s, int nc ){ + char c; + while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; + return s; +} + +/* Decode base64 text into a byte buffer. */ +static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ + if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; + while( ncIn>0 && *pIn!=PAD_CHAR ){ + static signed char nboi[] = { 0, 0, 1, 2, 3 }; + char *pUse = skipNonB64(pIn, ncIn); + unsigned long qv = 0L; + int nti, nbo, nac; + ncIn -= (pUse - pIn); + pIn = pUse; + nti = (ncIn>4)? 4 : ncIn; + ncIn -= nti; + nbo = nboi[nti]; + if( nbo==0 ) break; + for( nac=0; nac<4; ++nac ){ + char c = (nac>8) & 0xff; + deliberate_fall_through; /* FALLTHRU */ + case 1: + pOut[0] = (qv>>16) & 0xff; + break; + } + pOut += nbo; } - z[1] = a[0]+'0'; - z[2] = '.'; - if( nDigit==1 ){ - z[3] = '0'; - i = 4; - }else{ - for(i=1; iisNull==0 -** pB!=0 -** pB->isNull==0 +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +************************************************************************* +** +** This is a utility for converting binary to base85 or vice-versa. +** It can be built as a standalone program or an SQLite3 extension. +** +** Much like base64 representations, base85 can be sent through a +** sane USASCII channel unmolested. It also plays nicely in CSV or +** written as TCL brace-enclosed literals or SQL string literals. +** It is not suited for unmodified use in XML-like documents. +** +** The encoding used resembles Ascii85, but was devised by the author +** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85 +** variant sources existed, in the 1984 timeframe on a VAX mainframe. +** Further, this is an independent implementation of a base85 system. +** Hence, the author has rightfully put this into the public domain. +** +** Base85 numerals are taken from the set of 7-bit USASCII codes, +** excluding control characters and Space ! " ' ( ) { | } ~ Del +** in code order representing digit values 0 to 84 (base 10.) +** +** Groups of 4 bytes, interpreted as big-endian 32-bit values, +** are represented as 5-digit base85 numbers with MS to LS digit +** order. Groups of 1-3 bytes are represented with 2-4 digits, +** still big-endian but 8-24 bit values. (Using big-endian yields +** the simplest transition to byte groups smaller than 4 bytes. +** These byte groups can also be considered base-256 numbers.) +** Groups of 0 bytes are represented with 0 digits and vice-versa. +** No pad characters are used; Encoded base85 numeral sequence +** (aka "group") length maps 1-to-1 to the decoded binary length. +** +** Any character not in the base85 numeral set delimits groups. +** When base85 is streamed or stored in containers of indefinite +** size, newline is used to separate it into sub-sequences of no +** more than 80 digits so that fgets() can be used to read it. +** +** Length limitations are not imposed except that the runtime +** SQLite string or blob length limits are respected. Otherwise, +** any length binary sequence can be represented and recovered. +** Base85 sequences can be concatenated by separating them with +** a non-base85 character; the conversion to binary will then +** be the concatenation of the represented binary sequences. + +** The standalone program either converts base85 on stdin to create +** a binary file or converts a binary file to base85 on stdout. +** Read or make it blurt its help for invocation details. +** +** The SQLite3 extension creates a function, base85(x), which will +** either convert text base85 to a blob or a blob to text base85 +** and return the result (or throw an error for other types.) +** Unless built with OMIT_BASE85_CHECKER defined, it also creates a +** function, is_base85(t), which returns 1 iff the text t contains +** nothing other than base85 numerals and whitespace, or 0 otherwise. +** +** To build the extension: +** Set shell variable SQDIR= +** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted. +** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c +** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c +** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c +** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll +** +** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg. +** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85 +** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c +** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c */ -static int decimal_cmp(const Decimal *pA, const Decimal *pB){ - int nASig, nBSig, rc, n; - if( pA->sign!=pB->sign ){ - return pA->sign ? -1 : +1; - } - if( pA->sign ){ - const Decimal *pTemp = pA; - pA = pB; - pB = pTemp; - } - nASig = pA->nDigit - pA->nFrac; - nBSig = pB->nDigit - pB->nFrac; - if( nASig!=nBSig ){ - return nASig - nBSig; - } - n = pA->nDigit; - if( n>pB->nDigit ) n = pB->nDigit; - rc = memcmp(pA->a, pB->a, n); - if( rc==0 ){ - rc = pA->nDigit - pB->nDigit; - } - return rc; -} -/* -** SQL Function: decimal_cmp(X, Y) -** -** Return negative, zero, or positive if X is less then, equal to, or -** greater than Y. -*/ -static void decimalCmpFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *pA = 0, *pB = 0; - int rc; +#include +#include +#include +#include +#ifndef OMIT_BASE85_CHECKER +# include +#endif + +#ifndef BASE85_STANDALONE + +/* # include "sqlite3ext.h" */ + +SQLITE_EXTENSION_INIT1; + +#else + +# ifdef _WIN32 +# include +# include +# else +# define setmode(fd,m) +# endif - UNUSED_PARAMETER(argc); - pA = decimal_new(context, argv[0], 1); - if( pA==0 || pA->isNull ) goto cmp_done; - pB = decimal_new(context, argv[1], 1); - if( pB==0 || pB->isNull ) goto cmp_done; - rc = decimal_cmp(pA, pB); - if( rc<0 ) rc = -1; - else if( rc>0 ) rc = +1; - sqlite3_result_int(context, rc); -cmp_done: - decimal_free(pA); - decimal_free(pB); -} +static char *zHelp = + "Usage: base85 \n" + " is either -r to read or -w to write ,\n" + " content to be converted to/from base85 on stdout/stdin.\n" + " names a binary file to be rendered or created.\n" + " Or, the name '-' refers to the stdin or stdout stream.\n" + ; -/* -** Expand the Decimal so that it has a least nDigit digits and nFrac -** digits to the right of the decimal point. -*/ -static void decimal_expand(Decimal *p, int nDigit, int nFrac){ - int nAddSig; - int nAddFrac; - if( p==0 ) return; - nAddFrac = nFrac - p->nFrac; - nAddSig = (nDigit - p->nDigit) - nAddFrac; - if( nAddFrac==0 && nAddSig==0 ) return; - p->a = sqlite3_realloc64(p->a, nDigit+1); - if( p->a==0 ){ - p->oom = 1; - return; - } - if( nAddSig ){ - memmove(p->a+nAddSig, p->a, p->nDigit); - memset(p->a, 0, nAddSig); - p->nDigit += nAddSig; - } - if( nAddFrac ){ - memset(p->a+p->nDigit, 0, nAddFrac); - p->nDigit += nAddFrac; - p->nFrac += nAddFrac; - } +static void sayHelp(){ + printf("%s", zHelp); } +#endif -/* -** Add the value pB into pA. A := A + B. -** -** Both pA and pB might become denormalized by this routine. -*/ -static void decimal_add(Decimal *pA, Decimal *pB){ - int nSig, nFrac, nDigit; - int i, rc; - if( pA==0 ){ - return; - } - if( pA->oom || pB==0 || pB->oom ){ - pA->oom = 1; - return; - } - if( pA->isNull || pB->isNull ){ - pA->isNull = 1; - return; - } - nSig = pA->nDigit - pA->nFrac; - if( nSig && pA->a[0]==0 ) nSig--; - if( nSignDigit-pB->nFrac ){ - nSig = pB->nDigit - pB->nFrac; - } - nFrac = pA->nFrac; - if( nFracnFrac ) nFrac = pB->nFrac; - nDigit = nSig + nFrac + 1; - decimal_expand(pA, nDigit, nFrac); - decimal_expand(pB, nDigit, nFrac); - if( pA->oom || pB->oom ){ - pA->oom = 1; - }else{ - if( pA->sign==pB->sign ){ - int carry = 0; - for(i=nDigit-1; i>=0; i--){ - int x = pA->a[i] + pB->a[i] + carry; - if( x>=10 ){ - carry = 1; - pA->a[i] = x - 10; - }else{ - carry = 0; - pA->a[i] = x; - } - } - }else{ - signed char *aA, *aB; - int borrow = 0; - rc = memcmp(pA->a, pB->a, nDigit); - if( rc<0 ){ - aA = pB->a; - aB = pA->a; - pA->sign = !pA->sign; - }else{ - aA = pA->a; - aB = pB->a; - } - for(i=nDigit-1; i>=0; i--){ - int x = aA[i] - aB[i] - borrow; - if( x<0 ){ - pA->a[i] = x+10; - borrow = 1; - }else{ - pA->a[i] = x; - borrow = 0; - } - } - } - } +#ifndef U8_TYPEDEF +/* typedef unsigned char u8; */ +#define U8_TYPEDEF +#endif + +/* Classify c according to interval within USASCII set w.r.t. base85 + * Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not. + */ +#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z')) + +/* Provide digitValue to b85Numeral offset as a function of above class. */ +static u8 b85_cOffset[] = { 0, '#', 0, '*'-4, 0 }; +#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)] + +/* Say whether c is a base85 numeral. */ +#define IS_B85( c ) (B85_CLASS(c) & 1) + +#if 0 /* Not used, */ +static u8 base85DigitValue( char c ){ + u8 dv = (u8)(c - '#'); + if( dv>87 ) return 0xff; + return (dv > 3)? dv-3 : dv; } +#endif -/* -** Multiply A by B. A := A * B -** -** All significant digits after the decimal point are retained. -** Trailing zeros after the decimal point are omitted as long as -** the number of digits after the decimal point is no less than -** either the number of digits in either input. -*/ -static void decimalMul(Decimal *pA, Decimal *pB){ - signed char *acc = 0; - int i, j, k; - int minFrac; +/* Width of base64 lines. Should be an integer multiple of 5. */ +#define B85_DARK_MAX 80 - if( pA==0 || pA->oom || pA->isNull - || pB==0 || pB->oom || pB->isNull - ){ - goto mul_end; - } - acc = sqlite3_malloc64( pA->nDigit + pB->nDigit + 2 ); - if( acc==0 ){ - pA->oom = 1; - goto mul_end; - } - memset(acc, 0, pA->nDigit + pB->nDigit + 2); - minFrac = pA->nFrac; - if( pB->nFracnFrac; - for(i=pA->nDigit-1; i>=0; i--){ - signed char f = pA->a[i]; - int carry = 0, x; - for(j=pB->nDigit-1, k=i+j+3; j>=0; j--, k--){ - x = acc[k] + f*pB->a[j] + carry; - acc[k] = x%10; - carry = x/10; - } - x = acc[k] + carry; - acc[k] = x%10; - acc[k-1] += x/10; - } - sqlite3_free(pA->a); - pA->a = acc; - acc = 0; - pA->nDigit += pB->nDigit + 2; - pA->nFrac += pB->nFrac; - pA->sign ^= pB->sign; - while( pA->nFrac>minFrac && pA->a[pA->nDigit-1]==0 ){ - pA->nFrac--; - pA->nDigit--; - } -mul_end: - sqlite3_free(acc); +static char * skipNonB85( char *s, int nc ){ + char c; + while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s; + return s; } -/* -** Create a new Decimal object that contains an integer power of 2. -*/ -static Decimal *decimalPow2(int N){ - Decimal *pA = 0; /* The result to be returned */ - Decimal *pX = 0; /* Multiplier */ - if( N<-20000 || N>20000 ) goto pow2_fault; - pA = decimalNewFromText("1.0", 3); - if( pA==0 || pA->oom ) goto pow2_fault; - if( N==0 ) return pA; - if( N>0 ){ - pX = decimalNewFromText("2.0", 3); - }else{ - N = -N; - pX = decimalNewFromText("0.5", 3); - } - if( pX==0 || pX->oom ) goto pow2_fault; - while( 1 /* Exit by break */ ){ - if( N & 1 ){ - decimalMul(pA, pX); - if( pA->oom ) goto pow2_fault; - } - N >>= 1; - if( N==0 ) break; - decimalMul(pX, pX); - } - decimal_free(pX); - return pA; +/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral. + * Do not use the macro form with argument expression having a side-effect.*/ +#if 0 +static char base85Numeral( u8 b ){ + return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*'); +} +#else +# define base85Numeral( dn )\ + ((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*'))) +#endif -pow2_fault: - decimal_free(pA); - decimal_free(pX); - return 0; +static char *putcs(char *pc, char *s){ + char c; + while( (c = *s++)!=0 ) *pc++ = c; + return pc; } -/* -** Use an IEEE754 binary64 ("double") to generate a new Decimal object. +/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string +** to be appended to encoded groups to limit their length to B85_DARK_MAX +** or to terminate the last group (to aid concatenation.) */ -static Decimal *decimalFromDouble(double r){ - sqlite3_int64 m, a; - int e; - int isNeg; - Decimal *pA; - Decimal *pX; - char zNum[100]; - if( r<0.0 ){ - isNeg = 1; - r = -r; - }else{ - isNeg = 0; - } - memcpy(&a,&r,sizeof(a)); - if( a==0 ){ - e = 0; - m = 0; - }else{ - e = a>>52; - m = a & ((((sqlite3_int64)1)<<52)-1); - if( e==0 ){ - m <<= 1; - }else{ - m |= ((sqlite3_int64)1)<<52; +static char* toBase85( u8 *pIn, int nbIn, char *pOut, char *pSep ){ + int nCol = 0; + while( nbIn >= 4 ){ + int nco = 5; + unsigned long qbv = (((unsigned long)pIn[0])<<24) | + (pIn[1]<<16) | (pIn[2]<<8) | pIn[3]; + while( nco > 0 ){ + unsigned nqv = (unsigned)(qbv/85UL); + unsigned char dv = qbv - 85UL*nqv; + qbv = nqv; + pOut[--nco] = base85Numeral(dv); } - while( e<1075 && m>0 && (m&1)==0 ){ - m >>= 1; - e++; + nbIn -= 4; + pIn += 4; + pOut += 5; + if( pSep && (nCol += 5)>=B85_DARK_MAX ){ + pOut = putcs(pOut, pSep); + nCol = 0; } - if( isNeg ) m = -m; - e = e - 1075; - if( e>971 ){ - return 0; /* A NaN or an Infinity */ + } + if( nbIn > 0 ){ + int nco = nbIn + 1; + unsigned long qv = *pIn++; + int nbe = 1; + while( nbe++ < nbIn ){ + qv = (qv<<8) | *pIn++; + } + nCol += nco; + while( nco > 0 ){ + u8 dv = (u8)(qv % 85); + qv /= 85; + pOut[--nco] = base85Numeral(dv); } + pOut += (nbIn+1); } - - /* At this point m is the integer significand and e is the exponent */ - sqlite3_snprintf(sizeof(zNum), zNum, "%lld", m); - pA = decimalNewFromText(zNum, (int)strlen(zNum)); - pX = decimalPow2(e); - decimalMul(pA, pX); - decimal_free(pX); - return pA; + if( pSep && nCol>0 ) pOut = putcs(pOut, pSep); + *pOut = 0; + return pOut; } -/* -** SQL Function: decimal(X) -** OR: decimal_exp(X) -** -** Convert input X into decimal and then back into text. -** -** If X is originally a float, then a full decimal expansion of that floating -** point value is done. Or if X is an 8-byte blob, it is interpreted -** as a float and similarly expanded. -** -** The decimal_exp(X) function returns the result in exponential notation. -** decimal(X) returns a complete decimal, without the e+NNN at the end. -*/ -static void decimalFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *p = decimal_new(context, argv[0], 0); - UNUSED_PARAMETER(argc); - if( p ){ - if( sqlite3_user_data(context)!=0 ){ - decimal_result_sci(context, p); - }else{ - decimal_result(context, p); +/* Decode base85 text into a byte buffer. */ +static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){ + if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; + while( ncIn>0 ){ + static signed char nboi[] = { 0, 0, 1, 2, 3, 4 }; + char *pUse = skipNonB85(pIn, ncIn); + unsigned long qv = 0L; + int nti, nbo; + ncIn -= (pUse - pIn); + pIn = pUse; + nti = (ncIn>5)? 5 : ncIn; + nbo = nboi[nti]; + if( nbo==0 ) break; + while( nti>0 ){ + char c = *pIn++; + u8 cdo = B85_DNOS(c); + --ncIn; + if( cdo==0 ) break; + qv = 85 * qv + (c - cdo); + --nti; + } + nbo -= nti; /* Adjust for early (non-digit) end of group. */ + switch( nbo ){ + case 4: + *pOut++ = (qv >> 24)&0xff; + /* FALLTHRU */ + case 3: + *pOut++ = (qv >> 16)&0xff; + /* FALLTHRU */ + case 2: + *pOut++ = (qv >> 8)&0xff; + /* FALLTHRU */ + case 1: + *pOut++ = qv&0xff; + /* FALLTHRU */ + case 0: + break; } - decimal_free(p); } + return pOut; } -/* -** Compare text in decimal order. -*/ -static int decimalCollFunc( - void *notUsed, - int nKey1, const void *pKey1, - int nKey2, const void *pKey2 -){ - const unsigned char *zA = (const unsigned char*)pKey1; - const unsigned char *zB = (const unsigned char*)pKey2; - Decimal *pA = decimalNewFromText((const char*)zA, nKey1); - Decimal *pB = decimalNewFromText((const char*)zB, nKey2); - int rc; - UNUSED_PARAMETER(notUsed); - if( pA==0 || pB==0 ){ - rc = 0; - }else{ - rc = decimal_cmp(pA, pB); +#ifndef OMIT_BASE85_CHECKER +/* Say whether input char sequence is all (base85 and/or whitespace).*/ +static int allBase85( char *p, int len ){ + char c; + while( len-- > 0 && (c = *p++) != 0 ){ + if( !IS_B85(c) && !isspace(c) ) return 0; } - decimal_free(pA); - decimal_free(pB); - return rc; + return 1; } +#endif +#ifndef BASE85_STANDALONE -/* -** SQL Function: decimal_add(X, Y) -** decimal_sub(X, Y) -** -** Return the sum or difference of X and Y. -*/ -static void decimalAddFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *pA = decimal_new(context, argv[0], 1); - Decimal *pB = decimal_new(context, argv[1], 1); - UNUSED_PARAMETER(argc); - decimal_add(pA, pB); - decimal_result(context, pA); - decimal_free(pA); - decimal_free(pB); -} -static void decimalSubFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *pA = decimal_new(context, argv[0], 1); - Decimal *pB = decimal_new(context, argv[1], 1); - UNUSED_PARAMETER(argc); - if( pB ){ - pB->sign = !pB->sign; - decimal_add(pA, pB); - decimal_result(context, pA); +#ifndef OMIT_BASE85_CHECKER +/* This function does the work for the SQLite is_base85(t) UDF. */ +static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){ + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_TEXT: + { + int rv = allBase85( (char *)sqlite3_value_text(av[0]), + sqlite3_value_bytes(av[0]) ); + sqlite3_result_int(context, rv); + } + break; + case SQLITE_NULL: + sqlite3_result_null(context); + break; + default: + sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1); + return; } - decimal_free(pA); - decimal_free(pB); } +#endif -/* Aggregate funcion: decimal_sum(X) -** -** Works like sum() except that it uses decimal arithmetic for unlimited -** precision. -*/ -static void decimalSumStep( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *p; - Decimal *pArg; - UNUSED_PARAMETER(argc); - p = sqlite3_aggregate_context(context, sizeof(*p)); - if( p==0 ) return; - if( !p->isInit ){ - p->isInit = 1; - p->a = sqlite3_malloc(2); - if( p->a==0 ){ - p->oom = 1; - }else{ - p->a[0] = 0; +/* This function does the work for the SQLite base85(x) UDF. */ +static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ + sqlite3_int64 nb, nc, nv = sqlite3_value_bytes(av[0]); + int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), + SQLITE_LIMIT_LENGTH, -1); + char *cBuf; + u8 *bBuf; + assert(na==1); + switch( sqlite3_value_type(av[0]) ){ + case SQLITE_BLOB: + nb = nv; + /* ulongs tail newlines tailenc+nul*/ + nc = 5*(nv/4) + nv%4 + nv/64+1 + 2; + if( nvMax < nc ){ + sqlite3_result_error(context, "blob expanded to base85 too big", -1); + return; + } + bBuf = (u8*)sqlite3_value_blob(av[0]); + if( !bBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_text(context,"",-1,SQLITE_STATIC); + break; } - p->nDigit = 1; - p->nFrac = 0; + cBuf = sqlite3_malloc64(nc); + if( !cBuf ) goto memFail; + nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf); + sqlite3_result_text(context, cBuf, nc, sqlite3_free); + break; + case SQLITE_TEXT: + nc = nv; + nb = 4*(nv/5) + nv%5; /* may overestimate */ + if( nvMax < nb ){ + sqlite3_result_error(context, "blob from base85 may be too big", -1); + return; + }else if( nb<1 ){ + nb = 1; + } + cBuf = (char *)sqlite3_value_text(av[0]); + if( !cBuf ){ + if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ + goto memFail; + } + sqlite3_result_zeroblob(context, 0); + break; + } + bBuf = sqlite3_malloc64(nb); + if( !bBuf ) goto memFail; + nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf); + sqlite3_result_blob(context, bBuf, nb, sqlite3_free); + break; + default: + sqlite3_result_error(context, "base85 accepts only blob or text.", -1); + return; } - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pArg = decimal_new(context, argv[0], 1); - decimal_add(p, pArg); - decimal_free(pArg); -} -static void decimalSumInverse( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *p; - Decimal *pArg; - UNUSED_PARAMETER(argc); - p = sqlite3_aggregate_context(context, sizeof(*p)); - if( p==0 ) return; - if( sqlite3_value_type(argv[0])==SQLITE_NULL ) return; - pArg = decimal_new(context, argv[0], 1); - if( pArg ) pArg->sign = !pArg->sign; - decimal_add(p, pArg); - decimal_free(pArg); -} -static void decimalSumValue(sqlite3_context *context){ - Decimal *p = sqlite3_aggregate_context(context, 0); - if( p==0 ) return; - decimal_result(context, p); -} -static void decimalSumFinalize(sqlite3_context *context){ - Decimal *p = sqlite3_aggregate_context(context, 0); - if( p==0 ) return; - decimal_result(context, p); - decimal_clear(p); + return; + memFail: + sqlite3_result_error(context, "base85 OOM", -1); } /* -** SQL Function: decimal_mul(X, Y) -** -** Return the product of X and Y. +** Establish linkage to running SQLite library. */ -static void decimalMulFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - Decimal *pA = decimal_new(context, argv[0], 1); - Decimal *pB = decimal_new(context, argv[1], 1); - UNUSED_PARAMETER(argc); - if( pA==0 || pA->oom || pA->isNull - || pB==0 || pB->oom || pB->isNull - ){ - goto mul_end; - } - decimalMul(pA, pB); - if( pA->oom ){ - goto mul_end; - } - decimal_result(context, pA); +#ifndef SQLITE_SHELL_EXTFUNCS +#ifdef _WIN32 -mul_end: - decimal_free(pA); - decimal_free(pB); +#endif +int sqlite3_base85_init +#else +static int sqlite3_base85_init +#endif +(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ + SQLITE_EXTENSION_INIT2(pApi); + (void)pzErr; +#ifndef OMIT_BASE85_CHECKER + { + int rc = sqlite3_create_function + (db, "is_base85", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8, + 0, is_base85, 0, 0); + if( rc!=SQLITE_OK ) return rc; + } +#endif + return sqlite3_create_function + (db, "base85", 1, + SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, + 0, base85, 0, 0); } /* -** SQL Function: decimal_pow2(N) -** -** Return the N-th power of 2. N must be an integer. +** Define some macros to allow this extension to be built into the shell +** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This +** allows shell.c, as distributed, to have this extension built in. */ -static void decimalPow2Func( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){ - Decimal *pA = decimalPow2(sqlite3_value_int(argv[0])); - decimal_result_sci(context, pA); - decimal_free(pA); - } -} - -#ifdef _WIN32 - -#endif -int sqlite3_decimal_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ - int rc = SQLITE_OK; - static const struct { - const char *zFuncName; - int nArg; - int iArg; - void (*xFunc)(sqlite3_context*,int,sqlite3_value**); - } aFunc[] = { - { "decimal", 1, 0, decimalFunc }, - { "decimal_exp", 1, 1, decimalFunc }, - { "decimal_cmp", 2, 0, decimalCmpFunc }, - { "decimal_add", 2, 0, decimalAddFunc }, - { "decimal_sub", 2, 0, decimalSubFunc }, - { "decimal_mul", 2, 0, decimalMulFunc }, - { "decimal_pow2", 1, 0, decimalPow2Func }, - }; - unsigned int i; - (void)pzErrMsg; /* Unused parameter */ +# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0) +# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ - SQLITE_EXTENSION_INIT2(pApi); +#else /* standalone program */ - for(i=0; i<(int)(sizeof(aFunc)/sizeof(aFunc[0])) && rc==SQLITE_OK; i++){ - rc = sqlite3_create_function(db, aFunc[i].zFuncName, aFunc[i].nArg, - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, - aFunc[i].iArg ? db : 0, aFunc[i].xFunc, 0, 0); +int main(int na, char *av[]){ + int cin; + int rc = 0; + u8 bBuf[4*(B85_DARK_MAX/5)]; + char cBuf[5*(sizeof(bBuf)/4)+2]; + size_t nio; +# ifndef OMIT_BASE85_CHECKER + int b85Clean = 1; +# endif + char rw; + FILE *fb = 0, *foc = 0; + char fmode[3] = "xb"; + if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){ + sayHelp(); + return 0; } - if( rc==SQLITE_OK ){ - rc = sqlite3_create_window_function(db, "decimal_sum", 1, - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, 0, - decimalSumStep, decimalSumFinalize, - decimalSumValue, decimalSumInverse, 0); + fmode[0] = rw; + if( av[2][0]=='-' && av[2][1]==0 ){ + switch( rw ){ + case 'r': + fb = stdin; + setmode(fileno(stdin), O_BINARY); + break; + case 'w': + fb = stdout; + setmode(fileno(stdout), O_BINARY); + break; + } + }else{ + fb = fopen(av[2], fmode); + foc = fb; } - if( rc==SQLITE_OK ){ - rc = sqlite3_create_collation(db, "decimal", SQLITE_UTF8, - 0, decimalCollFunc); + if( !fb ){ + fprintf(stderr, "Cannot open %s for %c\n", av[2], rw); + rc = 1; + }else{ + switch( rw ){ + case 'r': + while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){ + toBase85( bBuf, (int)nio, cBuf, 0 ); + fprintf(stdout, "%s\n", cBuf); + } + break; + case 'w': + while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){ + int nc = strlen(cBuf); + size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf; + if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1; +#ifndef OMIT_BASE85_CHECKER + b85Clean &= allBase85( cBuf, nc ); +#endif + } + break; + default: + sayHelp(); + rc = 1; + } + if( foc ) fclose(foc); } +# ifndef OMIT_BASE85_CHECKER + if( !b85Clean ){ + fprintf(stderr, "Base85 input had non-base85 dark or control content.\n"); + } +# endif return rc; } -/************************* End ../ext/misc/decimal.c ********************/ -#undef sqlite3_base_init -#define sqlite3_base_init sqlite3_base64_init -/************************* Begin ../ext/misc/base64.c ******************/ +#endif + +/************************* End ext/misc/base85.c ********************/ +/************************* Begin ext/misc/ieee754.c ******************/ /* -** 2022-11-18 +** 2013-04-17 +** +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: +** +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. +** +****************************************************************************** +** +** This SQLite extension implements functions for the exact display +** and input of IEEE754 Binary64 floating-point numbers. +** +** ieee754(X) +** ieee754(Y,Z) ** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: +** In the first form, the value X should be a floating-point number. +** The function will return a string of the form 'ieee754(Y,Z)' where +** Y and Z are integers such that X==Y*pow(2,Z). ** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. +** In the second form, Y and Z are integers which are the mantissa and +** base-2 exponent of a new floating point number. The function returns +** a floating-point value equal to Y*pow(2,Z). ** -************************************************************************* +** Examples: ** -** This is a SQLite extension for converting in either direction -** between a (binary) blob and base64 text. Base64 can transit a -** sane USASCII channel unmolested. It also plays nicely in CSV or -** written as TCL brace-enclosed literals or SQL string literals, -** and can be used unmodified in XML-like documents. +** ieee754(2.0) -> 'ieee754(2,0)' +** ieee754(45.25) -> 'ieee754(181,-2)' +** ieee754(2, 0) -> 2.0 +** ieee754(181, -2) -> 45.25 ** -** This is an independent implementation of conversions specified in -** RFC 4648, done on the above date by the author (Larry Brasfield) -** who thereby has the right to put this into the public domain. +** Two additional functions break apart the one-argument ieee754() +** result into separate integer values: ** -** The conversions meet RFC 4648 requirements, provided that this -** C source specifies that line-feeds are included in the encoded -** data to limit visible line lengths to 72 characters and to -** terminate any encoded blob having non-zero length. +** ieee754_mantissa(45.25) -> 181 +** ieee754_exponent(45.25) -> -2 ** -** Length limitations are not imposed except that the runtime -** SQLite string or blob length limits are respected. Otherwise, -** any length binary sequence can be represented and recovered. -** Generated base64 sequences, with their line-feeds included, -** can be concatenated; the result converted back to binary will -** be the concatenation of the represented binary sequences. +** These functions convert binary64 numbers into blobs and back again. ** -** This SQLite3 extension creates a function, base64(x), which -** either: converts text x containing base64 to a returned blob; -** or converts a blob x to returned text containing base64. An -** error will be thrown for other input argument types. +** ieee754_from_blob(x'3ff0000000000000') -> 1.0 +** ieee754_to_blob(1.0) -> x'3ff0000000000000' ** -** This code relies on UTF-8 encoding only with respect to the -** meaning of the first 128 (7-bit) codes matching that of USASCII. -** It will fail miserably if somehow made to try to convert EBCDIC. -** Because it is table-driven, it could be enhanced to handle that, -** but the world and SQLite have moved on from that anachronism. +** In all single-argument functions, if the argument is an 8-byte blob +** then that blob is interpreted as a big-endian binary64 value. +** +** +** EXACT DECIMAL REPRESENTATION OF BINARY64 VALUES +** ----------------------------------------------- +** +** This extension in combination with the separate 'decimal' extension +** can be used to compute the exact decimal representation of binary64 +** values. To begin, first compute a table of exponent values: +** +** CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); +** WITH RECURSIVE c(x,v) AS ( +** VALUES(0,'1') +** UNION ALL +** SELECT x+1, decimal_mul(v,'2') FROM c WHERE x+1<=971 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** WITH RECURSIVE c(x,v) AS ( +** VALUES(-1,'0.5') +** UNION ALL +** SELECT x-1, decimal_mul(v,'0.5') FROM c WHERE x-1>=-1075 +** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** +** Then, to compute the exact decimal representation of a floating +** point value (the value 47.49 is used in the example) do: +** +** WITH c(n) AS (VALUES(47.49)) +** ---------------^^^^^---- Replace with whatever you want +** SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n); +** +** Here is a query to show various boundry values for the binary64 +** number format: +** +** WITH c(name,bin) AS (VALUES +** ('minimum positive value', x'0000000000000001'), +** ('maximum subnormal value', x'000fffffffffffff'), +** ('minimum positive normal value', x'0010000000000000'), +** ('maximum value', x'7fefffffffffffff')) +** SELECT c.name, decimal_mul(ieee754_mantissa(c.bin),pow2.v) +** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.bin); ** -** To build the extension: -** Set shell variable SQDIR= -** *Nix: gcc -O2 -shared -I$SQDIR -fPIC -o base64.so base64.c -** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR -o base64.dylib base64.c -** Win32: gcc -O2 -shared -I%SQDIR% -o base64.dll base64.c -** Win32: cl /Os -I%SQDIR% base64.c -link -dll -out:base64.dll */ - -#include - /* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include -#ifndef deliberate_fall_through -/* Quiet some compilers about some of our intentional code. */ -# if GCC_VERSION>=7000000 -# define deliberate_fall_through __attribute__((fallthrough)); -# else -# define deliberate_fall_through -# endif -#endif - -SQLITE_EXTENSION_INIT1; - -#define PC 0x80 /* pad character */ -#define WS 0x81 /* whitespace */ -#define ND 0x82 /* Not above or digit-value */ -#define PAD_CHAR '=' - -#ifndef U8_TYPEDEF -/* typedef unsigned char u8; */ -#define U8_TYPEDEF +/* Mark a function parameter as unused, to suppress nuisance compiler +** warnings. */ +#ifndef UNUSED_PARAMETER +# define UNUSED_PARAMETER(X) (void)(X) #endif -/* Decoding table, ASCII (7-bit) value to base 64 digit value or other */ -static const u8 b64DigitValues[128] = { - /* HT LF VT FF CR */ - ND,ND,ND,ND, ND,ND,ND,ND, ND,WS,WS,WS, WS,WS,ND,ND, - /* US */ - ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,ND, - /*sp + / */ - WS,ND,ND,ND, ND,ND,ND,ND, ND,ND,ND,62, ND,ND,ND,63, - /* 0 1 5 9 = */ - 52,53,54,55, 56,57,58,59, 60,61,ND,ND, ND,PC,ND,ND, - /* A O */ - ND, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11,12,13,14, - /* P Z */ - 15,16,17,18, 19,20,21,22, 23,24,25,ND, ND,ND,ND,ND, - /* a o */ - ND,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40, - /* p z */ - 41,42,43,44, 45,46,47,48, 49,50,51,ND, ND,ND,ND,ND -}; - -static const char b64Numerals[64+1] -= "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +/* +** Implementation of the ieee754() function +*/ +static void ieee754func( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + if( argc==1 ){ + sqlite3_int64 m, a; + double r; + int e; + int isNeg; + char zResult[100]; + assert( sizeof(m)==sizeof(r) ); + if( sqlite3_value_type(argv[0])==SQLITE_BLOB + && sqlite3_value_bytes(argv[0])==sizeof(r) + ){ + const unsigned char *x = sqlite3_value_blob(argv[0]); + unsigned int i; + sqlite3_uint64 v = 0; + for(i=0; i>52; + m = a & ((((sqlite3_int64)1)<<52)-1); + if( e==0 ){ + m <<= 1; + }else{ + m |= ((sqlite3_int64)1)<<52; + } + while( e<1075 && m>0 && (m&1)==0 ){ + m >>= 1; + e++; + } + if( isNeg ) m = -m; + } + switch( *(int*)sqlite3_user_data(context) ){ + case 0: + sqlite3_snprintf(sizeof(zResult), zResult, "ieee754(%lld,%d)", + m, e-1075); + sqlite3_result_text(context, zResult, -1, SQLITE_TRANSIENT); + break; + case 1: + sqlite3_result_int64(context, m); + break; + case 2: + sqlite3_result_int(context, e-1075); + break; + } + }else{ + sqlite3_int64 m, e, a; + double r; + int isNeg = 0; + m = sqlite3_value_int64(argv[0]); + e = sqlite3_value_int64(argv[1]); -#define BX_DV_PROTO(c) \ - ((((u8)(c))<0x80)? (u8)(b64DigitValues[(u8)(c)]) : 0x80) -#define IS_BX_DIGIT(bdp) (((u8)(bdp))<0x80) -#define IS_BX_WS(bdp) ((bdp)==WS) -#define IS_BX_PAD(bdp) ((bdp)==PC) -#define BX_NUMERAL(dv) (b64Numerals[(u8)(dv)]) -/* Width of base64 lines. Should be an integer multiple of 4. */ -#define B64_DARK_MAX 72 + /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */ + if( e>10000 ){ + e = 10000; + }else if( e<-10000 ){ + e = -10000; + } -/* Encode a byte buffer into base64 text with linefeeds appended to limit -** encoded group lengths to B64_DARK_MAX or to terminate the last group. -*/ -static char* toBase64( u8 *pIn, int nbIn, char *pOut ){ - int nCol = 0; - while( nbIn >= 3 ){ - /* Do the bit-shuffle, exploiting unsigned input to avoid masking. */ - pOut[0] = BX_NUMERAL(pIn[0]>>2); - pOut[1] = BX_NUMERAL(((pIn[0]<<4)|(pIn[1]>>4))&0x3f); - pOut[2] = BX_NUMERAL(((pIn[1]&0xf)<<2)|(pIn[2]>>6)); - pOut[3] = BX_NUMERAL(pIn[2]&0x3f); - pOut += 4; - nbIn -= 3; - pIn += 3; - if( (nCol += 4)>=B64_DARK_MAX || nbIn<=0 ){ - *pOut++ = '\n'; - nCol = 0; + if( m<0 ){ + if( m<(-9223372036854775807LL) ) return; + isNeg = 1; + m = -m; + }else if( m==0 && e>-1000 && e<1000 ){ + sqlite3_result_double(context, 0.0); + return; } - } - if( nbIn > 0 ){ - signed char nco = nbIn+1; - int nbe; - unsigned long qv = *pIn++; - for( nbe=1; nbe<3; ++nbe ){ - qv <<= 8; - if( nbe>32)&0xffe00000 ){ + m >>= 1; + e++; } - for( nbe=3; nbe>=0; --nbe ){ - char ce = (nbe>= 6; - pOut[nbe] = ce; + while( m!=0 && ((m>>32)&0xfff00000)==0 ){ + m <<= 1; + e--; } - pOut += 4; - *pOut++ = '\n'; + e += 1075; + if( e<=0 ){ + /* Subnormal */ + if( 1-e >= 64 ){ + m = 0; + }else{ + m >>= 1-e; + } + e = 0; + }else if( e>0x7ff ){ + e = 0x7ff; + } + a = m & ((((sqlite3_int64)1)<<52)-1); + a |= e<<52; + if( isNeg ) a |= ((sqlite3_uint64)1)<<63; + memcpy(&r, &a, sizeof(r)); + sqlite3_result_double(context, r); } - *pOut = 0; - return pOut; -} - -/* Skip over text which is not base64 numeral(s). */ -static char * skipNonB64( char *s, int nc ){ - char c; - while( nc-- > 0 && (c = *s) && !IS_BX_DIGIT(BX_DV_PROTO(c)) ) ++s; - return s; } -/* Decode base64 text into a byte buffer. */ -static u8* fromBase64( char *pIn, int ncIn, u8 *pOut ){ - if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; - while( ncIn>0 && *pIn!=PAD_CHAR ){ - static signed char nboi[] = { 0, 0, 1, 2, 3 }; - char *pUse = skipNonB64(pIn, ncIn); - unsigned long qv = 0L; - int nti, nbo, nac; - ncIn -= (pUse - pIn); - pIn = pUse; - nti = (ncIn>4)? 4 : ncIn; - ncIn -= nti; - nbo = nboi[nti]; - if( nbo==0 ) break; - for( nac=0; nac<4; ++nac ){ - char c = (nac>8) & 0xff; - case 1: - pOut[0] = (qv>>16) & 0xff; + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(context, r); + } +} +static void ieee754func_to_blob( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_FLOAT + || sqlite3_value_type(argv[0])==SQLITE_INTEGER + ){ + double r = sqlite3_value_double(argv[0]); + sqlite3_uint64 v; + unsigned char a[sizeof(r)]; + unsigned int i; + memcpy(&v, &r, sizeof(r)); + for(i=1; i<=sizeof(r); i++){ + a[sizeof(r)-i] = v&0xff; + v >>= 8; } - pOut += nbo; + sqlite3_result_blob(context, a, sizeof(r), SQLITE_TRANSIENT); } - return pOut; } -/* This function does the work for the SQLite base64(x) UDF. */ -static void base64(sqlite3_context *context, int na, sqlite3_value *av[]){ - int nb, nc, nv = sqlite3_value_bytes(av[0]); - int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), - SQLITE_LIMIT_LENGTH, -1); - char *cBuf; - u8 *bBuf; - assert(na==1); - switch( sqlite3_value_type(av[0]) ){ - case SQLITE_BLOB: - nb = nv; - nc = 4*(nv+2/3); /* quads needed */ - nc += (nc+(B64_DARK_MAX-1))/B64_DARK_MAX + 1; /* LFs and a 0-terminator */ - if( nvMax < nc ){ - sqlite3_result_error(context, "blob expanded to base64 too big", -1); - return; - } - bBuf = (u8*)sqlite3_value_blob(av[0]); - if( !bBuf ){ - if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ - goto memFail; - } - sqlite3_result_text(context,"",-1,SQLITE_STATIC); - break; - } - cBuf = sqlite3_malloc(nc); - if( !cBuf ) goto memFail; - nc = (int)(toBase64(bBuf, nb, cBuf) - cBuf); - sqlite3_result_text(context, cBuf, nc, sqlite3_free); - break; - case SQLITE_TEXT: - nc = nv; - nb = 3*((nv+3)/4); /* may overestimate due to LF and padding */ - if( nvMax < nb ){ - sqlite3_result_error(context, "blob from base64 may be too big", -1); - return; - }else if( nb<1 ){ - nb = 1; - } - cBuf = (char *)sqlite3_value_text(av[0]); - if( !cBuf ){ - if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ - goto memFail; - } - sqlite3_result_zeroblob(context, 0); - break; - } - bBuf = sqlite3_malloc(nb); - if( !bBuf ) goto memFail; - nb = (int)(fromBase64(cBuf, nc, bBuf) - bBuf); - sqlite3_result_blob(context, bBuf, nb, sqlite3_free); - break; - default: - sqlite3_result_error(context, "base64 accepts only blob or text", -1); - return; +/* +** Functions to convert between 64-bit integers and floats. +** +** The bit patterns are copied. The numeric values are different. +*/ +static void ieee754func_from_int( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_INTEGER ){ + double r; + sqlite3_int64 v = sqlite3_value_int64(argv[0]); + memcpy(&r, &v, sizeof(r)); + sqlite3_result_double(context, r); + } +} +static void ieee754func_to_int( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + UNUSED_PARAMETER(argc); + if( sqlite3_value_type(argv[0])==SQLITE_FLOAT ){ + double r = sqlite3_value_double(argv[0]); + sqlite3_uint64 v; + memcpy(&v, &r, sizeof(v)); + sqlite3_result_int64(context, v); } - return; - memFail: - sqlite3_result_error(context, "base64 OOM", -1); } /* -** Establish linkage to running SQLite library. +** SQL Function: ieee754_inc(r,N) +** +** Move the floating point value r by N quantums and return the new +** values. +** +** Behind the scenes: this routine merely casts r into a 64-bit unsigned +** integer, adds N, then casts the value back into float. +** +** Example: To find the smallest positive number: +** +** SELECT ieee754_inc(0.0,+1); */ -#ifndef SQLITE_SHELL_EXTFUNCS +static void ieee754inc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + double r; + sqlite3_int64 N; + sqlite3_uint64 m1, m2; + double r2; + UNUSED_PARAMETER(argc); + r = sqlite3_value_double(argv[0]); + N = sqlite3_value_int64(argv[1]); + memcpy(&m1, &r, 8); + m2 = m1 + N; + memcpy(&r2, &m2, 8); + sqlite3_result_double(context, r2); +} + + #ifdef _WIN32 #endif -int sqlite3_base_init -#else -static int sqlite3_base64_init -#endif -(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ +int sqlite3_ieee_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + static const struct { + char *zFName; + int nArg; + int iAux; + void (*xFunc)(sqlite3_context*,int,sqlite3_value**); + } aFunc[] = { + { "ieee754", 1, 0, ieee754func }, + { "ieee754", 2, 0, ieee754func }, + { "ieee754_mantissa", 1, 1, ieee754func }, + { "ieee754_exponent", 1, 2, ieee754func }, + { "ieee754_to_blob", 1, 0, ieee754func_to_blob }, + { "ieee754_from_blob", 1, 0, ieee754func_from_blob }, + { "ieee754_to_int", 1, 0, ieee754func_to_int }, + { "ieee754_from_int", 1, 0, ieee754func_from_int }, + { "ieee754_inc", 2, 0, ieee754inc }, + }; + unsigned int i; + int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); - (void)pzErr; - return sqlite3_create_function - (db, "base64", 1, - SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, - 0, base64, 0, 0); + (void)pzErrMsg; /* Unused parameter */ + for(i=0; i= 0 ) +** for each produced value (independent of production time ordering.) ** -** The encoding used resembles Ascii85, but was devised by the author -** (Larry Brasfield) before Mozilla, Adobe, ZMODEM or other Ascii85 -** variant sources existed, in the 1984 timeframe on a VAX mainframe. -** Further, this is an independent implementation of a base85 system. -** Hence, the author has rightfully put this into the public domain. +** All parameters must be either integer or convertable to integer. +** The start parameter is required. +** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff) +** The step parameter defaults to 1 and 0 is treated as 1. ** -** Base85 numerals are taken from the set of 7-bit USASCII codes, -** excluding control characters and Space ! " ' ( ) { | } ~ Del -** in code order representing digit values 0 to 84 (base 10.) +** Examples: ** -** Groups of 4 bytes, interpreted as big-endian 32-bit values, -** are represented as 5-digit base85 numbers with MS to LS digit -** order. Groups of 1-3 bytes are represented with 2-4 digits, -** still big-endian but 8-24 bit values. (Using big-endian yields -** the simplest transition to byte groups smaller than 4 bytes. -** These byte groups can also be considered base-256 numbers.) -** Groups of 0 bytes are represented with 0 digits and vice-versa. -** No pad characters are used; Encoded base85 numeral sequence -** (aka "group") length maps 1-to-1 to the decoded binary length. +** SELECT * FROM generate_series(0,100,5); ** -** Any character not in the base85 numeral set delimits groups. -** When base85 is streamed or stored in containers of indefinite -** size, newline is used to separate it into sub-sequences of no -** more than 80 digits so that fgets() can be used to read it. +** The query above returns integers from 0 through 100 counting by steps +** of 5. In other words, 0, 5, 10, 15, ..., 90, 95, 100. There are a total +** of 21 rows. ** -** Length limitations are not imposed except that the runtime -** SQLite string or blob length limits are respected. Otherwise, -** any length binary sequence can be represented and recovered. -** Base85 sequences can be concatenated by separating them with -** a non-base85 character; the conversion to binary will then -** be the concatenation of the represented binary sequences. +** SELECT * FROM generate_series(0,100); +** +** Integers from 0 through 100 with a step size of 1. 101 rows. +** +** SELECT * FROM generate_series(20) LIMIT 10; +** +** Integers 20 through 29. 10 rows. +** +** SELECT * FROM generate_series(0,-100,-5); +** +** Integers 0 -5 -10 ... -100. 21 rows. +** +** SELECT * FROM generate_series(0,-1); +** +** Empty sequence. +** +** HOW IT WORKS +** +** The generate_series "function" is really a virtual table with the +** following schema: +** +** CREATE TABLE generate_series( +** value, +** start HIDDEN, +** stop HIDDEN, +** step HIDDEN +** ); +** +** The virtual table also has a rowid which is an alias for the value. +** +** Function arguments in queries against this virtual table are translated +** into equality constraints against successive hidden columns. In other +** words, the following pairs of queries are equivalent to each other: +** +** SELECT * FROM generate_series(0,100,5); +** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5; +** +** SELECT * FROM generate_series(0,100); +** SELECT * FROM generate_series WHERE start=0 AND stop=100; +** +** SELECT * FROM generate_series(20) LIMIT 10; +** SELECT * FROM generate_series WHERE start=20 LIMIT 10; +** +** The generate_series virtual table implementation leaves the xCreate method +** set to NULL. This means that it is not possible to do a CREATE VIRTUAL +** TABLE command with "generate_series" as the USING argument. Instead, there +** is a single generate_series virtual table that is always available without +** having to be created first. +** +** The xBestIndex method looks for equality constraints against the hidden +** start, stop, and step columns, and if present, it uses those constraints +** to bound the sequence of generated values. If the equality constraints +** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step. +** xBestIndex returns a small cost when both start and stop are available, +** and a very large cost if either start or stop are unavailable. This +** encourages the query planner to order joins such that the bounds of the +** series are well-defined. +** +** Update on 2024-08-22: +** xBestIndex now also looks for equality and inequality constraints against +** the value column and uses those constraints as additional bounds against +** the sequence range. Thus, a query like this: +** +** SELECT value FROM generate_series($SA,$EA) +** WHERE value BETWEEN $SB AND $EB; +** +** Is logically the same as: +** +** SELECT value FROM generate_series(max($SA,$SB),min($EA,$EB)); +** +** Constraints on the value column can server as substitutes for constraints +** on the hidden start and stop columns. So, the following two queries +** are equivalent: +** +** SELECT value FROM generate_series($S,$E); +** SELECT value FROM generate_series WHERE value BETWEEN $S and $E; +** +*/ +/* #include "sqlite3ext.h" */ +SQLITE_EXTENSION_INIT1 +#include +#include +#include +#include -** The standalone program either converts base85 on stdin to create -** a binary file or converts a binary file to base85 on stdout. -** Read or make it blurt its help for invocation details. +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* series_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result. ** -** The SQLite3 extension creates a function, base85(x), which will -** either convert text base85 to a blob or a blob to text base85 -** and return the result (or throw an error for other types.) -** Unless built with OMIT_BASE85_CHECKER defined, it also creates a -** function, is_base85(t), which returns 1 iff the text t contains -** nothing other than base85 numerals and whitespace, or 0 otherwise. +** iOBase, iOTerm, and iOStep are the original values of the +** start=, stop=, and step= constraints on the query. These are +** the values reported by the start, stop, and step columns of the +** virtual table. ** -** To build the extension: -** Set shell variable SQDIR= -** and variable OPTS to -DOMIT_BASE85_CHECKER if is_base85() unwanted. -** *Nix: gcc -O2 -shared -I$SQDIR $OPTS -fPIC -o base85.so base85.c -** OSX: gcc -O2 -dynamiclib -fPIC -I$SQDIR $OPTS -o base85.dylib base85.c -** Win32: gcc -O2 -shared -I%SQDIR% %OPTS% -o base85.dll base85.c -** Win32: cl /Os -I%SQDIR% %OPTS% base85.c -link -dll -out:base85.dll +** iBase, iTerm, iStep, and bDescp are the actual values used to generate +** the sequence. These might be different from the iOxxxx values. +** For example in ** -** To build the standalone program, define PP symbol BASE85_STANDALONE. Eg. -** *Nix or OSX: gcc -O2 -DBASE85_STANDALONE base85.c -o base85 -** Win32: gcc -O2 -DBASE85_STANDALONE -o base85.exe base85.c -** Win32: cl /Os /MD -DBASE85_STANDALONE base85.c +** SELECT value FROM generate_series(1,11,2) +** WHERE value BETWEEN 4 AND 8; +** +** The iOBase is 1, but the iBase is 5. iOTerm is 11 but iTerm is 7. +** Another example: +** +** SELECT value FROM generate_series(1,15,3) ORDER BY value DESC; +** +** The cursor initialization for the above query is: +** +** iOBase = 1 iBase = 13 +** iOTerm = 15 iTerm = 1 +** iOStep = 3 iStep = 3 bDesc = 1 +** +** The actual step size is unsigned so that can have a value of +** +9223372036854775808 which is needed for querys like this: +** +** SELECT value +** FROM generate_series(9223372036854775807, +** -9223372036854775808, +** -9223372036854775808) +** ORDER BY value ASC; +** +** The setup for the previous query will be: +** +** iOBase = 9223372036854775807 iBase = -1 +** iOTerm = -9223372036854775808 iTerm = 9223372036854775807 +** iOStep = -9223372036854775808 iStep = 9223372036854775808 bDesc = 0 */ +/* typedef unsigned char u8; */ +typedef struct series_cursor series_cursor; +struct series_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3_int64 iOBase; /* Original starting value ("start") */ + sqlite3_int64 iOTerm; /* Original terminal value ("stop") */ + sqlite3_int64 iOStep; /* Original step value */ + sqlite3_int64 iBase; /* Starting value to actually use */ + sqlite3_int64 iTerm; /* Terminal value to actually use */ + sqlite3_uint64 iStep; /* The step size */ + sqlite3_int64 iValue; /* Current value */ + u8 bDesc; /* iStep is really negative */ + u8 bDone; /* True if stepped past last element */ +}; -#include -#include -#include -#include -#ifndef OMIT_BASE85_CHECKER -# include -#endif +/* +** Computed the difference between two 64-bit signed integers using a +** convoluted computation designed to work around the silly restriction +** against signed integer overflow in C. +*/ +static sqlite3_uint64 span64(sqlite3_int64 a, sqlite3_int64 b){ + assert( a>=b ); + return (*(sqlite3_uint64*)&a) - (*(sqlite3_uint64*)&b); +} -#ifndef BASE85_STANDALONE +/* +** Add or substract an unsigned 64-bit integer from a signed 64-bit integer +** and return the new signed 64-bit integer. +*/ +static sqlite3_int64 add64(sqlite3_int64 a, sqlite3_uint64 b){ + sqlite3_uint64 x = *(sqlite3_uint64*)&a; + x += b; + return *(sqlite3_int64*)&x; +} +static sqlite3_int64 sub64(sqlite3_int64 a, sqlite3_uint64 b){ + sqlite3_uint64 x = *(sqlite3_uint64*)&a; + x -= b; + return *(sqlite3_int64*)&x; +} -/* # include "sqlite3ext.h" */ +/* +** The seriesConnect() method is invoked to create a new +** series_vtab that describes the generate_series virtual table. +** +** Think of this routine as the constructor for series_vtab objects. +** +** All this routine needs to do is: +** +** (1) Allocate the series_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against generate_series will look like. +*/ +static int seriesConnect( + sqlite3 *db, + void *pUnused, + int argcUnused, const char *const*argvUnused, + sqlite3_vtab **ppVtab, + char **pzErrUnused +){ + sqlite3_vtab *pNew; + int rc; -SQLITE_EXTENSION_INIT1; +/* Column numbers */ +#define SERIES_COLUMN_ROWID (-1) +#define SERIES_COLUMN_VALUE 0 +#define SERIES_COLUMN_START 1 +#define SERIES_COLUMN_STOP 2 +#define SERIES_COLUMN_STEP 3 -#else + (void)pUnused; + (void)argcUnused; + (void)argvUnused; + (void)pzErrUnused; + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(value,start hidden,stop hidden,step hidden)"); + if( rc==SQLITE_OK ){ + pNew = *ppVtab = sqlite3_malloc64( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + } + return rc; +} -# ifdef _WIN32 -# include -# include -# else -# define setmode(fd,m) -# endif +/* +** This method is the destructor for series_cursor objects. +*/ +static int seriesDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} -static char *zHelp = - "Usage: base85 \n" - " is either -r to read or -w to write ,\n" - " content to be converted to/from base85 on stdout/stdin.\n" - " names a binary file to be rendered or created.\n" - " Or, the name '-' refers to the stdin or stdout stream.\n" - ; +/* +** Constructor for a new series_cursor object. +*/ +static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){ + series_cursor *pCur; + (void)pUnused; + pCur = sqlite3_malloc64( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + *ppCursor = &pCur->base; + return SQLITE_OK; +} -static void sayHelp(){ - printf("%s", zHelp); +/* +** Destructor for a series_cursor. +*/ +static int seriesClose(sqlite3_vtab_cursor *cur){ + sqlite3_free(cur); + return SQLITE_OK; } -#endif -#ifndef U8_TYPEDEF -/* typedef unsigned char u8; */ -#define U8_TYPEDEF -#endif -/* Classify c according to interval within USASCII set w.r.t. base85 - * Values of 1 and 3 are base85 numerals. Values of 0, 2, or 4 are not. - */ -#define B85_CLASS( c ) (((c)>='#')+((c)>'&')+((c)>='*')+((c)>'z')) +/* +** Advance a series_cursor to its next row of output. +*/ +static int seriesNext(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + if( pCur->iValue==pCur->iTerm ){ + pCur->bDone = 1; + }else if( pCur->bDesc ){ + pCur->iValue = sub64(pCur->iValue, pCur->iStep); + assert( pCur->iValue>=pCur->iTerm ); + }else{ + pCur->iValue = add64(pCur->iValue, pCur->iStep); + assert( pCur->iValue<=pCur->iTerm ); + } + return SQLITE_OK; +} -/* Provide digitValue to b85Numeral offset as a function of above class. */ -static u8 b85_cOffset[] = { 0, '#', 0, '*'-4, 0 }; -#define B85_DNOS( c ) b85_cOffset[B85_CLASS(c)] +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. +*/ +static int seriesColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + series_cursor *pCur = (series_cursor*)cur; + sqlite3_int64 x = 0; + switch( i ){ + case SERIES_COLUMN_START: x = pCur->iOBase; break; + case SERIES_COLUMN_STOP: x = pCur->iOTerm; break; + case SERIES_COLUMN_STEP: x = pCur->iOStep; break; + default: x = pCur->iValue; break; + } + sqlite3_result_int64(ctx, x); + return SQLITE_OK; +} -/* Say whether c is a base85 numeral. */ -#define IS_B85( c ) (B85_CLASS(c) & 1) +#ifndef LARGEST_UINT64 +#define LARGEST_INT64 ((sqlite3_int64)0x7fffffffffffffffLL) +#define LARGEST_UINT64 ((sqlite3_uint64)0xffffffffffffffffULL) +#define SMALLEST_INT64 ((sqlite3_int64)0x8000000000000000LL) +#endif -#if 0 /* Not used, */ -static u8 base85DigitValue( char c ){ - u8 dv = (u8)(c - '#'); - if( dv>87 ) return 0xff; - return (dv > 3)? dv-3 : dv; +/* +** The rowid is the same as the value. +*/ +static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + series_cursor *pCur = (series_cursor*)cur; + *pRowid = pCur->iValue; + return SQLITE_OK; } -#endif -/* Width of base64 lines. Should be an integer multiple of 5. */ -#define B85_DARK_MAX 80 +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int seriesEof(sqlite3_vtab_cursor *cur){ + series_cursor *pCur = (series_cursor*)cur; + return pCur->bDone; +} +/* True to cause run-time checking of the start=, stop=, and/or step= +** parameters. The only reason to do this is for testing the +** constraint checking logic for virtual tables in the SQLite core. +*/ +#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY +# define SQLITE_SERIES_CONSTRAINT_VERIFY 0 +#endif -static char * skipNonB85( char *s, int nc ){ - char c; - while( nc-- > 0 && (c = *s) && !IS_B85(c) ) ++s; - return s; +/* +** Return the number of steps between pCur->iBase and pCur->iTerm if +** the step width is pCur->iStep. +*/ +static sqlite3_uint64 seriesSteps(series_cursor *pCur){ + if( pCur->bDesc ){ + assert( pCur->iBase >= pCur->iTerm ); + return span64(pCur->iBase, pCur->iTerm)/pCur->iStep; + }else{ + assert( pCur->iBase <= pCur->iTerm ); + return span64(pCur->iTerm, pCur->iBase)/pCur->iStep; + } } -/* Convert small integer, known to be in 0..84 inclusive, to base85 numeral. - * Do not use the macro form with argument expression having a side-effect.*/ -#if 0 -static char base85Numeral( u8 b ){ - return (b < 4)? (char)(b + '#') : (char)(b - 4 + '*'); -} +#if defined(SQLITE_ENABLE_MATH_FUNCTIONS) || defined(_WIN32) +/* +** Case 1 (the most common case): +** The standard math library is available so use ceil() and floor() from there. +*/ +static double seriesCeil(double r){ return ceil(r); } +static double seriesFloor(double r){ return floor(r); } +#elif defined(__GNUC__) && !defined(SQLITE_DISABLE_INTRINSIC) +/* +** Case 2 (2nd most common): Use GCC/Clang builtins +*/ +static double seriesCeil(double r){ return __builtin_ceil(r); } +static double seriesFloor(double r){ return __builtin_floor(r); } #else -# define base85Numeral( dn )\ - ((char)(((dn) < 4)? (char)((dn) + '#') : (char)((dn) - 4 + '*'))) -#endif - -static char *putcs(char *pc, char *s){ - char c; - while( (c = *s++)!=0 ) *pc++ = c; - return pc; +/* +** Case 3 (rarely happens): Use home-grown ceil() and floor() routines. +*/ +static double seriesCeil(double r){ + sqlite3_int64 x; + if( r!=r ) return r; + if( r<=(-4503599627370496.0) ) return r; + if( r>=(+4503599627370496.0) ) return r; + x = (sqlite3_int64)r; + if( r==(double)x ) return r; + if( r>(double)x ) x++; + return (double)x; +} +static double seriesFloor(double r){ + sqlite3_int64 x; + if( r!=r ) return r; + if( r<=(-4503599627370496.0) ) return r; + if( r>=(+4503599627370496.0) ) return r; + x = (sqlite3_int64)r; + if( r==(double)x ) return r; + if( r<(double)x ) x--; + return (double)x; } +#endif -/* Encode a byte buffer into base85 text. If pSep!=0, it's a C string -** to be appended to encoded groups to limit their length to B85_DARK_MAX -** or to terminate the last group (to aid concatenation.) +/* +** This method is called to "rewind" the series_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to seriesColumn() or seriesRowid() or +** seriesEof(). +** +** The query plan selected by seriesBestIndex is passed in the idxNum +** parameter. (idxStr is not used in this implementation.) idxNum +** is a bitmask showing which constraints are available: +** +** 0x0001: start=VALUE +** 0x0002: stop=VALUE +** 0x0004: step=VALUE +** 0x0008: descending order +** 0x0010: ascending order +** 0x0020: LIMIT VALUE +** 0x0040: OFFSET VALUE +** 0x0080: value=VALUE +** 0x0100: value>=VALUE +** 0x0200: value>VALUE +** 0x1000: value<=VALUE +** 0x2000: value= 4 ){ - int nco = 5; - unsigned long qbv = (((unsigned long)pIn[0])<<24) | - (pIn[1]<<16) | (pIn[2]<<8) | pIn[3]; - while( nco > 0 ){ - unsigned nqv = (unsigned)(qbv/85UL); - unsigned char dv = qbv - 85UL*nqv; - qbv = nqv; - pOut[--nco] = base85Numeral(dv); - } - nbIn -= 4; - pIn += 4; - pOut += 5; - if( pSep && (nCol += 5)>=B85_DARK_MAX ){ - pOut = putcs(pOut, pSep); - nCol = 0; +static int seriesFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStrUnused, + int argc, sqlite3_value **argv +){ + series_cursor *pCur = (series_cursor *)pVtabCursor; + int iArg = 0; /* Arguments used so far */ + int i; /* Loop counter */ + sqlite3_int64 iMin = SMALLEST_INT64; /* Smallest allowed output value */ + sqlite3_int64 iMax = LARGEST_INT64; /* Largest allowed output value */ + sqlite3_int64 iLimit = 0; /* if >0, the value of the LIMIT */ + sqlite3_int64 iOffset = 0; /* if >0, the value of the OFFSET */ + + (void)idxStrUnused; + + /* If any constraints have a NULL value, then return no rows. + ** See ticket https://sqlite.org/src/info/fac496b61722daf2 + */ + for(i=0; i 0 ){ - int nco = nbIn + 1; - unsigned long qv = *pIn++; - int nbe = 1; - while( nbe++ < nbIn ){ - qv = (qv<<8) | *pIn++; - } - nCol += nco; - while( nco > 0 ){ - u8 dv = (u8)(qv % 85); - qv /= 85; - pOut[--nco] = base85Numeral(dv); - } - pOut += (nbIn+1); + + /* Capture the three HIDDEN parameters to the virtual table and insert + ** default values for any parameters that are omitted. + */ + if( idxNum & 0x01 ){ + pCur->iOBase = sqlite3_value_int64(argv[iArg++]); + }else{ + pCur->iOBase = 0; + } + if( idxNum & 0x02 ){ + pCur->iOTerm = sqlite3_value_int64(argv[iArg++]); + }else{ + pCur->iOTerm = 0xffffffff; + } + if( idxNum & 0x04 ){ + pCur->iOStep = sqlite3_value_int64(argv[iArg++]); + if( pCur->iOStep==0 ) pCur->iOStep = 1; + }else{ + pCur->iOStep = 1; } - if( pSep && nCol>0 ) pOut = putcs(pOut, pSep); - *pOut = 0; - return pOut; -} -/* Decode base85 text into a byte buffer. */ -static u8* fromBase85( char *pIn, int ncIn, u8 *pOut ){ - if( ncIn>0 && pIn[ncIn-1]=='\n' ) --ncIn; - while( ncIn>0 ){ - static signed char nboi[] = { 0, 0, 1, 2, 3, 4 }; - char *pUse = skipNonB85(pIn, ncIn); - unsigned long qv = 0L; - int nti, nbo; - ncIn -= (pUse - pIn); - pIn = pUse; - nti = (ncIn>5)? 5 : ncIn; - nbo = nboi[nti]; - if( nbo==0 ) break; - while( nti>0 ){ - char c = *pIn++; - u8 cdo = B85_DNOS(c); - --ncIn; - if( cdo==0 ) break; - qv = 85 * qv + (c - cdo); - --nti; - } - nbo -= nti; /* Adjust for early (non-digit) end of group. */ - switch( nbo ){ - case 4: - *pOut++ = (qv >> 24)&0xff; - case 3: - *pOut++ = (qv >> 16)&0xff; - case 2: - *pOut++ = (qv >> 8)&0xff; - case 1: - *pOut++ = qv&0xff; - case 0: - break; + /* If there are constraints on the value column but there are + ** no constraints on the start, stop, and step columns, then + ** initialize the default range to be the entire range of 64-bit signed + ** integers. This range will contracted by the value column constraints + ** further below. + */ + if( (idxNum & 0x05)==0 && (idxNum & 0x0380)!=0 ){ + pCur->iOBase = SMALLEST_INT64; + } + if( (idxNum & 0x06)==0 && (idxNum & 0x3080)!=0 ){ + pCur->iOTerm = LARGEST_INT64; + } + pCur->iBase = pCur->iOBase; + pCur->iTerm = pCur->iOTerm; + if( pCur->iOStep>0 ){ + pCur->iStep = pCur->iOStep; + }else if( pCur->iOStep>SMALLEST_INT64 ){ + pCur->iStep = -pCur->iOStep; + }else{ + pCur->iStep = LARGEST_INT64; + pCur->iStep++; + } + pCur->bDesc = pCur->iOStep<0; + if( pCur->bDesc==0 && pCur->iBase>pCur->iTerm ){ + goto series_no_rows; + } + if( pCur->bDesc!=0 && pCur->iBaseiTerm ){ + goto series_no_rows; + } + + /* Extract the LIMIT and OFFSET values, but do not apply them yet. + ** The range must first be constrained by the limits on value. + */ + if( idxNum & 0x20 ){ + iLimit = sqlite3_value_int64(argv[iArg++]); + if( idxNum & 0x40 ){ + iOffset = sqlite3_value_int64(argv[iArg++]); } } - return pOut; -} -#ifndef OMIT_BASE85_CHECKER -/* Say whether input char sequence is all (base85 and/or whitespace).*/ -static int allBase85( char *p, int len ){ - char c; - while( len-- > 0 && (c = *p++) != 0 ){ - if( !IS_B85(c) && !isspace(c) ) return 0; + /* Narrow the range of iMin and iMax (the minimum and maximum outputs) + ** based on equality and inequality constraints on the "value" column. + */ + if( idxNum & 0x3380 ){ + if( idxNum & 0x0080 ){ /* value=X */ + if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ + double r = sqlite3_value_double(argv[iArg++]); + if( r==seriesCeil(r) + && r>=(double)SMALLEST_INT64 + && r<=(double)LARGEST_INT64 + ){ + iMin = iMax = (sqlite3_int64)r; + }else{ + goto series_no_rows; + } + }else{ + iMin = iMax = sqlite3_value_int64(argv[iArg++]); + } + }else{ + if( idxNum & 0x0300 ){ /* value>X or value>=X */ + if( sqlite3_value_numeric_type(argv[iArg])==SQLITE_FLOAT ){ + double r = sqlite3_value_double(argv[iArg++]); + if( r<(double)SMALLEST_INT64 ){ + iMin = SMALLEST_INT64; + }else if( (idxNum & 0x0200)!=0 && r==seriesCeil(r) ){ + iMin = (sqlite3_int64)seriesCeil(r+1.0); + }else{ + iMin = (sqlite3_int64)seriesCeil(r); + } + }else{ + iMin = sqlite3_value_int64(argv[iArg++]); + if( (idxNum & 0x0200)!=0 ){ + if( iMin==LARGEST_INT64 ){ + goto series_no_rows; + }else{ + iMin++; + } + } + } + } + if( idxNum & 0x3000 ){ /* value(double)LARGEST_INT64 ){ + iMax = LARGEST_INT64; + }else if( (idxNum & 0x2000)!=0 && r==seriesFloor(r) ){ + iMax = (sqlite3_int64)(r-1.0); + }else{ + iMax = (sqlite3_int64)seriesFloor(r); + } + }else{ + iMax = sqlite3_value_int64(argv[iArg++]); + if( idxNum & 0x2000 ){ + if( iMax==SMALLEST_INT64 ){ + goto series_no_rows; + }else{ + iMax--; + } + } + } + } + if( iMin>iMax ){ + goto series_no_rows; + } + } + + /* Try to reduce the range of values to be generated based on + ** constraints on the "value" column. + */ + if( pCur->bDesc==0 ){ + if( pCur->iBaseiBase); + pCur->iBase = add64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); + if( pCur->iBaseiBase > sub64(LARGEST_INT64, pCur->iStep) ){ + goto series_no_rows; + } + pCur->iBase = add64(pCur->iBase, pCur->iStep); + } + } + if( pCur->iTerm>iMax ){ + pCur->iTerm = iMax; + } + }else{ + if( pCur->iBase>iMax ){ + sqlite3_uint64 span = span64(pCur->iBase,iMax); + pCur->iBase = sub64(pCur->iBase, (span/pCur->iStep)*pCur->iStep); + if( pCur->iBase>iMax ){ + if( pCur->iBase < add64(SMALLEST_INT64, pCur->iStep) ){ + goto series_no_rows; + } + pCur->iBase = sub64(pCur->iBase, pCur->iStep); + } + } + if( pCur->iTermiTerm = iMin; + } + } } - return 1; -} -#endif - -#ifndef BASE85_STANDALONE -# ifndef OMIT_BASE85_CHECKER -/* This function does the work for the SQLite is_base85(t) UDF. */ -static void is_base85(sqlite3_context *context, int na, sqlite3_value *av[]){ - assert(na==1); - switch( sqlite3_value_type(av[0]) ){ - case SQLITE_TEXT: - { - int rv = allBase85( (char *)sqlite3_value_text(av[0]), - sqlite3_value_bytes(av[0]) ); - sqlite3_result_int(context, rv); + /* Adjust iTerm so that it is exactly the last value of the series. + */ + if( pCur->bDesc==0 ){ + if( pCur->iBase>pCur->iTerm ){ + goto series_no_rows; } - break; - case SQLITE_NULL: - sqlite3_result_null(context); - break; - default: - sqlite3_result_error(context, "is_base85 accepts only text or NULL", -1); - return; + pCur->iTerm = sub64(pCur->iTerm, + span64(pCur->iTerm,pCur->iBase) % pCur->iStep); + }else{ + if( pCur->iBaseiTerm ){ + goto series_no_rows; + } + pCur->iTerm = add64(pCur->iTerm, + span64(pCur->iBase,pCur->iTerm) % pCur->iStep); } -} -# endif -/* This function does the work for the SQLite base85(x) UDF. */ -static void base85(sqlite3_context *context, int na, sqlite3_value *av[]){ - int nb, nc, nv = sqlite3_value_bytes(av[0]); - int nvMax = sqlite3_limit(sqlite3_context_db_handle(context), - SQLITE_LIMIT_LENGTH, -1); - char *cBuf; - u8 *bBuf; - assert(na==1); - switch( sqlite3_value_type(av[0]) ){ - case SQLITE_BLOB: - nb = nv; - /* ulongs tail newlines tailenc+nul*/ - nc = 5*(nv/4) + nv%4 + nv/64+1 + 2; - if( nvMax < nc ){ - sqlite3_result_error(context, "blob expanded to base85 too big", -1); - return; - } - bBuf = (u8*)sqlite3_value_blob(av[0]); - if( !bBuf ){ - if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ - goto memFail; + /* Transform the series generator to output values in the requested + ** order. + */ + if( ((idxNum & 0x0008)!=0 && pCur->bDesc==0) + || ((idxNum & 0x0010)!=0 && pCur->bDesc!=0) + ){ + sqlite3_int64 tmp = pCur->iBase; + pCur->iBase = pCur->iTerm; + pCur->iTerm = tmp; + pCur->bDesc = !pCur->bDesc; + } + + /* Apply LIMIT and OFFSET constraints, if any */ + assert( pCur->iStep!=0 ); + if( idxNum & 0x20 ){ + if( iOffset>0 ){ + if( seriesSteps(pCur) < (sqlite3_uint64)iOffset ){ + goto series_no_rows; + }else if( pCur->bDesc ){ + pCur->iBase = sub64(pCur->iBase, pCur->iStep*iOffset); + }else{ + pCur->iBase = add64(pCur->iBase, pCur->iStep*iOffset); } - sqlite3_result_text(context,"",-1,SQLITE_STATIC); - break; - } - cBuf = sqlite3_malloc(nc); - if( !cBuf ) goto memFail; - nc = (int)(toBase85(bBuf, nb, cBuf, "\n") - cBuf); - sqlite3_result_text(context, cBuf, nc, sqlite3_free); - break; - case SQLITE_TEXT: - nc = nv; - nb = 4*(nv/5) + nv%5; /* may overestimate */ - if( nvMax < nb ){ - sqlite3_result_error(context, "blob from base85 may be too big", -1); - return; - }else if( nb<1 ){ - nb = 1; } - cBuf = (char *)sqlite3_value_text(av[0]); - if( !cBuf ){ - if( SQLITE_NOMEM==sqlite3_errcode(sqlite3_context_db_handle(context)) ){ - goto memFail; - } - sqlite3_result_zeroblob(context, 0); - break; + if( iLimit>=0 && seriesSteps(pCur) > (sqlite3_uint64)iLimit ){ + pCur->iTerm = add64(pCur->iBase, (iLimit - 1)*pCur->iStep); } - bBuf = sqlite3_malloc(nb); - if( !bBuf ) goto memFail; - nb = (int)(fromBase85(cBuf, nc, bBuf) - bBuf); - sqlite3_result_blob(context, bBuf, nb, sqlite3_free); - break; - default: - sqlite3_result_error(context, "base85 accepts only blob or text.", -1); - return; } - return; - memFail: - sqlite3_result_error(context, "base85 OOM", -1); + pCur->iValue = pCur->iBase; + pCur->bDone = 0; + return SQLITE_OK; + +series_no_rows: + pCur->iBase = 0; + pCur->iTerm = 0; + pCur->iStep = 1; + pCur->bDesc = 0; + pCur->bDone = 1; + return SQLITE_OK; } /* -** Establish linkage to running SQLite library. +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** The query plan is represented by bits in idxNum: +** +** 0x0001 start = $num +** 0x0002 stop = $num +** 0x0004 step = $num +** 0x0008 output is in descending order +** 0x0010 output is in ascending order +** 0x0020 LIMIT $num +** 0x0040 OFFSET $num +** 0x0080 value = $num +** 0x0100 value >= $num +** 0x0200 value > $num +** 0x1000 value <= $num +** 0x2000 value < $num +** +** Only one of 0x0100 or 0x0200 will be returned. Similarly, only +** one of 0x1000 or 0x2000 will be returned. If the 0x0080 is set, then +** none of the 0xff00 bits will be set. +** +** The order of parameters passed to xFilter is as follows: +** +** * The argument to start= if bit 0x0001 is in the idxNum mask +** * The argument to stop= if bit 0x0002 is in the idxNum mask +** * The argument to step= if bit 0x0004 is in the idxNum mask +** * The argument to LIMIT if bit 0x0020 is in the idxNum mask +** * The argument to OFFSET if bit 0x0040 is in the idxNum mask +** * The argument to value=, or value>= or value> if any of +** bits 0x0380 are in the idxNum mask +** * The argument to value<= or value< if either of bits 0x3000 +** are in the mask +** */ -#ifndef SQLITE_SHELL_EXTFUNCS -#ifdef _WIN32 +static int seriesBestIndex( + sqlite3_vtab *pVTab, + sqlite3_index_info *pIdxInfo +){ + int i, j; /* Loop over constraints */ + int idxNum = 0; /* The query plan bitmask */ +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + int bStartSeen = 0; /* EQ constraint seen on the START column */ +#endif + int unusableMask = 0; /* Mask of unusable constraints */ + int nArg = 0; /* Number of arguments that seriesFilter() expects */ + int aIdx[7]; /* Constraints on start, stop, step, LIMIT, OFFSET, + ** and value. aIdx[5] covers value=, value>=, and + ** value>, aIdx[6] covers value<= and value< */ + const struct sqlite3_index_constraint *pConstraint; + + /* This implementation assumes that the start, stop, and step columns + ** are the last three columns in the virtual table. */ + assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); + assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); + aIdx[0] = aIdx[1] = aIdx[2] = aIdx[3] = aIdx[4] = aIdx[5] = aIdx[6] = -1; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + int iCol; /* 0 for start, 1 for stop, 2 for step */ + int iMask; /* bitmask for those column */ + int op = pConstraint->op; + if( op>=SQLITE_INDEX_CONSTRAINT_LIMIT + && op<=SQLITE_INDEX_CONSTRAINT_OFFSET + ){ + if( pConstraint->usable==0 ){ + /* do nothing */ + }else if( op==SQLITE_INDEX_CONSTRAINT_LIMIT ){ + aIdx[3] = i; + idxNum |= 0x20; + }else{ + assert( op==SQLITE_INDEX_CONSTRAINT_OFFSET ); + aIdx[4] = i; + idxNum |= 0x40; + } + continue; + } + if( pConstraint->iColumniColumn==SERIES_COLUMN_VALUE || + pConstraint->iColumn==SERIES_COLUMN_ROWID) + && pConstraint->usable + ){ + switch( op ){ + case SQLITE_INDEX_CONSTRAINT_EQ: + case SQLITE_INDEX_CONSTRAINT_IS: { + idxNum |= 0x0080; + idxNum &= ~0x3300; + aIdx[5] = i; + aIdx[6] = -1; +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + bStartSeen = 1; #endif -int sqlite3_base_init -#else -static int sqlite3_base85_init + break; + } + case SQLITE_INDEX_CONSTRAINT_GE: { + if( idxNum & 0x0080 ) break; + idxNum |= 0x0100; + idxNum &= ~0x0200; + aIdx[5] = i; +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + bStartSeen = 1; #endif -(sqlite3 *db, char **pzErr, const sqlite3_api_routines *pApi){ - SQLITE_EXTENSION_INIT2(pApi); - (void)pzErr; -# ifndef OMIT_BASE85_CHECKER - { - int rc = sqlite3_create_function - (db, "is_base85", 1, - SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_UTF8, - 0, is_base85, 0, 0); - if( rc!=SQLITE_OK ) return rc; + break; + } + case SQLITE_INDEX_CONSTRAINT_GT: { + if( idxNum & 0x0080 ) break; + idxNum |= 0x0200; + idxNum &= ~0x0100; + aIdx[5] = i; +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + bStartSeen = 1; +#endif + break; + } + case SQLITE_INDEX_CONSTRAINT_LE: { + if( idxNum & 0x0080 ) break; + idxNum |= 0x1000; + idxNum &= ~0x2000; + aIdx[6] = i; + break; + } + case SQLITE_INDEX_CONSTRAINT_LT: { + if( idxNum & 0x0080 ) break; + idxNum |= 0x2000; + idxNum &= ~0x1000; + aIdx[6] = i; + break; + } + } + } + continue; + } + iCol = pConstraint->iColumn - SERIES_COLUMN_START; + assert( iCol>=0 && iCol<=2 ); + iMask = 1 << iCol; +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + if( iCol==0 && op==SQLITE_INDEX_CONSTRAINT_EQ ){ + bStartSeen = 1; + } +#endif + if( pConstraint->usable==0 ){ + unusableMask |= iMask; + continue; + }else if( op==SQLITE_INDEX_CONSTRAINT_EQ ){ + idxNum |= iMask; + aIdx[iCol] = i; + } } -# endif - return sqlite3_create_function - (db, "base85", 1, - SQLITE_DETERMINISTIC|SQLITE_INNOCUOUS|SQLITE_DIRECTONLY|SQLITE_UTF8, - 0, base85, 0, 0); -} - -/* -** Define some macros to allow this extension to be built into the shell -** conveniently, in conjunction with use of SQLITE_SHELL_EXTFUNCS. This -** allows shell.c, as distributed, to have this extension built in. -*/ -# define BASE85_INIT(db) sqlite3_base85_init(db, 0, 0) -# define BASE85_EXPOSE(db, pzErr) /* Not needed, ..._init() does this. */ - -#else /* standalone program */ - -int shell_main(int na, char *av[]){ - int cin; - int rc = 0; - u8 bBuf[4*(B85_DARK_MAX/5)]; - char cBuf[5*(sizeof(bBuf)/4)+2]; - size_t nio; -# ifndef OMIT_BASE85_CHECKER - int b85Clean = 1; -# endif - char rw; - FILE *fb = 0, *foc = 0; - char fmode[3] = "xb"; - if( na < 3 || av[1][0]!='-' || (rw = av[1][1])==0 || (rw!='r' && rw!='w') ){ - sayHelp(); - return 0; + if( aIdx[3]==0 ){ + /* Ignore OFFSET if LIMIT is omitted */ + idxNum &= ~0x60; + aIdx[4] = 0; } - fmode[0] = rw; - if( av[2][0]=='-' && av[2][1]==0 ){ - switch( rw ){ - case 'r': - fb = stdin; - setmode(fileno(stdin), O_BINARY); - break; - case 'w': - fb = stdout; - setmode(fileno(stdout), O_BINARY); - break; + for(i=0; i<7; i++){ + if( (j = aIdx[i])>=0 ){ + pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; + pIdxInfo->aConstraintUsage[j].omit = + !SQLITE_SERIES_CONSTRAINT_VERIFY || i>=3; } - }else{ - fb = fopen(av[2], fmode); - foc = fb; } - if( !fb ){ - fprintf(stderr, "Cannot open %s for %c\n", av[2], rw); - rc = 1; - }else{ - switch( rw ){ - case 'r': - while( (nio = fread( bBuf, 1, sizeof(bBuf), fb))>0 ){ - toBase85( bBuf, (int)nio, cBuf, 0 ); - fprintf(stdout, "%s\n", cBuf); - } - break; - case 'w': - while( 0 != fgets(cBuf, sizeof(cBuf), stdin) ){ - int nc = strlen(cBuf); - size_t nbo = fromBase85( cBuf, nc, bBuf ) - bBuf; - if( 1 != fwrite(bBuf, nbo, 1, fb) ) rc = 1; -# ifndef OMIT_BASE85_CHECKER - b85Clean &= allBase85( cBuf, nc ); -# endif + /* The current generate_column() implementation requires at least one + ** argument (the START value). Legacy versions assumed START=0 if the + ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES + ** to obtain the legacy behavior */ +#ifndef ZERO_ARGUMENT_GENERATE_SERIES + if( !bStartSeen ){ + sqlite3_free(pVTab->zErrMsg); + pVTab->zErrMsg = sqlite3_mprintf( + "first argument to \"generate_series()\" missing or unusable"); + return SQLITE_ERROR; + } +#endif + if( (unusableMask & ~idxNum)!=0 ){ + /* The start, stop, and step columns are inputs. Therefore if there + ** are unusable constraints on any of start, stop, or step then + ** this plan is unusable */ + return SQLITE_CONSTRAINT; + } + if( (idxNum & 0x03)==0x03 ){ + /* Both start= and stop= boundaries are available. This is the + ** the preferred case */ + pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); + pIdxInfo->estimatedRows = 1000; + if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ + if( pIdxInfo->aOrderBy[0].desc ){ + idxNum |= 0x08; + }else{ + idxNum |= 0x10; } - break; - default: - sayHelp(); - rc = 1; + pIdxInfo->orderByConsumed = 1; } - if( foc ) fclose(foc); - } -# ifndef OMIT_BASE85_CHECKER - if( !b85Clean ){ - fprintf(stderr, "Base85 input had non-base85 dark or control content.\n"); + }else if( (idxNum & 0x21)==0x21 ){ + /* We have start= and LIMIT */ + pIdxInfo->estimatedRows = 2500; + }else{ + /* If either boundary is missing, we have to generate a huge span + ** of numbers. Make this case very expensive so that the query + ** planner will work hard to avoid it. */ + pIdxInfo->estimatedRows = 2147483647; } -# endif - return rc; + pIdxInfo->idxNum = idxNum; +#ifdef SQLITE_INDEX_SCAN_HEX + pIdxInfo->idxFlags = SQLITE_INDEX_SCAN_HEX; +#endif + return SQLITE_OK; } +/* +** This following structure defines all the methods for the +** generate_series virtual table. +*/ +static sqlite3_module seriesModule = { + 0, /* iVersion */ + 0, /* xCreate */ + seriesConnect, /* xConnect */ + seriesBestIndex, /* xBestIndex */ + seriesDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + seriesOpen, /* xOpen - open a cursor */ + seriesClose, /* xClose - close a cursor */ + seriesFilter, /* xFilter - configure scan constraints */ + seriesNext, /* xNext - advance a cursor */ + seriesEof, /* xEof - check for end of scan */ + seriesColumn, /* xColumn - read data */ + seriesRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ +}; + +#endif /* SQLITE_OMIT_VIRTUALTABLE */ + +#ifdef _WIN32 + +#endif +int sqlite3_series_init( + sqlite3 *db, + char **pzErrMsg, + const sqlite3_api_routines *pApi +){ + int rc = SQLITE_OK; + SQLITE_EXTENSION_INIT2(pApi); +#ifndef SQLITE_OMIT_VIRTUALTABLE + if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){ + *pzErrMsg = sqlite3_mprintf( + "generate_series() requires SQLite 3.8.12 or later"); + return SQLITE_ERROR; + } + rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0); #endif + return rc; +} -/************************* End ../ext/misc/base85.c ********************/ -/************************* Begin ../ext/misc/ieee754.c ******************/ +/************************* End ext/misc/series.c ********************/ +/************************* Begin ext/misc/regexp.c ******************/ /* -** 2013-04-17 +** 2012-11-13 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -4498,910 +8769,928 @@ int shell_main(int na, char *av[]){ ** ****************************************************************************** ** -** This SQLite extension implements functions for the exact display -** and input of IEEE754 Binary64 floating-point numbers. -** -** ieee754(X) -** ieee754(Y,Z) -** -** In the first form, the value X should be a floating-point number. -** The function will return a string of the form 'ieee754(Y,Z)' where -** Y and Z are integers such that X==Y*pow(2,Z). -** -** In the second form, Y and Z are integers which are the mantissa and -** base-2 exponent of a new floating point number. The function returns -** a floating-point value equal to Y*pow(2,Z). -** -** Examples: -** -** ieee754(2.0) -> 'ieee754(2,0)' -** ieee754(45.25) -> 'ieee754(181,-2)' -** ieee754(2, 0) -> 2.0 -** ieee754(181, -2) -> 45.25 -** -** Two additional functions break apart the one-argument ieee754() -** result into separate integer values: -** -** ieee754_mantissa(45.25) -> 181 -** ieee754_exponent(45.25) -> -2 -** -** These functions convert binary64 numbers into blobs and back again. -** -** ieee754_from_blob(x'3ff0000000000000') -> 1.0 -** ieee754_to_blob(1.0) -> x'3ff0000000000000' -** -** In all single-argument functions, if the argument is an 8-byte blob -** then that blob is interpreted as a big-endian binary64 value. -** -** -** EXACT DECIMAL REPRESENTATION OF BINARY64 VALUES -** ----------------------------------------------- -** -** This extension in combination with the separate 'decimal' extension -** can be used to compute the exact decimal representation of binary64 -** values. To begin, first compute a table of exponent values: -** -** CREATE TABLE pow2(x INTEGER PRIMARY KEY, v TEXT); -** WITH RECURSIVE c(x,v) AS ( -** VALUES(0,'1') -** UNION ALL -** SELECT x+1, decimal_mul(v,'2') FROM c WHERE x+1<=971 -** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; -** WITH RECURSIVE c(x,v) AS ( -** VALUES(-1,'0.5') -** UNION ALL -** SELECT x-1, decimal_mul(v,'0.5') FROM c WHERE x-1>=-1075 -** ) INSERT INTO pow2(x,v) SELECT x, v FROM c; +** The code in this file implements a compact but reasonably +** efficient regular-expression matcher for posix extended regular +** expressions against UTF8 text. ** -** Then, to compute the exact decimal representation of a floating -** point value (the value 47.49 is used in the example) do: +** This file is an SQLite extension. It registers a single function +** named "regexp(A,B)" where A is the regular expression and B is the +** string to be matched. By registering this function, SQLite will also +** then implement the "B regexp A" operator. Note that with the function +** the regular expression comes first, but with the operator it comes +** second. ** -** WITH c(n) AS (VALUES(47.49)) -** ---------------^^^^^---- Replace with whatever you want -** SELECT decimal_mul(ieee754_mantissa(c.n),pow2.v) -** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.n); +** The following regular expression syntax is supported: ** -** Here is a query to show various boundry values for the binary64 -** number format: +** X* zero or more occurrences of X +** X+ one or more occurrences of X +** X? zero or one occurrences of X +** X{p,q} between p and q occurrences of X +** (X) match X +** X|Y X or Y +** ^X X occurring at the beginning of the string +** X$ X occurring at the end of the string +** . Match any single character +** \c Character c where c is one of \{}()[]|*+?-. +** \c C-language escapes for c in afnrtv. ex: \t or \n +** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX +** \xXX Where XX is exactly 2 hex digits, unicode value XX +** [abc] Any single character from the set abc +** [^abc] Any single character not in the set abc +** [a-z] Any single character in the range a-z +** [^a-z] Any single character not in the range a-z +** \b Word boundary +** \w Word character. [A-Za-z0-9_] +** \W Non-word character +** \d Digit +** \D Non-digit +** \s Whitespace character +** \S Non-whitespace character ** -** WITH c(name,bin) AS (VALUES -** ('minimum positive value', x'0000000000000001'), -** ('maximum subnormal value', x'000fffffffffffff'), -** ('mininum positive nornal value', x'0010000000000000'), -** ('maximum value', x'7fefffffffffffff')) -** SELECT c.name, decimal_mul(ieee754_mantissa(c.bin),pow2.v) -** FROM pow2, c WHERE pow2.x=ieee754_exponent(c.bin); +** A nondeterministic finite automaton (NFA) is used for matching, so the +** performance is bounded by O(N*M) where N is the size of the regular +** expression and M is the size of the input string. The matcher never +** exhibits exponential behavior. Note that the X{p,q} operator expands +** to p copies of X following by q-p copies of X? and that the size of the +** regular expression in the O(N*M) performance bound is computed after +** this expansion. ** +** To help prevent DoS attacks, the maximum size of the NFA is restricted. */ +#include +#include /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 -#include -#include -/* Mark a function parameter as unused, to suppress nuisance compiler -** warnings. */ -#ifndef UNUSED_PARAMETER -# define UNUSED_PARAMETER(X) (void)(X) -#endif +/* +** The following #defines change the names of some functions implemented in +** this file to prevent name collisions with C-library functions of the +** same name. +*/ +#define re_match sqlite3re_match +#define re_compile sqlite3re_compile +#define re_free sqlite3re_free + +/* The end-of-input character */ +#define RE_EOF 0 /* End of input */ +#define RE_START 0xfffffff /* Start of input - larger than an UTF-8 */ + +/* The NFA is implemented as sequence of opcodes taken from the following +** set. Each opcode has a single integer argument. +*/ +#define RE_OP_MATCH 1 /* Match the one character in the argument */ +#define RE_OP_ANY 2 /* Match any one character. (Implements ".") */ +#define RE_OP_ANYSTAR 3 /* Special optimized version of .* */ +#define RE_OP_FORK 4 /* Continue to both next and opcode at iArg */ +#define RE_OP_GOTO 5 /* Jump to opcode at iArg */ +#define RE_OP_ACCEPT 6 /* Halt and indicate a successful match */ +#define RE_OP_CC_INC 7 /* Beginning of a [...] character class */ +#define RE_OP_CC_EXC 8 /* Beginning of a [^...] character class */ +#define RE_OP_CC_VALUE 9 /* Single value in a character class */ +#define RE_OP_CC_RANGE 10 /* Range of values in a character class */ +#define RE_OP_WORD 11 /* Perl word character [A-Za-z0-9_] */ +#define RE_OP_NOTWORD 12 /* Not a perl word character */ +#define RE_OP_DIGIT 13 /* digit: [0-9] */ +#define RE_OP_NOTDIGIT 14 /* Not a digit */ +#define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ +#define RE_OP_NOTSPACE 16 /* Not a digit */ +#define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ +#define RE_OP_ATSTART 18 /* Currently at the start of the string */ + +/* Each opcode is a "state" in the NFA */ +typedef unsigned short ReStateNumber; + +/* Because this is an NFA and not a DFA, multiple states can be active at +** once. An instance of the following object records all active states in +** the NFA. The implementation is optimized for the common case where the +** number of actives states is small. +*/ +typedef struct ReStateSet { + unsigned nState; /* Number of current states */ + ReStateNumber *aState; /* Current states */ +} ReStateSet; -/* -** Implementation of the ieee754() function +/* An input string read one character at a time. */ -static void ieee754func( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - if( argc==1 ){ - sqlite3_int64 m, a; - double r; - int e; - int isNeg; - char zResult[100]; - assert( sizeof(m)==sizeof(r) ); - if( sqlite3_value_type(argv[0])==SQLITE_BLOB - && sqlite3_value_bytes(argv[0])==sizeof(r) - ){ - const unsigned char *x = sqlite3_value_blob(argv[0]); - unsigned int i; - sqlite3_uint64 v = 0; - for(i=0; i>52; - m = a & ((((sqlite3_int64)1)<<52)-1); - if( e==0 ){ - m <<= 1; - }else{ - m |= ((sqlite3_int64)1)<<52; - } - while( e<1075 && m>0 && (m&1)==0 ){ - m >>= 1; - e++; - } - if( isNeg ) m = -m; - } - switch( *(int*)sqlite3_user_data(context) ){ - case 0: - sqlite3_snprintf(sizeof(zResult), zResult, "ieee754(%lld,%d)", - m, e-1075); - sqlite3_result_text(context, zResult, -1, SQLITE_TRANSIENT); - break; - case 1: - sqlite3_result_int64(context, m); - break; - case 2: - sqlite3_result_int(context, e-1075); - break; - } - }else{ - sqlite3_int64 m, e, a; - double r; - int isNeg = 0; - m = sqlite3_value_int64(argv[0]); - e = sqlite3_value_int64(argv[1]); +typedef struct ReInput ReInput; +struct ReInput { + const unsigned char *z; /* All text */ + int i; /* Next byte to read */ + int mx; /* EOF when i>=mx */ +}; - /* Limit the range of e. Ticket 22dea1cfdb9151e4 2021-03-02 */ - if( e>10000 ){ - e = 10000; - }else if( e<-10000 ){ - e = -10000; - } +/* A compiled NFA (or an NFA that is in the process of being compiled) is +** an instance of the following object. +*/ +typedef struct ReCompiled ReCompiled; +struct ReCompiled { + ReInput sIn; /* Regular expression text */ + const char *zErr; /* Error message to return */ + char *aOp; /* Operators for the virtual machine */ + int *aArg; /* Arguments to each operator */ + unsigned (*xNextChar)(ReInput*); /* Next character function */ + unsigned char zInit[12]; /* Initial text to match */ + int nInit; /* Number of bytes in zInit */ + unsigned nState; /* Number of entries in aOp[] and aArg[] */ + unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ + unsigned mxAlloc; /* Complexity limit */ +}; - if( m<0 ){ - isNeg = 1; - m = -m; - if( m<0 ) return; - }else if( m==0 && e>-1000 && e<1000 ){ - sqlite3_result_double(context, 0.0); - return; - } - while( (m>>32)&0xffe00000 ){ - m >>= 1; - e++; - } - while( m!=0 && ((m>>32)&0xfff00000)==0 ){ - m <<= 1; - e--; - } - e += 1075; - if( e<=0 ){ - /* Subnormal */ - if( 1-e >= 64 ){ - m = 0; - }else{ - m >>= 1-e; - } - e = 0; - }else if( e>0x7ff ){ - e = 0x7ff; - } - a = m & ((((sqlite3_int64)1)<<52)-1); - a |= e<<52; - if( isNeg ) a |= ((sqlite3_uint64)1)<<63; - memcpy(&r, &a, sizeof(r)); - sqlite3_result_double(context, r); - } +/* Add a state to the given state set if it is not already there */ +static void re_add_state(ReStateSet *pSet, int newState){ + unsigned i; + for(i=0; inState; i++) if( pSet->aState[i]==newState ) return; + pSet->aState[pSet->nState++] = (ReStateNumber)newState; } -/* -** Functions to convert between blobs and floats. +/* Extract the next unicode character from *pzIn and return it. Advance +** *pzIn to the first byte past the end of the character returned. To +** be clear: this routine converts utf8 to unicode. This routine is +** optimized for the common case where the next character is a single byte. */ -static void ieee754func_from_blob( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_BLOB - && sqlite3_value_bytes(argv[0])==sizeof(double) - ){ - double r; - const unsigned char *x = sqlite3_value_blob(argv[0]); - unsigned int i; - sqlite3_uint64 v = 0; - for(i=0; ii>=p->mx ) return 0; + c = p->z[p->i++]; + if( c>=0x80 ){ + if( (c&0xe0)==0xc0 && p->imx && (p->z[p->i]&0xc0)==0x80 ){ + c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f); + if( c<0x80 ) c = 0xfffd; + }else if( (c&0xf0)==0xe0 && p->i+1mx && (p->z[p->i]&0xc0)==0x80 + && (p->z[p->i+1]&0xc0)==0x80 ){ + c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); + p->i += 2; + if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; + }else if( (c&0xf8)==0xf0 && p->i+2mx && (p->z[p->i]&0xc0)==0x80 + && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ + c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) + | (p->z[p->i+2]&0x3f); + p->i += 3; + if( c<=0xffff || c>0x10ffff ) c = 0xfffd; + }else{ + c = 0xfffd; } - memcpy(&r, &v, sizeof(r)); - sqlite3_result_double(context, r); } + return c; } -static void ieee754func_to_blob( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - UNUSED_PARAMETER(argc); - if( sqlite3_value_type(argv[0])==SQLITE_FLOAT - || sqlite3_value_type(argv[0])==SQLITE_INTEGER - ){ - double r = sqlite3_value_double(argv[0]); - sqlite3_uint64 v; - unsigned char a[sizeof(r)]; - unsigned int i; - memcpy(&v, &r, sizeof(r)); - for(i=1; i<=sizeof(r); i++){ - a[sizeof(r)-i] = v&0xff; - v >>= 8; - } - sqlite3_result_blob(context, a, sizeof(r), SQLITE_TRANSIENT); - } +static unsigned re_next_char_nocase(ReInput *p){ + unsigned c = re_next_char(p); + if( c>='A' && c<='Z' ) c += 'a' - 'A'; + return c; } -/* -** SQL Function: ieee754_inc(r,N) -** -** Move the floating point value r by N quantums and return the new -** values. -** -** Behind the scenes: this routine merely casts r into a 64-bit unsigned -** integer, adds N, then casts the value back into float. -** -** Example: To find the smallest positive number: -** -** SELECT ieee754_inc(0.0,+1); -*/ -static void ieee754inc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - double r; - sqlite3_int64 N; - sqlite3_uint64 m1, m2; - double r2; - UNUSED_PARAMETER(argc); - r = sqlite3_value_double(argv[0]); - N = sqlite3_value_int64(argv[1]); - memcpy(&m1, &r, 8); - m2 = m1 + N; - memcpy(&r2, &m2, 8); - sqlite3_result_double(context, r2); +/* Return true if c is a perl "word" character: [A-Za-z0-9_] */ +static int re_word_char(int c){ + return (c>='0' && c<='9') || (c>='a' && c<='z') + || (c>='A' && c<='Z') || c=='_'; } +/* Return true if c is a "digit" character: [0-9] */ +static int re_digit_char(int c){ + return (c>='0' && c<='9'); +} + +/* Return true if c is a perl "space" character: [ \t\r\n\v\f] */ +static int re_space_char(int c){ + return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; +} + +/* Run a compiled regular expression on the zero-terminated input +** string zIn[]. Return true on a match and false if there is no match. +*/ +static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ + ReStateSet aStateSet[2], *pThis, *pNext; + ReStateNumber aSpace[100]; + ReStateNumber *pToFree; + unsigned int i = 0; + unsigned int iSwap = 0; + int c = RE_START; + int cPrev = 0; + int rc = 0; + ReInput in; + + in.z = zIn; + in.i = 0; + in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn); -#ifdef _WIN32 + /* Look for the initial prefix match, if there is one. */ + if( pRe->nInit ){ + unsigned char x = pRe->zInit[0]; + while( in.i+pRe->nInit<=in.mx + && (zIn[in.i]!=x || + strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) + ){ + in.i++; + } + if( in.i+pRe->nInit>in.mx ) return 0; + c = RE_START-1; + } -#endif -int sqlite3_ieee_init( - sqlite3 *db, - char **pzErrMsg, - const sqlite3_api_routines *pApi -){ - static const struct { - char *zFName; - int nArg; - int iAux; - void (*xFunc)(sqlite3_context*,int,sqlite3_value**); - } aFunc[] = { - { "ieee754", 1, 0, ieee754func }, - { "ieee754", 2, 0, ieee754func }, - { "ieee754_mantissa", 1, 1, ieee754func }, - { "ieee754_exponent", 1, 2, ieee754func }, - { "ieee754_to_blob", 1, 0, ieee754func_to_blob }, - { "ieee754_from_blob", 1, 0, ieee754func_from_blob }, - { "ieee754_inc", 2, 0, ieee754inc }, - }; - unsigned int i; - int rc = SQLITE_OK; - SQLITE_EXTENSION_INIT2(pApi); - (void)pzErrMsg; /* Unused parameter */ - for(i=0; inState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ + pToFree = 0; + aStateSet[0].aState = aSpace; + }else{ + pToFree = sqlite3_malloc64( sizeof(ReStateNumber)*2*pRe->nState ); + if( pToFree==0 ) return -1; + aStateSet[0].aState = pToFree; + } + aStateSet[1].aState = &aStateSet[0].aState[pRe->nState]; + pNext = &aStateSet[1]; + pNext->nState = 0; + re_add_state(pNext, 0); + while( c!=RE_EOF && pNext->nState>0 ){ + cPrev = c; + c = pRe->xNextChar(&in); + pThis = pNext; + pNext = &aStateSet[iSwap]; + iSwap = 1 - iSwap; + pNext->nState = 0; + for(i=0; inState; i++){ + int x = pThis->aState[i]; + switch( pRe->aOp[x] ){ + case RE_OP_MATCH: { + if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); + break; + } + case RE_OP_ATSTART: { + if( cPrev==RE_START ) re_add_state(pThis, x+1); + break; + } + case RE_OP_ANY: { + if( c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_WORD: { + if( re_word_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTWORD: { + if( !re_word_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_DIGIT: { + if( re_digit_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTDIGIT: { + if( !re_digit_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_SPACE: { + if( re_space_char(c) ) re_add_state(pNext, x+1); + break; + } + case RE_OP_NOTSPACE: { + if( !re_space_char(c) && c!=0 ) re_add_state(pNext, x+1); + break; + } + case RE_OP_BOUNDARY: { + if( re_word_char(c)!=re_word_char(cPrev) ) re_add_state(pThis, x+1); + break; + } + case RE_OP_ANYSTAR: { + re_add_state(pNext, x); + re_add_state(pThis, x+1); + break; + } + case RE_OP_FORK: { + re_add_state(pThis, x+pRe->aArg[x]); + re_add_state(pThis, x+1); + break; + } + case RE_OP_GOTO: { + re_add_state(pThis, x+pRe->aArg[x]); + break; + } + case RE_OP_ACCEPT: { + rc = 1; + goto re_match_end; + } + case RE_OP_CC_EXC: { + if( c==0 ) break; + /* fall-through */ goto re_op_cc_inc; + } + case RE_OP_CC_INC: re_op_cc_inc: { + int j = 1; + int n = pRe->aArg[x]; + int hit = 0; + for(j=1; j>0 && jaOp[x+j]==RE_OP_CC_VALUE ){ + if( pRe->aArg[x+j]==c ){ + hit = 1; + j = -1; + } + }else{ + if( pRe->aArg[x+j]<=c && pRe->aArg[x+j+1]>=c ){ + hit = 1; + j = -1; + }else{ + j++; + } + } + } + if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; + if( hit ) re_add_state(pNext, x+n); + break; + } + } + } + } + for(i=0; inState; i++){ + int x = pNext->aState[i]; + while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x]; + if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; } } +re_match_end: + sqlite3_free(pToFree); return rc; } -/************************* End ../ext/misc/ieee754.c ********************/ -/************************* Begin ../ext/misc/series.c ******************/ -/* -** 2015-08-18, 2023-04-28 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -************************************************************************* -** -** This file demonstrates how to create a table-valued-function using -** a virtual table. This demo implements the generate_series() function -** which gives the same results as the eponymous function in PostgreSQL, -** within the limitation that its arguments are signed 64-bit integers. -** -** Considering its equivalents to generate_series(start,stop,step): A -** value V[n] sequence is produced for integer n ascending from 0 where -** ( V[n] == start + n * step && sgn(V[n] - stop) * sgn(step) >= 0 ) -** for each produced value (independent of production time ordering.) -** -** All parameters must be either integer or convertable to integer. -** The start parameter is required. -** The stop parameter defaults to (1<<32)-1 (aka 4294967295 or 0xffffffff) -** The step parameter defaults to 1 and 0 is treated as 1. -** -** Examples: -** -** SELECT * FROM generate_series(0,100,5); -** -** The query above returns integers from 0 through 100 counting by steps -** of 5. -** -** SELECT * FROM generate_series(0,100); -** -** Integers from 0 through 100 with a step size of 1. -** -** SELECT * FROM generate_series(20) LIMIT 10; -** -** Integers 20 through 29. -** -** SELECT * FROM generate_series(0,-100,-5); -** -** Integers 0 -5 -10 ... -100. -** -** SELECT * FROM generate_series(0,-1); -** -** Empty sequence. -** -** HOW IT WORKS -** -** The generate_series "function" is really a virtual table with the -** following schema: -** -** CREATE TABLE generate_series( -** value, -** start HIDDEN, -** stop HIDDEN, -** step HIDDEN -** ); -** -** The virtual table also has a rowid, logically equivalent to n+1 where -** "n" is the ascending integer in the aforesaid production definition. -** -** Function arguments in queries against this virtual table are translated -** into equality constraints against successive hidden columns. In other -** words, the following pairs of queries are equivalent to each other: -** -** SELECT * FROM generate_series(0,100,5); -** SELECT * FROM generate_series WHERE start=0 AND stop=100 AND step=5; -** -** SELECT * FROM generate_series(0,100); -** SELECT * FROM generate_series WHERE start=0 AND stop=100; -** -** SELECT * FROM generate_series(20) LIMIT 10; -** SELECT * FROM generate_series WHERE start=20 LIMIT 10; -** -** The generate_series virtual table implementation leaves the xCreate method -** set to NULL. This means that it is not possible to do a CREATE VIRTUAL -** TABLE command with "generate_series" as the USING argument. Instead, there -** is a single generate_series virtual table that is always available without -** having to be created first. -** -** The xBestIndex method looks for equality constraints against the hidden -** start, stop, and step columns, and if present, it uses those constraints -** to bound the sequence of generated values. If the equality constraints -** are missing, it uses 0 for start, 4294967295 for stop, and 1 for step. -** xBestIndex returns a small cost when both start and stop are available, -** and a very large cost if either start or stop are unavailable. This -** encourages the query planner to order joins such that the bounds of the -** series are well-defined. +/* Resize the opcode and argument arrays for an RE under construction. */ -/* #include "sqlite3ext.h" */ -SQLITE_EXTENSION_INIT1 -#include -#include -#include +static int re_resize(ReCompiled *p, unsigned int N){ + char *aOp; + int *aArg; + if( N>p->mxAlloc ){ p->zErr = "REGEXP pattern too big"; return 1; } + aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); + if( aOp==0 ){ p->zErr = "out of memory"; return 1; } + p->aOp = aOp; + aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); + if( aArg==0 ){ p->zErr = "out of memory"; return 1; } + p->aArg = aArg; + p->nAlloc = N; + return 0; +} -#ifndef SQLITE_OMIT_VIRTUALTABLE -/* -** Return that member of a generate_series(...) sequence whose 0-based -** index is ix. The 0th member is given by smBase. The sequence members -** progress per ix increment by smStep. +/* Insert a new opcode and argument into an RE under construction. The +** insertion point is just prior to existing opcode iBefore. */ -static sqlite3_int64 genSeqMember(sqlite3_int64 smBase, - sqlite3_int64 smStep, - sqlite3_uint64 ix){ - if( ix>=(sqlite3_uint64)LLONG_MAX ){ - /* Get ix into signed i64 range. */ - ix -= (sqlite3_uint64)LLONG_MAX; - /* With 2's complement ALU, this next can be 1 step, but is split into - * 2 for UBSAN's satisfaction (and hypothetical 1's complement ALUs.) */ - smBase += (LLONG_MAX/2) * smStep; - smBase += (LLONG_MAX - LLONG_MAX/2) * smStep; - } - /* Under UBSAN (or on 1's complement machines), must do this last term - * in steps to avoid the dreaded (and harmless) signed multiply overlow. */ - if( ix>=2 ){ - sqlite3_int64 ix2 = (sqlite3_int64)ix/2; - smBase += ix2*smStep; - ix -= ix2; +static int re_insert(ReCompiled *p, int iBefore, int op, int arg){ + int i; + if( p->nAlloc<=p->nState && re_resize(p, p->nAlloc*2) ) return 0; + for(i=p->nState; i>iBefore; i--){ + p->aOp[i] = p->aOp[i-1]; + p->aArg[i] = p->aArg[i-1]; } - return smBase + ((sqlite3_int64)ix)*smStep; + p->nState++; + p->aOp[iBefore] = (char)op; + p->aArg[iBefore] = arg; + return iBefore; } -/* typedef unsigned char u8; */ +/* Append a new opcode and argument to the end of the RE under construction. +*/ +static int re_append(ReCompiled *p, int op, int arg){ + return re_insert(p, p->nState, op, arg); +} -typedef struct SequenceSpec { - sqlite3_int64 iBase; /* Starting value ("start") */ - sqlite3_int64 iTerm; /* Given terminal value ("stop") */ - sqlite3_int64 iStep; /* Increment ("step") */ - sqlite3_uint64 uSeqIndexMax; /* maximum sequence index (aka "n") */ - sqlite3_uint64 uSeqIndexNow; /* Current index during generation */ - sqlite3_int64 iValueNow; /* Current value during generation */ - u8 isNotEOF; /* Sequence generation not exhausted */ - u8 isReversing; /* Sequence is being reverse generated */ -} SequenceSpec; - -/* -** Prepare a SequenceSpec for use in generating an integer series -** given initialized iBase, iTerm and iStep values. Sequence is -** initialized per given isReversing. Other members are computed. -*/ -static void setupSequence( SequenceSpec *pss ){ - int bSameSigns; - pss->uSeqIndexMax = 0; - pss->isNotEOF = 0; - bSameSigns = (pss->iBase < 0)==(pss->iTerm < 0); - if( pss->iTerm < pss->iBase ){ - sqlite3_uint64 nuspan = 0; - if( bSameSigns ){ - nuspan = (sqlite3_uint64)(pss->iBase - pss->iTerm); - }else{ - /* Under UBSAN (or on 1's complement machines), must do this in steps. - * In this clause, iBase>=0 and iTerm<0 . */ - nuspan = 1; - nuspan += pss->iBase; - nuspan += -(pss->iTerm+1); - } - if( pss->iStep<0 ){ - pss->isNotEOF = 1; - if( nuspan==ULONG_MAX ){ - pss->uSeqIndexMax = ( pss->iStep>LLONG_MIN )? nuspan/-pss->iStep : 1; - }else if( pss->iStep>LLONG_MIN ){ - pss->uSeqIndexMax = nuspan/-pss->iStep; - } - } - }else if( pss->iTerm > pss->iBase ){ - sqlite3_uint64 puspan = 0; - if( bSameSigns ){ - puspan = (sqlite3_uint64)(pss->iTerm - pss->iBase); - }else{ - /* Under UBSAN (or on 1's complement machines), must do this in steps. - * In this clause, iTerm>=0 and iBase<0 . */ - puspan = 1; - puspan += pss->iTerm; - puspan += -(pss->iBase+1); - } - if( pss->iStep>0 ){ - pss->isNotEOF = 1; - pss->uSeqIndexMax = puspan/pss->iStep; - } - }else if( pss->iTerm == pss->iBase ){ - pss->isNotEOF = 1; - pss->uSeqIndexMax = 0; +/* Make a copy of N opcodes starting at iStart onto the end of the RE +** under construction. +*/ +static void re_copy(ReCompiled *p, int iStart, unsigned int N){ + if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; + memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); + memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); + p->nState += N; +} + +/* Return true if c is a hexadecimal digit character: [0-9a-fA-F] +** If c is a hex digit, also set *pV = (*pV)*16 + valueof(c). If +** c is not a hex digit *pV is unchanged. +*/ +static int re_hex(int c, int *pV){ + if( c>='0' && c<='9' ){ + c -= '0'; + }else if( c>='a' && c<='f' ){ + c -= 'a' - 10; + }else if( c>='A' && c<='F' ){ + c -= 'A' - 10; + }else{ + return 0; } - pss->uSeqIndexNow = (pss->isReversing)? pss->uSeqIndexMax : 0; - pss->iValueNow = (pss->isReversing) - ? genSeqMember(pss->iBase, pss->iStep, pss->uSeqIndexMax) - : pss->iBase; + *pV = (*pV)*16 + (c & 0xff); + return 1; } -/* -** Progress sequence generator to yield next value, if any. -** Leave its state to either yield next value or be at EOF. -** Return whether there is a next value, or 0 at EOF. +/* A backslash character has been seen, read the next character and +** return its interpretation. */ -static int progressSequence( SequenceSpec *pss ){ - if( !pss->isNotEOF ) return 0; - if( pss->isReversing ){ - if( pss->uSeqIndexNow > 0 ){ - pss->uSeqIndexNow--; - pss->iValueNow -= pss->iStep; - }else{ - pss->isNotEOF = 0; +static unsigned re_esc_char(ReCompiled *p){ + static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]-"; + static const char zTrans[] = "\a\f\n\r\t\v"; + int i, v = 0; + char c; + if( p->sIn.i>=p->sIn.mx ) return 0; + c = p->sIn.z[p->sIn.i]; + if( c=='u' && p->sIn.i+4sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + && re_hex(zIn[3],&v) + && re_hex(zIn[4],&v) + ){ + p->sIn.i += 5; + return v; + } + } + if( c=='x' && p->sIn.i+2sIn.mx ){ + const unsigned char *zIn = p->sIn.z + p->sIn.i; + if( re_hex(zIn[1],&v) + && re_hex(zIn[2],&v) + ){ + p->sIn.i += 3; + return v; } + } + for(i=0; zEsc[i] && zEsc[i]!=c; i++){} + if( zEsc[i] ){ + if( i<6 ) c = zTrans[i]; + p->sIn.i++; }else{ - if( pss->uSeqIndexNow < pss->uSeqIndexMax ){ - pss->uSeqIndexNow++; - pss->iValueNow += pss->iStep; - }else{ - pss->isNotEOF = 0; + p->zErr = "unknown \\ escape"; + } + return c; +} + +/* Forward declaration */ +static const char *re_subcompile_string(ReCompiled*); + +/* Peek at the next byte of input */ +static unsigned char rePeek(ReCompiled *p){ + return p->sIn.isIn.mx ? p->sIn.z[p->sIn.i] : 0; +} + +/* Compile RE text into a sequence of opcodes. Continue up to the +** first unmatched ")" character, then return. If an error is found, +** return a pointer to the error message string. +*/ +static const char *re_subcompile_re(ReCompiled *p){ + const char *zErr; + int iStart, iEnd, iGoto; + iStart = p->nState; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + while( rePeek(p)=='|' ){ + iEnd = p->nState; + re_insert(p, iStart, RE_OP_FORK, iEnd + 2 - iStart); + iGoto = re_append(p, RE_OP_GOTO, 0); + p->sIn.i++; + zErr = re_subcompile_string(p); + if( zErr ) return zErr; + p->aArg[iGoto] = p->nState - iGoto; + } + return 0; +} + +/* Compile an element of regular expression text (anything that can be +** an operand to the "|" operator). Return NULL on success or a pointer +** to the error message if there is a problem. +*/ +static const char *re_subcompile_string(ReCompiled *p){ + int iPrev = -1; + int iStart; + unsigned c; + const char *zErr; + while( (c = p->xNextChar(&p->sIn))!=0 ){ + iStart = p->nState; + switch( c ){ + case '|': + case ')': { + p->sIn.i--; + return 0; + } + case '(': { + zErr = re_subcompile_re(p); + if( zErr ) return zErr; + if( rePeek(p)!=')' ) return "unmatched '('"; + p->sIn.i++; + break; + } + case '.': { + if( rePeek(p)=='*' ){ + re_append(p, RE_OP_ANYSTAR, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_ANY, 0); + } + break; + } + case '*': { + if( iPrev<0 ) return "'*' without operand"; + re_insert(p, iPrev, RE_OP_GOTO, p->nState - iPrev + 1); + re_append(p, RE_OP_FORK, iPrev - p->nState + 1); + break; + } + case '+': { + if( iPrev<0 ) return "'+' without operand"; + re_append(p, RE_OP_FORK, iPrev - p->nState); + break; + } + case '?': { + if( iPrev<0 ) return "'?' without operand"; + re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); + break; + } + case '$': { + re_append(p, RE_OP_MATCH, RE_EOF); + break; + } + case '^': { + re_append(p, RE_OP_ATSTART, 0); + break; + } + case '{': { + unsigned int m = 0, n = 0; + unsigned int sz, j; + if( iPrev<0 ) return "'{m,n}' without operand"; + while( (c=rePeek(p))>='0' && c<='9' ){ + m = m*10 + c - '0'; + if( m*2>p->mxAlloc ) return "REGEXP pattern too big"; + p->sIn.i++; + } + n = m; + if( c==',' ){ + p->sIn.i++; + n = 0; + while( (c=rePeek(p))>='0' && c<='9' ){ + n = n*10 + c-'0'; + if( n*2>p->mxAlloc ) return "REGEXP pattern too big"; + p->sIn.i++; + } + } + if( c!='}' ) return "unmatched '{'"; + if( nsIn.i++; + sz = p->nState - iPrev; + if( m==0 ){ + if( n==0 ) return "both m and n are zero in '{m,n}'"; + re_insert(p, iPrev, RE_OP_FORK, sz+1); + iPrev++; + n--; + }else{ + for(j=1; j0 ){ + re_append(p, RE_OP_FORK, -(int)sz); + } + break; + } + case '[': { + unsigned int iFirst = p->nState; + if( rePeek(p)=='^' ){ + re_append(p, RE_OP_CC_EXC, 0); + p->sIn.i++; + }else{ + re_append(p, RE_OP_CC_INC, 0); + } + while( (c = p->xNextChar(&p->sIn))!=0 ){ + if( c=='[' && rePeek(p)==':' ){ + return "POSIX character classes not supported"; + } + if( c=='\\' ) c = re_esc_char(p); + if( rePeek(p)=='-' ){ + re_append(p, RE_OP_CC_RANGE, c); + p->sIn.i++; + c = p->xNextChar(&p->sIn); + if( c=='\\' ) c = re_esc_char(p); + re_append(p, RE_OP_CC_RANGE, c); + }else{ + re_append(p, RE_OP_CC_VALUE, c); + } + if( rePeek(p)==']' ){ p->sIn.i++; break; } + } + if( c==0 ) return "unclosed '['"; + if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst; + break; + } + case '\\': { + int specialOp = 0; + switch( rePeek(p) ){ + case 'b': specialOp = RE_OP_BOUNDARY; break; + case 'd': specialOp = RE_OP_DIGIT; break; + case 'D': specialOp = RE_OP_NOTDIGIT; break; + case 's': specialOp = RE_OP_SPACE; break; + case 'S': specialOp = RE_OP_NOTSPACE; break; + case 'w': specialOp = RE_OP_WORD; break; + case 'W': specialOp = RE_OP_NOTWORD; break; + } + if( specialOp ){ + p->sIn.i++; + re_append(p, specialOp, 0); + }else{ + c = re_esc_char(p); + re_append(p, RE_OP_MATCH, c); + } + break; + } + default: { + re_append(p, RE_OP_MATCH, c); + break; + } } + iPrev = iStart; } - return pss->isNotEOF; + return 0; } -/* series_cursor is a subclass of sqlite3_vtab_cursor which will -** serve as the underlying representation of a cursor that scans -** over rows of the result -*/ -typedef struct series_cursor series_cursor; -struct series_cursor { - sqlite3_vtab_cursor base; /* Base class - must be first */ - SequenceSpec ss; /* (this) Derived class data */ -}; - -/* -** The seriesConnect() method is invoked to create a new -** series_vtab that describes the generate_series virtual table. -** -** Think of this routine as the constructor for series_vtab objects. -** -** All this routine needs to do is: -** -** (1) Allocate the series_vtab object and initialize all fields. -** -** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the -** result set of queries against generate_series will look like. +/* Free and reclaim all the memory used by a previously compiled +** regular expression. Applications should invoke this routine once +** for every call to re_compile() to avoid memory leaks. */ -static int seriesConnect( - sqlite3 *db, - void *pUnused, - int argcUnused, const char *const*argvUnused, - sqlite3_vtab **ppVtab, - char **pzErrUnused -){ - sqlite3_vtab *pNew; - int rc; - -/* Column numbers */ -#define SERIES_COLUMN_VALUE 0 -#define SERIES_COLUMN_START 1 -#define SERIES_COLUMN_STOP 2 -#define SERIES_COLUMN_STEP 3 - - (void)pUnused; - (void)argcUnused; - (void)argvUnused; - (void)pzErrUnused; - rc = sqlite3_declare_vtab(db, - "CREATE TABLE x(value,start hidden,stop hidden,step hidden)"); - if( rc==SQLITE_OK ){ - pNew = *ppVtab = sqlite3_malloc( sizeof(*pNew) ); - if( pNew==0 ) return SQLITE_NOMEM; - memset(pNew, 0, sizeof(*pNew)); - sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); +static void re_free(ReCompiled *pRe){ + if( pRe ){ + sqlite3_free(pRe->aOp); + sqlite3_free(pRe->aArg); + sqlite3_free(pRe); } - return rc; -} - -/* -** This method is the destructor for series_cursor objects. -*/ -static int seriesDisconnect(sqlite3_vtab *pVtab){ - sqlite3_free(pVtab); - return SQLITE_OK; } /* -** Constructor for a new series_cursor object. +** Version of re_free() that accepts a pointer of type (void*). Required +** to satisfy sanitizers when the re_free() function is called via a +** function pointer. */ -static int seriesOpen(sqlite3_vtab *pUnused, sqlite3_vtab_cursor **ppCursor){ - series_cursor *pCur; - (void)pUnused; - pCur = sqlite3_malloc( sizeof(*pCur) ); - if( pCur==0 ) return SQLITE_NOMEM; - memset(pCur, 0, sizeof(*pCur)); - *ppCursor = &pCur->base; - return SQLITE_OK; +static void re_free_voidptr(void *p){ + re_free((ReCompiled*)p); } /* -** Destructor for a series_cursor. +** Compile a textual regular expression in zIn[] into a compiled regular +** expression suitable for us by re_match() and return a pointer to the +** compiled regular expression in *ppRe. Return NULL on success or an +** error message if something goes wrong. */ -static int seriesClose(sqlite3_vtab_cursor *cur){ - sqlite3_free(cur); - return SQLITE_OK; -} - +static const char *re_compile( + ReCompiled **ppRe, /* OUT: write compiled NFA here */ + const char *zIn, /* Input regular expression */ + int mxRe, /* Complexity limit */ + int noCase /* True for caseless comparisons */ +){ + ReCompiled *pRe; + const char *zErr; + int i, j; -/* -** Advance a series_cursor to its next row of output. -*/ -static int seriesNext(sqlite3_vtab_cursor *cur){ - series_cursor *pCur = (series_cursor*)cur; - progressSequence( & pCur->ss ); - return SQLITE_OK; -} + *ppRe = 0; + pRe = sqlite3_malloc64( sizeof(*pRe) ); + if( pRe==0 ){ + return "out of memory"; + } + memset(pRe, 0, sizeof(*pRe)); + pRe->xNextChar = noCase ? re_next_char_nocase : re_next_char; + pRe->mxAlloc = mxRe; + if( re_resize(pRe, 30) ){ + zErr = pRe->zErr; + re_free(pRe); + return zErr; + } + if( zIn[0]=='^' ){ + zIn++; + }else{ + re_append(pRe, RE_OP_ANYSTAR, 0); + } + pRe->sIn.z = (unsigned char*)zIn; + pRe->sIn.i = 0; + pRe->sIn.mx = (int)strlen(zIn); + zErr = re_subcompile_re(pRe); + if( zErr ){ + re_free(pRe); + return zErr; + } + if( pRe->sIn.i>=pRe->sIn.mx ){ + re_append(pRe, RE_OP_ACCEPT, 0); + *ppRe = pRe; + }else{ + re_free(pRe); + return "unrecognized character"; + } -/* -** Return values of columns for the row at which the series_cursor -** is currently pointing. -*/ -static int seriesColumn( - sqlite3_vtab_cursor *cur, /* The cursor */ - sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ - int i /* Which column to return */ -){ - series_cursor *pCur = (series_cursor*)cur; - sqlite3_int64 x = 0; - switch( i ){ - case SERIES_COLUMN_START: x = pCur->ss.iBase; break; - case SERIES_COLUMN_STOP: x = pCur->ss.iTerm; break; - case SERIES_COLUMN_STEP: x = pCur->ss.iStep; break; - default: x = pCur->ss.iValueNow; break; + /* The following is a performance optimization. If the regex begins with + ** ".*" (if the input regex lacks an initial "^") and afterwards there are + ** one or more matching characters, enter those matching characters into + ** zInit[]. The re_match() routine can then search ahead in the input + ** string looking for the initial match without having to run the whole + ** regex engine over the string. Do not worry about trying to match + ** unicode characters beyond plane 0 - those are very rare and this is + ** just an optimization. */ + if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ + for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ + unsigned x = pRe->aArg[i]; + if( x<=0x7f ){ + pRe->zInit[j++] = (unsigned char)x; + }else if( x<=0x7ff ){ + pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else if( x<=0xffff ){ + pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12)); + pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); + pRe->zInit[j++] = 0x80 | (x&0x3f); + }else{ + break; + } + } + if( j>0 && pRe->zInit[j-1]==0 ) j--; + pRe->nInit = j; } - sqlite3_result_int64(ctx, x); - return SQLITE_OK; + return pRe->zErr; } -#ifndef LARGEST_UINT64 -#define LARGEST_UINT64 (0xffffffff|(((sqlite3_uint64)0xffffffff)<<32)) -#endif - /* -** Return the rowid for the current row, logically equivalent to n+1 where -** "n" is the ascending integer in the aforesaid production definition. +** The value of LIMIT_MAX_PATTERN_LENGTH. */ -static int seriesRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ - series_cursor *pCur = (series_cursor*)cur; - sqlite3_uint64 n = pCur->ss.uSeqIndexNow; - *pRowid = (sqlite3_int64)((nss.isNotEOF; +static int re_maxnfa(int mxlen){ + return 75+mxlen/2; } -/* True to cause run-time checking of the start=, stop=, and/or step= -** parameters. The only reason to do this is for testing the -** constraint checking logic for virtual tables in the SQLite core. -*/ -#ifndef SQLITE_SERIES_CONSTRAINT_VERIFY -# define SQLITE_SERIES_CONSTRAINT_VERIFY 0 -#endif - /* -** This method is called to "rewind" the series_cursor object back -** to the first row of output. This method is always called at least -** once prior to any call to seriesColumn() or seriesRowid() or -** seriesEof(). -** -** The query plan selected by seriesBestIndex is passed in the idxNum -** parameter. (idxStr is not used in this implementation.) idxNum -** is a bitmask showing which constraints are available: -** -** 1: start=VALUE -** 2: stop=VALUE -** 4: step=VALUE +** Implementation of the regexp() SQL function. This function implements +** the build-in REGEXP operator. The first argument to the function is the +** pattern and the second argument is the string. So, the SQL statements: ** -** Also, if bit 8 is set, that means that the series should be output -** in descending order rather than in ascending order. If bit 16 is -** set, then output must appear in ascending order. +** A REGEXP B ** -** This routine should initialize the cursor and position it so that it -** is pointing at the first row, or pointing off the end of the table -** (so that seriesEof() will return true) if the table is empty. +** is implemented as regexp(B,A). */ -static int seriesFilter( - sqlite3_vtab_cursor *pVtabCursor, - int idxNum, const char *idxStrUnused, - int argc, sqlite3_value **argv +static void re_sql_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv ){ - series_cursor *pCur = (series_cursor *)pVtabCursor; - int i = 0; - (void)idxStrUnused; - if( idxNum & 1 ){ - pCur->ss.iBase = sqlite3_value_int64(argv[i++]); - }else{ - pCur->ss.iBase = 0; - } - if( idxNum & 2 ){ - pCur->ss.iTerm = sqlite3_value_int64(argv[i++]); - }else{ - pCur->ss.iTerm = 0xffffffff; - } - if( idxNum & 4 ){ - pCur->ss.iStep = sqlite3_value_int64(argv[i++]); - if( pCur->ss.iStep==0 ){ - pCur->ss.iStep = 1; - }else if( pCur->ss.iStep<0 ){ - if( (idxNum & 16)==0 ) idxNum |= 8; + ReCompiled *pRe; /* Compiled regular expression */ + const char *zPattern; /* The regular expression */ + const unsigned char *zStr;/* String being searched */ + const char *zErr; /* Compile error message */ + int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ + + (void)argc; /* Unused */ + pRe = sqlite3_get_auxdata(context, 0); + if( pRe==0 ){ + int mxLen = re_maxlen(context); + int nPattern; + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + nPattern = sqlite3_value_bytes(argv[0]); + if( nPattern>mxLen ){ + zErr = "REGEXP pattern too big"; + }else{ + zErr = re_compile(&pRe, zPattern, re_maxnfa(mxLen), + sqlite3_user_data(context)!=0); } - }else{ - pCur->ss.iStep = 1; - } - for(i=0; iss.iBase = 1; - pCur->ss.iTerm = 0; - pCur->ss.iStep = 1; - break; + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; + } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; } + setAux = 1; } - if( idxNum & 8 ){ - pCur->ss.isReversing = pCur->ss.iStep > 0; - }else{ - pCur->ss.isReversing = pCur->ss.iStep < 0; + zStr = (const unsigned char*)sqlite3_value_text(argv[1]); + if( zStr!=0 ){ + sqlite3_result_int(context, re_match(pRe, zStr, -1)); + } + if( setAux ){ + sqlite3_set_auxdata(context, 0, pRe, re_free_voidptr); } - setupSequence( &pCur->ss ); - return SQLITE_OK; } +#if defined(SQLITE_DEBUG) /* -** SQLite will invoke this method one or more times while planning a query -** that uses the generate_series virtual table. This routine needs to create -** a query plan for each invocation and compute an estimated cost for that -** plan. -** -** In this implementation idxNum is used to represent the -** query plan. idxStr is unused. -** -** The query plan is represented by bits in idxNum: +** This function is used for testing and debugging only. It is only available +** if the SQLITE_DEBUG compile-time option is used. ** -** (1) start = $value -- constraint exists -** (2) stop = $value -- constraint exists -** (4) step = $value -- constraint exists -** (8) output in descending order +** Compile a regular expression and then convert the compiled expression into +** text and return that text. */ -static int seriesBestIndex( - sqlite3_vtab *pVTab, - sqlite3_index_info *pIdxInfo +static void re_bytecode_func( + sqlite3_context *context, + int argc, + sqlite3_value **argv ){ - int i, j; /* Loop over constraints */ - int idxNum = 0; /* The query plan bitmask */ - int bStartSeen = 0; /* EQ constraint seen on the START column */ - int unusableMask = 0; /* Mask of unusable constraints */ - int nArg = 0; /* Number of arguments that seriesFilter() expects */ - int aIdx[3]; /* Constraints on start, stop, and step */ - const struct sqlite3_index_constraint *pConstraint; - - /* This implementation assumes that the start, stop, and step columns - ** are the last three columns in the virtual table. */ - assert( SERIES_COLUMN_STOP == SERIES_COLUMN_START+1 ); - assert( SERIES_COLUMN_STEP == SERIES_COLUMN_START+2 ); + const char *zPattern; + const char *zErr; + ReCompiled *pRe; + sqlite3_str *pStr; + int i; + int n; + char *z; + static const char *ReOpName[] = { + "EOF", + "MATCH", + "ANY", + "ANYSTAR", + "FORK", + "GOTO", + "ACCEPT", + "CC_INC", + "CC_EXC", + "CC_VALUE", + "CC_RANGE", + "WORD", + "NOTWORD", + "DIGIT", + "NOTDIGIT", + "SPACE", + "NOTSPACE", + "BOUNDARY", + "ATSTART", + }; - aIdx[0] = aIdx[1] = aIdx[2] = -1; - pConstraint = pIdxInfo->aConstraint; - for(i=0; inConstraint; i++, pConstraint++){ - int iCol; /* 0 for start, 1 for stop, 2 for step */ - int iMask; /* bitmask for those column */ - if( pConstraint->iColumniColumn - SERIES_COLUMN_START; - assert( iCol>=0 && iCol<=2 ); - iMask = 1 << iCol; - if( iCol==0 ) bStartSeen = 1; - if( pConstraint->usable==0 ){ - unusableMask |= iMask; - continue; - }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ - idxNum |= iMask; - aIdx[iCol] = i; - } + (void)argc; + zPattern = (const char*)sqlite3_value_text(argv[0]); + if( zPattern==0 ) return; + zErr = re_compile(&pRe, zPattern, re_maxnfa(re_maxlen(context)), + sqlite3_user_data(context)!=0); + if( zErr ){ + re_free(pRe); + sqlite3_result_error(context, zErr, -1); + return; } - for(i=0; i<3; i++){ - if( (j = aIdx[i])>=0 ){ - pIdxInfo->aConstraintUsage[j].argvIndex = ++nArg; - pIdxInfo->aConstraintUsage[j].omit = !SQLITE_SERIES_CONSTRAINT_VERIFY; - } + if( pRe==0 ){ + sqlite3_result_error_nomem(context); + return; } - /* The current generate_column() implementation requires at least one - ** argument (the START value). Legacy versions assumed START=0 if the - ** first argument was omitted. Compile with -DZERO_ARGUMENT_GENERATE_SERIES - ** to obtain the legacy behavior */ -#ifndef ZERO_ARGUMENT_GENERATE_SERIES - if( !bStartSeen ){ - sqlite3_free(pVTab->zErrMsg); - pVTab->zErrMsg = sqlite3_mprintf( - "first argument to \"generate_series()\" missing or unusable"); - return SQLITE_ERROR; + pStr = sqlite3_str_new(0); + if( pStr==0 ) goto re_bytecode_func_err; + if( pRe->nInit>0 ){ + sqlite3_str_appendf(pStr, "INIT "); + for(i=0; inInit; i++){ + sqlite3_str_appendf(pStr, "%02x", pRe->zInit[i]); + } + sqlite3_str_appendf(pStr, "\n"); } -#endif - if( (unusableMask & ~idxNum)!=0 ){ - /* The start, stop, and step columns are inputs. Therefore if there - ** are unusable constraints on any of start, stop, or step then - ** this plan is unusable */ - return SQLITE_CONSTRAINT; + for(i=0; (unsigned)inState; i++){ + sqlite3_str_appendf(pStr, "%-8s %4d\n", + ReOpName[(unsigned char)pRe->aOp[i]], pRe->aArg[i]); } - if( (idxNum & 3)==3 ){ - /* Both start= and stop= boundaries are available. This is the - ** the preferred case */ - pIdxInfo->estimatedCost = (double)(2 - ((idxNum&4)!=0)); - pIdxInfo->estimatedRows = 1000; - if( pIdxInfo->nOrderBy>=1 && pIdxInfo->aOrderBy[0].iColumn==0 ){ - if( pIdxInfo->aOrderBy[0].desc ){ - idxNum |= 8; - }else{ - idxNum |= 16; - } - pIdxInfo->orderByConsumed = 1; - } + n = sqlite3_str_length(pStr); + z = sqlite3_str_finish(pStr); + if( n==0 ){ + sqlite3_free(z); }else{ - /* If either boundary is missing, we have to generate a huge span - ** of numbers. Make this case very expensive so that the query - ** planner will work hard to avoid it. */ - pIdxInfo->estimatedRows = 2147483647; + sqlite3_result_text(context, z, n-1, sqlite3_free); } - pIdxInfo->idxNum = idxNum; - return SQLITE_OK; + +re_bytecode_func_err: + re_free(pRe); } -/* -** This following structure defines all the methods for the -** generate_series virtual table. -*/ -static sqlite3_module seriesModule = { - 0, /* iVersion */ - 0, /* xCreate */ - seriesConnect, /* xConnect */ - seriesBestIndex, /* xBestIndex */ - seriesDisconnect, /* xDisconnect */ - 0, /* xDestroy */ - seriesOpen, /* xOpen - open a cursor */ - seriesClose, /* xClose - close a cursor */ - seriesFilter, /* xFilter - configure scan constraints */ - seriesNext, /* xNext - advance a cursor */ - seriesEof, /* xEof - check for end of scan */ - seriesColumn, /* xColumn - read data */ - seriesRowid, /* xRowid - read data */ - 0, /* xUpdate */ - 0, /* xBegin */ - 0, /* xSync */ - 0, /* xCommit */ - 0, /* xRollback */ - 0, /* xFindMethod */ - 0, /* xRename */ - 0, /* xSavepoint */ - 0, /* xRelease */ - 0, /* xRollbackTo */ - 0 /* xShadowName */ -}; +#endif /* SQLITE_DEBUG */ -#endif /* SQLITE_OMIT_VIRTUALTABLE */ +/* +** Invoke this routine to register the regexp() function with the +** SQLite database connection. +*/ #ifdef _WIN32 #endif -int sqlite3_series_init( +int sqlite3_regexp_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); -#ifndef SQLITE_OMIT_VIRTUALTABLE - if( sqlite3_libversion_number()<3008012 && pzErrMsg!=0 ){ - *pzErrMsg = sqlite3_mprintf( - "generate_series() requires SQLite 3.8.12 or later"); - return SQLITE_ERROR; + (void)pzErrMsg; /* Unused */ + rc = sqlite3_create_function(db, "regexp", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + 0, re_sql_func, 0, 0); + if( rc==SQLITE_OK ){ + /* The regexpi(PATTERN,STRING) function is a case-insensitive version + ** of regexp(PATTERN,STRING). */ + rc = sqlite3_create_function(db, "regexpi", 2, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + (void*)db, re_sql_func, 0, 0); +#if defined(SQLITE_DEBUG) + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "regexp_bytecode", 1, + SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, + 0, re_bytecode_func, 0, 0); + } +#endif /* SQLITE_DEBUG */ } - rc = sqlite3_create_module(db, "generate_series", &seriesModule, 0); -#endif return rc; } -/************************* End ../ext/misc/series.c ********************/ -/************************* Begin ../ext/misc/regexp.c ******************/ +/************************* End ext/misc/regexp.c ********************/ +#ifndef SQLITE_SHELL_FIDDLE +/************************* Begin ext/misc/fileio.c ******************/ /* -** 2012-11-13 +** 2014-06-13 ** ** The author disclaims copyright to this source code. In place of ** a legal notice, here is a blessing: @@ -5412,7680 +9701,9702 @@ int sqlite3_series_init( ** ****************************************************************************** ** -** The code in this file implements a compact but reasonably -** efficient regular-expression matcher for posix extended regular -** expressions against UTF8 text. +** This SQLite extension implements SQL functions readfile() and +** writefile(), and eponymous virtual type "fsdir". ** -** This file is an SQLite extension. It registers a single function -** named "regexp(A,B)" where A is the regular expression and B is the -** string to be matched. By registering this function, SQLite will also -** then implement the "B regexp A" operator. Note that with the function -** the regular expression comes first, but with the operator it comes -** second. +** WRITEFILE(FILE, DATA [, MODE [, MTIME]]): ** -** The following regular expression syntax is supported: +** If neither of the optional arguments is present, then this UDF +** function writes blob DATA to file FILE. If successful, the number +** of bytes written is returned. If an error occurs, NULL is returned. ** -** X* zero or more occurrences of X -** X+ one or more occurrences of X -** X? zero or one occurrences of X -** X{p,q} between p and q occurrences of X -** (X) match X -** X|Y X or Y -** ^X X occurring at the beginning of the string -** X$ X occurring at the end of the string -** . Match any single character -** \c Character c where c is one of \{}()[]|*+?. -** \c C-language escapes for c in afnrtv. ex: \t or \n -** \uXXXX Where XXXX is exactly 4 hex digits, unicode value XXXX -** \xXX Where XX is exactly 2 hex digits, unicode value XX -** [abc] Any single character from the set abc -** [^abc] Any single character not in the set abc -** [a-z] Any single character in the range a-z -** [^a-z] Any single character not in the range a-z -** \b Word boundary -** \w Word character. [A-Za-z0-9_] -** \W Non-word character -** \d Digit -** \D Non-digit -** \s Whitespace character -** \S Non-whitespace character +** If the first option argument - MODE - is present, then it must +** be passed an integer value that corresponds to a POSIX mode +** value (file type + permissions, as returned in the stat.st_mode +** field by the stat() system call). Three types of files may +** be written/created: +** +** regular files: (mode & 0170000)==0100000 +** symbolic links: (mode & 0170000)==0120000 +** directories: (mode & 0170000)==0040000 +** +** For a directory, the DATA is ignored. For a symbolic link, it is +** interpreted as text and used as the target of the link. For a +** regular file, it is interpreted as a blob and written into the +** named file. Regardless of the type of file, its permissions are +** set to (mode & 0777) before returning. +** +** If the optional MTIME argument is present, then it is interpreted +** as an integer - the number of seconds since the unix epoch. The +** modification-time of the target file is set to this value before +** returning. +** +** If five or more arguments are passed to this function and an +** error is encountered, an exception is raised. +** +** READFILE(FILE): ** -** A nondeterministic finite automaton (NFA) is used for matching, so the -** performance is bounded by O(N*M) where N is the size of the regular -** expression and M is the size of the input string. The matcher never -** exhibits exponential behavior. Note that the X{p,q} operator expands -** to p copies of X following by q-p copies of X? and that the size of the -** regular expression in the O(N*M) performance bound is computed after -** this expansion. +** Read and return the contents of file FILE (type blob) from disk. +** +** FSDIR: +** +** Used as follows: +** +** SELECT * FROM fsdir($path [, $dir]); +** +** Parameter $path is an absolute or relative pathname. If the file that it +** refers to does not exist, it is an error. If the path refers to a regular +** file or symbolic link, it returns a single row. Or, if the path refers +** to a directory, it returns one row for the directory, and one row for each +** file within the hierarchy rooted at $path. +** +** Each row has the following columns: +** +** name: Path to file or directory (text value). +** mode: Value of stat.st_mode for directory entry (an integer). +** mtime: Value of stat.st_mtime for directory entry (an integer). +** data: For a regular file, a blob containing the file data. For a +** symlink, a text value containing the text of the link. For a +** directory, NULL. +** level: Directory hierarchy level. Topmost is 1. +** +** If a non-NULL value is specified for the optional $dir parameter and +** $path is a relative path, then $path is interpreted relative to $dir. +** And the paths returned in the "name" column of the table are also +** relative to directory $dir. */ -#include -#include /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 +#include +#include +#include -/* -** The following #defines change the names of some functions implemented in -** this file to prevent name collisions with C-library functions of the -** same name. -*/ -#define re_match sqlite3re_match -#define re_compile sqlite3re_compile -#define re_free sqlite3re_free - -/* The end-of-input character */ -#define RE_EOF 0 /* End of input */ -#define RE_START 0xfffffff /* Start of input - larger than an UTF-8 */ +#include +#include +#include +#if !defined(_WIN32) && !defined(WIN32) +# include +# include +# include +# include +# define STRUCT_STAT struct stat +# include +# include +#else +/* # include "windirent.h" */ +# include +# define STRUCT_STAT struct _stat +# define chmod(path,mode) fileio_chmod(path,mode) +# define mkdir(path,mode) fileio_mkdir(path) + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + extern char *sqlite3_win32_unicode_to_utf8(LPCWSTR); +#endif +#include +#include -/* The NFA is implemented as sequence of opcodes taken from the following -** set. Each opcode has a single integer argument. +/* When used as part of the CLI, the sqlite3_stdio.h module will have +** been included before this one. In that case use the sqlite3_stdio.h +** #defines. If not, create our own for fopen(). */ -#define RE_OP_MATCH 1 /* Match the one character in the argument */ -#define RE_OP_ANY 2 /* Match any one character. (Implements ".") */ -#define RE_OP_ANYSTAR 3 /* Special optimized version of .* */ -#define RE_OP_FORK 4 /* Continue to both next and opcode at iArg */ -#define RE_OP_GOTO 5 /* Jump to opcode at iArg */ -#define RE_OP_ACCEPT 6 /* Halt and indicate a successful match */ -#define RE_OP_CC_INC 7 /* Beginning of a [...] character class */ -#define RE_OP_CC_EXC 8 /* Beginning of a [^...] character class */ -#define RE_OP_CC_VALUE 9 /* Single value in a character class */ -#define RE_OP_CC_RANGE 10 /* Range of values in a character class */ -#define RE_OP_WORD 11 /* Perl word character [A-Za-z0-9_] */ -#define RE_OP_NOTWORD 12 /* Not a perl word character */ -#define RE_OP_DIGIT 13 /* digit: [0-9] */ -#define RE_OP_NOTDIGIT 14 /* Not a digit */ -#define RE_OP_SPACE 15 /* space: [ \t\n\r\v\f] */ -#define RE_OP_NOTSPACE 16 /* Not a digit */ -#define RE_OP_BOUNDARY 17 /* Boundary between word and non-word */ -#define RE_OP_ATSTART 18 /* Currently at the start of the string */ - -#if defined(SQLITE_DEBUG) -/* Opcode names used for symbolic debugging */ -static const char *ReOpName[] = { - "EOF", - "MATCH", - "ANY", - "ANYSTAR", - "FORK", - "GOTO", - "ACCEPT", - "CC_INC", - "CC_EXC", - "CC_VALUE", - "CC_RANGE", - "WORD", - "NOTWORD", - "DIGIT", - "NOTDIGIT", - "SPACE", - "NOTSPACE", - "BOUNDARY", - "ATSTART", -}; -#endif /* SQLITE_DEBUG */ +#ifndef _SQLITE3_STDIO_H_ +# define sqlite3_fopen fopen +#endif +/* +** Structure of the fsdir() table-valued function +*/ + /* 0 1 2 3 4 5 6 */ +#define FSDIR_SCHEMA "(name,mode,mtime,data,level,path HIDDEN,dir HIDDEN)" -/* Each opcode is a "state" in the NFA */ -typedef unsigned short ReStateNumber; +#define FSDIR_COLUMN_NAME 0 /* Name of the file */ +#define FSDIR_COLUMN_MODE 1 /* Access mode */ +#define FSDIR_COLUMN_MTIME 2 /* Last modification time */ +#define FSDIR_COLUMN_DATA 3 /* File content */ +#define FSDIR_COLUMN_LEVEL 4 /* Level. Topmost is 1 */ +#define FSDIR_COLUMN_PATH 5 /* Path to top of search */ +#define FSDIR_COLUMN_DIR 6 /* Path is relative to this directory */ -/* Because this is an NFA and not a DFA, multiple states can be active at -** once. An instance of the following object records all active states in -** the NFA. The implementation is optimized for the common case where the -** number of actives states is small. +/* +** UTF8 chmod() function for Windows */ -typedef struct ReStateSet { - unsigned nState; /* Number of current states */ - ReStateNumber *aState; /* Current states */ -} ReStateSet; +#if defined(_WIN32) || defined(WIN32) +static int fileio_chmod(const char *zPath, int pmode){ + int rc; + wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); + if( b1==0 ) return -1; + rc = _wchmod(b1, pmode); + sqlite3_free(b1); + return rc; +} +#endif -/* An input string read one character at a time. +/* +** UTF8 mkdir() function for Windows */ -typedef struct ReInput ReInput; -struct ReInput { - const unsigned char *z; /* All text */ - int i; /* Next byte to read */ - int mx; /* EOF when i>=mx */ -}; +#if defined(_WIN32) || defined(WIN32) +static int fileio_mkdir(const char *zPath){ + int rc; + wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); + if( b1==0 ) return -1; + rc = _wmkdir(b1); + sqlite3_free(b1); + return rc; +} +#endif -/* A compiled NFA (or an NFA that is in the process of being compiled) is -** an instance of the following object. + +/* +** Set the result stored by context ctx to a blob containing the +** contents of file zName. Or, leave the result unchanged (NULL) +** if the file does not exist or is unreadable. +** +** If the file exceeds the SQLite blob size limit, through an +** SQLITE_TOOBIG error. +** +** Throw an SQLITE_IOERR if there are difficulties pulling the file +** off of disk. */ -typedef struct ReCompiled ReCompiled; -struct ReCompiled { - ReInput sIn; /* Regular expression text */ - const char *zErr; /* Error message to return */ - char *aOp; /* Operators for the virtual machine */ - int *aArg; /* Arguments to each operator */ - unsigned (*xNextChar)(ReInput*); /* Next character function */ - unsigned char zInit[12]; /* Initial text to match */ - int nInit; /* Number of bytes in zInit */ - unsigned nState; /* Number of entries in aOp[] and aArg[] */ - unsigned nAlloc; /* Slots allocated for aOp[] and aArg[] */ -}; +static void readFileContents(sqlite3_context *ctx, const char *zName){ + FILE *in; + sqlite3_int64 nIn; + void *pBuf; + sqlite3 *db; + int mxBlob; -/* Add a state to the given state set if it is not already there */ -static void re_add_state(ReStateSet *pSet, int newState){ - unsigned i; - for(i=0; inState; i++) if( pSet->aState[i]==newState ) return; - pSet->aState[pSet->nState++] = (ReStateNumber)newState; + in = sqlite3_fopen(zName, "rb"); + if( in==0 ){ + /* File does not exist or is unreadable. Leave the result set to NULL. */ + return; + } + fseek(in, 0, SEEK_END); + nIn = ftell(in); + rewind(in); + db = sqlite3_context_db_handle(ctx); + mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); + if( nIn>mxBlob ){ + sqlite3_result_error_code(ctx, SQLITE_TOOBIG); + fclose(in); + return; + } + pBuf = sqlite3_malloc64( nIn ? nIn : 1 ); + if( pBuf==0 ){ + sqlite3_result_error_nomem(ctx); + fclose(in); + return; + } + if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){ + sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free); + }else{ + sqlite3_result_error_code(ctx, SQLITE_IOERR); + sqlite3_free(pBuf); + } + fclose(in); } -/* Extract the next unicode character from *pzIn and return it. Advance -** *pzIn to the first byte past the end of the character returned. To -** be clear: this routine converts utf8 to unicode. This routine is -** optimized for the common case where the next character is a single byte. +/* +** Implementation of the "readfile(X)" SQL function. The entire content +** of the file named X is read and returned as a BLOB. NULL is returned +** if the file does not exist or is unreadable. */ -static unsigned re_next_char(ReInput *p){ - unsigned c; - if( p->i>=p->mx ) return 0; - c = p->z[p->i++]; - if( c>=0x80 ){ - if( (c&0xe0)==0xc0 && p->imx && (p->z[p->i]&0xc0)==0x80 ){ - c = (c&0x1f)<<6 | (p->z[p->i++]&0x3f); - if( c<0x80 ) c = 0xfffd; - }else if( (c&0xf0)==0xe0 && p->i+1mx && (p->z[p->i]&0xc0)==0x80 - && (p->z[p->i+1]&0xc0)==0x80 ){ - c = (c&0x0f)<<12 | ((p->z[p->i]&0x3f)<<6) | (p->z[p->i+1]&0x3f); - p->i += 2; - if( c<=0x7ff || (c>=0xd800 && c<=0xdfff) ) c = 0xfffd; - }else if( (c&0xf8)==0xf0 && p->i+2mx && (p->z[p->i]&0xc0)==0x80 - && (p->z[p->i+1]&0xc0)==0x80 && (p->z[p->i+2]&0xc0)==0x80 ){ - c = (c&0x07)<<18 | ((p->z[p->i]&0x3f)<<12) | ((p->z[p->i+1]&0x3f)<<6) - | (p->z[p->i+2]&0x3f); - p->i += 3; - if( c<=0xffff || c>0x10ffff ) c = 0xfffd; - }else{ - c = 0xfffd; - } - } - return c; +static void readfileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zName; + (void)(argc); /* Unused parameter */ + zName = (const char*)sqlite3_value_text(argv[0]); + if( zName==0 ) return; + readFileContents(context, zName); } -static unsigned re_next_char_nocase(ReInput *p){ - unsigned c = re_next_char(p); - if( c>='A' && c<='Z' ) c += 'a' - 'A'; - return c; + +/* +** Set the error message contained in context ctx to the results of +** vprintf(zFmt, ...). +*/ +static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ + char *zMsg = 0; + va_list ap; + va_start(ap, zFmt); + zMsg = sqlite3_vmprintf(zFmt, ap); + sqlite3_result_error(ctx, zMsg, -1); + sqlite3_free(zMsg); + va_end(ap); } -/* Return true if c is a perl "word" character: [A-Za-z0-9_] */ -static int re_word_char(int c){ - return (c>='0' && c<='9') || (c>='a' && c<='z') - || (c>='A' && c<='Z') || c=='_'; +#if defined(_WIN32) +/* +** This function is designed to convert a Win32 FILETIME structure into the +** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC). +*/ +static sqlite3_uint64 fileTimeToUnixTime( + LPFILETIME pFileTime +){ + SYSTEMTIME epochSystemTime; + ULARGE_INTEGER epochIntervals; + FILETIME epochFileTime; + ULARGE_INTEGER fileIntervals; + + memset(&epochSystemTime, 0, sizeof(SYSTEMTIME)); + epochSystemTime.wYear = 1970; + epochSystemTime.wMonth = 1; + epochSystemTime.wDay = 1; + SystemTimeToFileTime(&epochSystemTime, &epochFileTime); + epochIntervals.LowPart = epochFileTime.dwLowDateTime; + epochIntervals.HighPart = epochFileTime.dwHighDateTime; + + fileIntervals.LowPart = pFileTime->dwLowDateTime; + fileIntervals.HighPart = pFileTime->dwHighDateTime; + + return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; } +#endif /* _WIN32 */ -/* Return true if c is a "digit" character: [0-9] */ -static int re_digit_char(int c){ - return (c>='0' && c<='9'); +/* +** This function is used in place of stat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls stat(). +*/ +static int fileStat( + const char *zPath, + STRUCT_STAT *pStatBuf +){ +#if defined(_WIN32) + int rc; + wchar_t *b1 = sqlite3_win32_utf8_to_unicode(zPath); + if( b1==0 ) return 1; + rc = _wstat(b1, pStatBuf); + if( rc==0 ){ + HANDLE hFindFile; + WIN32_FIND_DATAW fd; + memset(&fd, 0, sizeof(WIN32_FIND_DATAW)); + hFindFile = FindFirstFileW(b1, &fd); + if( hFindFile!=NULL ){ + pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime); + pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime); + pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime); + FindClose(hFindFile); + } + } + sqlite3_free(b1); + return rc; +#else + return stat(zPath, pStatBuf); +#endif } -/* Return true if c is a perl "space" character: [ \t\r\n\v\f] */ -static int re_space_char(int c){ - return c==' ' || c=='\t' || c=='\n' || c=='\r' || c=='\v' || c=='\f'; +/* +** This function is used in place of lstat(). On Windows, special handling +** is required in order for the included time to be returned as UTC. On all +** other systems, this function simply calls lstat(). +*/ +static int fileLinkStat( + const char *zPath, + STRUCT_STAT *pStatBuf +){ +#if defined(_WIN32) + return fileStat(zPath, pStatBuf); +#else + return lstat(zPath, pStatBuf); +#endif } -/* Run a compiled regular expression on the zero-terminated input -** string zIn[]. Return true on a match and false if there is no match. +/* +** Argument zFile is the name of a file that will be created and/or written +** by SQL function writefile(). This function ensures that the directory +** zFile will be written to exists, creating it if required. The permissions +** for any path components created by this function are set in accordance +** with the current umask. +** +** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise, +** SQLITE_OK is returned if the directory is successfully created, or +** SQLITE_ERROR otherwise. */ -static int re_match(ReCompiled *pRe, const unsigned char *zIn, int nIn){ - ReStateSet aStateSet[2], *pThis, *pNext; - ReStateNumber aSpace[100]; - ReStateNumber *pToFree; - unsigned int i = 0; - unsigned int iSwap = 0; - int c = RE_START; - int cPrev = 0; - int rc = 0; - ReInput in; +static int makeDirectory( + const char *zFile +){ + char *zCopy = sqlite3_mprintf("%s", zFile); + int rc = SQLITE_OK; - in.z = zIn; - in.i = 0; - in.mx = nIn>=0 ? nIn : (int)strlen((char const*)zIn); + if( zCopy==0 ){ + rc = SQLITE_NOMEM; + }else{ + int nCopy = (int)strlen(zCopy); + int i = 1; - /* Look for the initial prefix match, if there is one. */ - if( pRe->nInit ){ - unsigned char x = pRe->zInit[0]; - while( in.i+pRe->nInit<=in.mx - && (zIn[in.i]!=x || - strncmp((const char*)zIn+in.i, (const char*)pRe->zInit, pRe->nInit)!=0) - ){ - in.i++; + while( rc==SQLITE_OK ){ + STRUCT_STAT sStat; + int rc2; + + for(; zCopy[i]!='/' && inInit>in.mx ) return 0; - c = RE_START-1; - } - if( pRe->nState<=(sizeof(aSpace)/(sizeof(aSpace[0])*2)) ){ - pToFree = 0; - aStateSet[0].aState = aSpace; - }else{ - pToFree = sqlite3_malloc64( sizeof(ReStateNumber)*2*pRe->nState ); - if( pToFree==0 ) return -1; - aStateSet[0].aState = pToFree; + sqlite3_free(zCopy); } - aStateSet[1].aState = &aStateSet[0].aState[pRe->nState]; - pNext = &aStateSet[1]; - pNext->nState = 0; - re_add_state(pNext, 0); - while( c!=RE_EOF && pNext->nState>0 ){ - cPrev = c; - c = pRe->xNextChar(&in); - pThis = pNext; - pNext = &aStateSet[iSwap]; - iSwap = 1 - iSwap; - pNext->nState = 0; - for(i=0; inState; i++){ - int x = pThis->aState[i]; - switch( pRe->aOp[x] ){ - case RE_OP_MATCH: { - if( pRe->aArg[x]==c ) re_add_state(pNext, x+1); - break; - } - case RE_OP_ATSTART: { - if( cPrev==RE_START ) re_add_state(pThis, x+1); - break; - } - case RE_OP_ANY: { - if( c!=0 ) re_add_state(pNext, x+1); - break; - } - case RE_OP_WORD: { - if( re_word_char(c) ) re_add_state(pNext, x+1); - break; - } - case RE_OP_NOTWORD: { - if( !re_word_char(c) && c!=0 ) re_add_state(pNext, x+1); - break; - } - case RE_OP_DIGIT: { - if( re_digit_char(c) ) re_add_state(pNext, x+1); - break; - } - case RE_OP_NOTDIGIT: { - if( !re_digit_char(c) && c!=0 ) re_add_state(pNext, x+1); - break; - } - case RE_OP_SPACE: { - if( re_space_char(c) ) re_add_state(pNext, x+1); - break; - } - case RE_OP_NOTSPACE: { - if( !re_space_char(c) && c!=0 ) re_add_state(pNext, x+1); - break; - } - case RE_OP_BOUNDARY: { - if( re_word_char(c)!=re_word_char(cPrev) ) re_add_state(pThis, x+1); - break; - } - case RE_OP_ANYSTAR: { - re_add_state(pNext, x); - re_add_state(pThis, x+1); - break; - } - case RE_OP_FORK: { - re_add_state(pThis, x+pRe->aArg[x]); - re_add_state(pThis, x+1); - break; - } - case RE_OP_GOTO: { - re_add_state(pThis, x+pRe->aArg[x]); - break; - } - case RE_OP_ACCEPT: { - rc = 1; - goto re_match_end; - } - case RE_OP_CC_EXC: { - if( c==0 ) break; - /* fall-through */ goto re_op_cc_inc; - } - case RE_OP_CC_INC: re_op_cc_inc: { - int j = 1; - int n = pRe->aArg[x]; - int hit = 0; - for(j=1; j>0 && jaOp[x+j]==RE_OP_CC_VALUE ){ - if( pRe->aArg[x+j]==c ){ - hit = 1; - j = -1; - } - }else{ - if( pRe->aArg[x+j]<=c && pRe->aArg[x+j+1]>=c ){ - hit = 1; - j = -1; - }else{ - j++; - } - } - } - if( pRe->aOp[x]==RE_OP_CC_EXC ) hit = !hit; - if( hit ) re_add_state(pNext, x+n); - break; + + return rc; +} + +/* +** This function does the work for the writefile() UDF. Refer to +** header comments at the top of this file for details. +*/ +static int writeFile( + sqlite3_context *pCtx, /* Context to return bytes written in */ + const char *zFile, /* File to write */ + sqlite3_value *pData, /* Data to write */ + mode_t mode, /* MODE parameter passed to writefile() */ + sqlite3_int64 mtime /* MTIME parameter (or -1 to not set time) */ +){ + if( zFile==0 ) return 1; +#if !defined(_WIN32) && !defined(WIN32) + if( S_ISLNK(mode) ){ + const char *zTo = (const char*)sqlite3_value_text(pData); + if( zTo==0 ) return 1; + unlink(zFile); + if( symlink(zTo, zFile)<0 ) return 1; + }else +#endif + { + if( S_ISDIR(mode) ){ + if( mkdir(zFile, mode) ){ + /* The mkdir() call to create the directory failed. This might not + ** be an error though - if there is already a directory at the same + ** path and either the permissions already match or can be changed + ** to do so using chmod(), it is not an error. */ + STRUCT_STAT sStat; + if( errno!=EEXIST + || 0!=fileStat(zFile, &sStat) + || !S_ISDIR(sStat.st_mode) + || ((sStat.st_mode&0777)!=(mode&0777) && 0!=chmod(zFile, mode&0777)) + ){ + return 1; + } + } + }else{ + sqlite3_int64 nWrite = 0; + const char *z; + int rc = 0; + FILE *out = sqlite3_fopen(zFile, "wb"); + if( out==0 ) return 1; + z = (const char*)sqlite3_value_blob(pData); + if( z ){ + sqlite3_int64 n = fwrite(z, 1, sqlite3_value_bytes(pData), out); + nWrite = sqlite3_value_bytes(pData); + if( nWrite!=n ){ + rc = 1; } } + fclose(out); + if( rc==0 && mode && chmod(zFile, mode & 0777) ){ + rc = 1; + } + if( rc ) return 2; + sqlite3_result_int64(pCtx, nWrite); } } - for(i=0; inState; i++){ - int x = pNext->aState[i]; - while( pRe->aOp[x]==RE_OP_GOTO ) x += pRe->aArg[x]; - if( pRe->aOp[x]==RE_OP_ACCEPT ){ rc = 1; break; } + + if( mtime>=0 ){ +#if defined(_WIN32) + /* Windows */ + FILETIME lastAccess; + FILETIME lastWrite; + SYSTEMTIME currentTime; + LONGLONG intervals; + HANDLE hFile; + LPWSTR zUnicodeName; + extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); + + GetSystemTime(¤tTime); + SystemTimeToFileTime(¤tTime, &lastAccess); + intervals = (mtime*10000000) + 116444736000000000; + lastWrite.dwLowDateTime = (DWORD)intervals; + lastWrite.dwHighDateTime = intervals >> 32; + zUnicodeName = sqlite3_win32_utf8_to_unicode(zFile); + if( zUnicodeName==0 ){ + return 1; + } + hFile = CreateFileW( + zUnicodeName, FILE_WRITE_ATTRIBUTES, 0, NULL, OPEN_EXISTING, + FILE_FLAG_BACKUP_SEMANTICS, NULL + ); + sqlite3_free(zUnicodeName); + if( hFile!=INVALID_HANDLE_VALUE ){ + BOOL bResult = SetFileTime(hFile, NULL, &lastAccess, &lastWrite); + CloseHandle(hFile); + return !bResult; + }else{ + return 1; + } +#elif defined(AT_FDCWD) && 0 /* utimensat() is not universally available */ + /* Recent unix */ + struct timespec times[2]; + times[0].tv_nsec = times[1].tv_nsec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimensat(AT_FDCWD, zFile, times, AT_SYMLINK_NOFOLLOW) ){ + return 1; + } +#else + /* Legacy unix. + ** + ** Do not use utimes() on a symbolic link - it sees through the link and + ** modifies the timestamps on the target. Or fails if the target does + ** not exist. */ + if( 0==S_ISLNK(mode) ){ + struct timeval times[2]; + times[0].tv_usec = times[1].tv_usec = 0; + times[0].tv_sec = time(0); + times[1].tv_sec = mtime; + if( utimes(zFile, times) ){ + return 1; + } + } +#endif } -re_match_end: - sqlite3_free(pToFree); - return rc; + + return 0; } -/* Resize the opcode and argument arrays for an RE under construction. +/* +** Implementation of the "writefile(W,X[,Y[,Z]]])" SQL function. +** Refer to header comments at the top of this file for details. */ -static int re_resize(ReCompiled *p, int N){ - char *aOp; - int *aArg; - aOp = sqlite3_realloc64(p->aOp, N*sizeof(p->aOp[0])); - if( aOp==0 ) return 1; - p->aOp = aOp; - aArg = sqlite3_realloc64(p->aArg, N*sizeof(p->aArg[0])); - if( aArg==0 ) return 1; - p->aArg = aArg; - p->nAlloc = N; - return 0; +static void writefileFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ + const char *zFile; + mode_t mode = 0; + int res; + sqlite3_int64 mtime = -1; + + if( argc<2 || argc>4 ){ + sqlite3_result_error(context, + "wrong number of arguments to function writefile()", -1 + ); + return; + } + + zFile = (const char*)sqlite3_value_text(argv[0]); + if( zFile==0 ) return; + if( argc>=3 ){ + mode = (mode_t)sqlite3_value_int(argv[2]); + } + if( argc==4 ){ + mtime = sqlite3_value_int64(argv[3]); + } + + res = writeFile(context, zFile, argv[1], mode, mtime); + if( res==1 && errno==ENOENT ){ + if( makeDirectory(zFile)==SQLITE_OK ){ + res = writeFile(context, zFile, argv[1], mode, mtime); + } + } + + if( argc>2 && res!=0 ){ + if( S_ISLNK(mode) ){ + ctxErrorMsg(context, "failed to create symlink: %s", zFile); + }else if( S_ISDIR(mode) ){ + ctxErrorMsg(context, "failed to create directory: %s", zFile); + }else{ + ctxErrorMsg(context, "failed to write file: %s", zFile); + } + } } -/* Insert a new opcode and argument into an RE under construction. The -** insertion point is just prior to existing opcode iBefore. +/* +** SQL function: lsmode(MODE) +** +** Given a numberic st_mode from stat(), convert it into a human-readable +** text string in the style of "ls -l". */ -static int re_insert(ReCompiled *p, int iBefore, int op, int arg){ +static void lsModeFunc( + sqlite3_context *context, + int argc, + sqlite3_value **argv +){ int i; - if( p->nAlloc<=p->nState && re_resize(p, p->nAlloc*2) ) return 0; - for(i=p->nState; i>iBefore; i--){ - p->aOp[i] = p->aOp[i-1]; - p->aArg[i] = p->aArg[i-1]; + int iMode = sqlite3_value_int(argv[0]); + char z[16]; + (void)argc; + if( S_ISLNK(iMode) ){ + z[0] = 'l'; + }else if( S_ISREG(iMode) ){ + z[0] = '-'; + }else if( S_ISDIR(iMode) ){ + z[0] = 'd'; + }else{ + z[0] = '?'; } - p->nState++; - p->aOp[iBefore] = (char)op; - p->aArg[iBefore] = arg; - return iBefore; + for(i=0; i<3; i++){ + int m = (iMode >> ((2-i)*3)); + char *a = &z[1 + i*3]; + a[0] = (m & 0x4) ? 'r' : '-'; + a[1] = (m & 0x2) ? 'w' : '-'; + a[2] = (m & 0x1) ? 'x' : '-'; + } + z[10] = '\0'; + sqlite3_result_text(context, z, -1, SQLITE_TRANSIENT); } -/* Append a new opcode and argument to the end of the RE under construction. +#ifndef SQLITE_OMIT_VIRTUALTABLE + +/* +** Cursor type for recursively iterating through a directory structure. */ -static int re_append(ReCompiled *p, int op, int arg){ - return re_insert(p, p->nState, op, arg); +typedef struct fsdir_cursor fsdir_cursor; +typedef struct FsdirLevel FsdirLevel; + +struct FsdirLevel { + DIR *pDir; /* From opendir() */ + char *zDir; /* Name of directory (nul-terminated) */ +}; + +struct fsdir_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + + int nLvl; /* Number of entries in aLvl[] array */ + int mxLvl; /* Maximum level */ + int iLvl; /* Index of current entry */ + FsdirLevel *aLvl; /* Hierarchy of directories being traversed */ + + const char *zBase; + int nBase; + + STRUCT_STAT sStat; /* Current lstat() results */ + char *zPath; /* Path to current entry */ + sqlite3_int64 iRowid; /* Current rowid */ +}; + +typedef struct fsdir_tab fsdir_tab; +struct fsdir_tab { + sqlite3_vtab base; /* Base class - must be first */ +}; + +/* +** Construct a new fsdir virtual table object. +*/ +static int fsdirConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + fsdir_tab *pNew = 0; + int rc; + (void)pAux; + (void)argc; + (void)argv; + (void)pzErr; + rc = sqlite3_declare_vtab(db, "CREATE TABLE x" FSDIR_SCHEMA); + if( rc==SQLITE_OK ){ + pNew = (fsdir_tab*)sqlite3_malloc64( sizeof(*pNew) ); + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + sqlite3_vtab_config(db, SQLITE_VTAB_DIRECTONLY); + } + *ppVtab = (sqlite3_vtab*)pNew; + return rc; } -/* Make a copy of N opcodes starting at iStart onto the end of the RE -** under construction. +/* +** This method is the destructor for fsdir vtab objects. */ -static void re_copy(ReCompiled *p, int iStart, int N){ - if( p->nState+N>=p->nAlloc && re_resize(p, p->nAlloc*2+N) ) return; - memcpy(&p->aOp[p->nState], &p->aOp[iStart], N*sizeof(p->aOp[0])); - memcpy(&p->aArg[p->nState], &p->aArg[iStart], N*sizeof(p->aArg[0])); - p->nState += N; +static int fsdirDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; +} + +/* +** Constructor for a new fsdir_cursor object. +*/ +static int fsdirOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + fsdir_cursor *pCur; + (void)p; + pCur = sqlite3_malloc64( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->iLvl = -1; + *ppCursor = &pCur->base; + return SQLITE_OK; } -/* Return true if c is a hexadecimal digit character: [0-9a-fA-F] -** If c is a hex digit, also set *pV = (*pV)*16 + valueof(c). If -** c is not a hex digit *pV is unchanged. +/* +** Reset a cursor back to the state it was in when first returned +** by fsdirOpen(). */ -static int re_hex(int c, int *pV){ - if( c>='0' && c<='9' ){ - c -= '0'; - }else if( c>='a' && c<='f' ){ - c -= 'a' - 10; - }else if( c>='A' && c<='F' ){ - c -= 'A' - 10; - }else{ - return 0; +static void fsdirResetCursor(fsdir_cursor *pCur){ + int i; + for(i=0; i<=pCur->iLvl; i++){ + FsdirLevel *pLvl = &pCur->aLvl[i]; + if( pLvl->pDir ) closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); } - *pV = (*pV)*16 + (c & 0xff); - return 1; + sqlite3_free(pCur->zPath); + sqlite3_free(pCur->aLvl); + pCur->aLvl = 0; + pCur->zPath = 0; + pCur->zBase = 0; + pCur->nBase = 0; + pCur->nLvl = 0; + pCur->iLvl = -1; + pCur->iRowid = 1; } -/* A backslash character has been seen, read the next character and -** return its interpretation. +/* +** Destructor for an fsdir_cursor. */ -static unsigned re_esc_char(ReCompiled *p){ - static const char zEsc[] = "afnrtv\\()*.+?[$^{|}]"; - static const char zTrans[] = "\a\f\n\r\t\v"; - int i, v = 0; - char c; - if( p->sIn.i>=p->sIn.mx ) return 0; - c = p->sIn.z[p->sIn.i]; - if( c=='u' && p->sIn.i+4sIn.mx ){ - const unsigned char *zIn = p->sIn.z + p->sIn.i; - if( re_hex(zIn[1],&v) - && re_hex(zIn[2],&v) - && re_hex(zIn[3],&v) - && re_hex(zIn[4],&v) - ){ - p->sIn.i += 5; - return v; - } - } - if( c=='x' && p->sIn.i+2sIn.mx ){ - const unsigned char *zIn = p->sIn.z + p->sIn.i; - if( re_hex(zIn[1],&v) - && re_hex(zIn[2],&v) - ){ - p->sIn.i += 3; - return v; - } - } - for(i=0; zEsc[i] && zEsc[i]!=c; i++){} - if( zEsc[i] ){ - if( i<6 ) c = zTrans[i]; - p->sIn.i++; - }else{ - p->zErr = "unknown \\ escape"; - } - return c; -} - -/* Forward declaration */ -static const char *re_subcompile_string(ReCompiled*); +static int fsdirClose(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; -/* Peek at the next byte of input */ -static unsigned char rePeek(ReCompiled *p){ - return p->sIn.isIn.mx ? p->sIn.z[p->sIn.i] : 0; + fsdirResetCursor(pCur); + sqlite3_free(pCur); + return SQLITE_OK; } -/* Compile RE text into a sequence of opcodes. Continue up to the -** first unmatched ")" character, then return. If an error is found, -** return a pointer to the error message string. +/* +** Set the error message for the virtual table associated with cursor +** pCur to the results of vprintf(zFmt, ...). */ -static const char *re_subcompile_re(ReCompiled *p){ - const char *zErr; - int iStart, iEnd, iGoto; - iStart = p->nState; - zErr = re_subcompile_string(p); - if( zErr ) return zErr; - while( rePeek(p)=='|' ){ - iEnd = p->nState; - re_insert(p, iStart, RE_OP_FORK, iEnd + 2 - iStart); - iGoto = re_append(p, RE_OP_GOTO, 0); - p->sIn.i++; - zErr = re_subcompile_string(p); - if( zErr ) return zErr; - p->aArg[iGoto] = p->nState - iGoto; - } - return 0; +static void fsdirSetErrmsg(fsdir_cursor *pCur, const char *zFmt, ...){ + va_list ap; + va_start(ap, zFmt); + pCur->base.pVtab->zErrMsg = sqlite3_vmprintf(zFmt, ap); + va_end(ap); } -/* Compile an element of regular expression text (anything that can be -** an operand to the "|" operator). Return NULL on success or a pointer -** to the error message if there is a problem. + +/* +** Advance an fsdir_cursor to its next row of output. */ -static const char *re_subcompile_string(ReCompiled *p){ - int iPrev = -1; - int iStart; - unsigned c; - const char *zErr; - while( (c = p->xNextChar(&p->sIn))!=0 ){ - iStart = p->nState; - switch( c ){ - case '|': - case ')': { - p->sIn.i--; - return 0; - } - case '(': { - zErr = re_subcompile_re(p); - if( zErr ) return zErr; - if( rePeek(p)!=')' ) return "unmatched '('"; - p->sIn.i++; - break; - } - case '.': { - if( rePeek(p)=='*' ){ - re_append(p, RE_OP_ANYSTAR, 0); - p->sIn.i++; - }else{ - re_append(p, RE_OP_ANY, 0); - } - break; - } - case '*': { - if( iPrev<0 ) return "'*' without operand"; - re_insert(p, iPrev, RE_OP_GOTO, p->nState - iPrev + 1); - re_append(p, RE_OP_FORK, iPrev - p->nState + 1); - break; - } - case '+': { - if( iPrev<0 ) return "'+' without operand"; - re_append(p, RE_OP_FORK, iPrev - p->nState); - break; - } - case '?': { - if( iPrev<0 ) return "'?' without operand"; - re_insert(p, iPrev, RE_OP_FORK, p->nState - iPrev+1); - break; - } - case '$': { - re_append(p, RE_OP_MATCH, RE_EOF); - break; - } - case '^': { - re_append(p, RE_OP_ATSTART, 0); - break; - } - case '{': { - int m = 0, n = 0; - int sz, j; - if( iPrev<0 ) return "'{m,n}' without operand"; - while( (c=rePeek(p))>='0' && c<='9' ){ m = m*10 + c - '0'; p->sIn.i++; } - n = m; - if( c==',' ){ - p->sIn.i++; - n = 0; - while( (c=rePeek(p))>='0' && c<='9' ){ n = n*10 + c-'0'; p->sIn.i++; } - } - if( c!='}' ) return "unmatched '{'"; - if( n>0 && nsIn.i++; - sz = p->nState - iPrev; - if( m==0 ){ - if( n==0 ) return "both m and n are zero in '{m,n}'"; - re_insert(p, iPrev, RE_OP_FORK, sz+1); - iPrev++; - n--; - }else{ - for(j=1; j0 ){ - re_append(p, RE_OP_FORK, -sz); - } - break; - } - case '[': { - unsigned int iFirst = p->nState; - if( rePeek(p)=='^' ){ - re_append(p, RE_OP_CC_EXC, 0); - p->sIn.i++; - }else{ - re_append(p, RE_OP_CC_INC, 0); - } - while( (c = p->xNextChar(&p->sIn))!=0 ){ - if( c=='[' && rePeek(p)==':' ){ - return "POSIX character classes not supported"; - } - if( c=='\\' ) c = re_esc_char(p); - if( rePeek(p)=='-' ){ - re_append(p, RE_OP_CC_RANGE, c); - p->sIn.i++; - c = p->xNextChar(&p->sIn); - if( c=='\\' ) c = re_esc_char(p); - re_append(p, RE_OP_CC_RANGE, c); - }else{ - re_append(p, RE_OP_CC_VALUE, c); - } - if( rePeek(p)==']' ){ p->sIn.i++; break; } - } - if( c==0 ) return "unclosed '['"; - if( p->nState>iFirst ) p->aArg[iFirst] = p->nState - iFirst; - break; - } - case '\\': { - int specialOp = 0; - switch( rePeek(p) ){ - case 'b': specialOp = RE_OP_BOUNDARY; break; - case 'd': specialOp = RE_OP_DIGIT; break; - case 'D': specialOp = RE_OP_NOTDIGIT; break; - case 's': specialOp = RE_OP_SPACE; break; - case 'S': specialOp = RE_OP_NOTSPACE; break; - case 'w': specialOp = RE_OP_WORD; break; - case 'W': specialOp = RE_OP_NOTWORD; break; - } - if( specialOp ){ - p->sIn.i++; - re_append(p, specialOp, 0); - }else{ - c = re_esc_char(p); - re_append(p, RE_OP_MATCH, c); - } - break; +static int fsdirNext(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + mode_t m = pCur->sStat.st_mode; + + pCur->iRowid++; + if( S_ISDIR(m) && pCur->iLvl+3mxLvl ){ + /* Descend into this directory */ + int iNew = pCur->iLvl + 1; + FsdirLevel *pLvl; + if( iNew>=pCur->nLvl ){ + int nNew = iNew+1; + sqlite3_int64 nByte = nNew*sizeof(FsdirLevel); + FsdirLevel *aNew = (FsdirLevel*)sqlite3_realloc64(pCur->aLvl, nByte); + if( aNew==0 ) return SQLITE_NOMEM; + memset(&aNew[pCur->nLvl], 0, sizeof(FsdirLevel)*(nNew-pCur->nLvl)); + pCur->aLvl = aNew; + pCur->nLvl = nNew; + } + pCur->iLvl = iNew; + pLvl = &pCur->aLvl[iNew]; + + pLvl->zDir = pCur->zPath; + pCur->zPath = 0; + pLvl->pDir = opendir(pLvl->zDir); + if( pLvl->pDir==0 ){ + fsdirSetErrmsg(pCur, "cannot read directory: %s", pLvl->zDir); + return SQLITE_ERROR; + } + } + + while( pCur->iLvl>=0 ){ + FsdirLevel *pLvl = &pCur->aLvl[pCur->iLvl]; + struct dirent *pEntry = readdir(pLvl->pDir); + if( pEntry ){ + if( pEntry->d_name[0]=='.' ){ + if( pEntry->d_name[1]=='.' && pEntry->d_name[2]=='\0' ) continue; + if( pEntry->d_name[1]=='\0' ) continue; } - default: { - re_append(p, RE_OP_MATCH, c); - break; + sqlite3_free(pCur->zPath); + pCur->zPath = sqlite3_mprintf("%s/%s", pLvl->zDir, pEntry->d_name); + if( pCur->zPath==0 ) return SQLITE_NOMEM; + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; } + return SQLITE_OK; } - iPrev = iStart; + closedir(pLvl->pDir); + sqlite3_free(pLvl->zDir); + pLvl->pDir = 0; + pLvl->zDir = 0; + pCur->iLvl--; } - return 0; + + /* EOF */ + sqlite3_free(pCur->zPath); + pCur->zPath = 0; + return SQLITE_OK; } -/* Free and reclaim all the memory used by a previously compiled -** regular expression. Applications should invoke this routine once -** for every call to re_compile() to avoid memory leaks. +/* +** Return values of columns for the row at which the series_cursor +** is currently pointing. */ -static void re_free(ReCompiled *pRe){ - if( pRe ){ - sqlite3_free(pRe->aOp); - sqlite3_free(pRe->aArg); - sqlite3_free(pRe); +static int fsdirColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ +){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + switch( i ){ + case FSDIR_COLUMN_NAME: { + sqlite3_result_text(ctx, &pCur->zPath[pCur->nBase], -1, SQLITE_TRANSIENT); + break; + } + + case FSDIR_COLUMN_MODE: + sqlite3_result_int64(ctx, pCur->sStat.st_mode); + break; + + case FSDIR_COLUMN_MTIME: + sqlite3_result_int64(ctx, pCur->sStat.st_mtime); + break; + + case FSDIR_COLUMN_DATA: { + mode_t m = pCur->sStat.st_mode; + if( S_ISDIR(m) ){ + sqlite3_result_null(ctx); +#if !defined(_WIN32) && !defined(WIN32) + }else if( S_ISLNK(m) ){ + char aStatic[64]; + char *aBuf = aStatic; + sqlite3_int64 nBuf = 64; + int n; + + while( 1 ){ + n = readlink(pCur->zPath, aBuf, nBuf); + if( nzPath); + } + break; + } + case FSDIR_COLUMN_LEVEL: + sqlite3_result_int(ctx, pCur->iLvl+2); + break; + case FSDIR_COLUMN_PATH: + default: { + /* The FSDIR_COLUMN_PATH and FSDIR_COLUMN_DIR are input parameters. + ** always return their values as NULL */ + break; + } } + return SQLITE_OK; } /* -** Compile a textual regular expression in zIn[] into a compiled regular -** expression suitable for us by re_match() and return a pointer to the -** compiled regular expression in *ppRe. Return NULL on success or an -** error message if something goes wrong. +** Return the rowid for the current row. In this implementation, the +** first row returned is assigned rowid value 1, and each subsequent +** row a value 1 more than that of the previous. */ -static const char *re_compile(ReCompiled **ppRe, const char *zIn, int noCase){ - ReCompiled *pRe; - const char *zErr; - int i, j; +static int fsdirRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; +} - *ppRe = 0; - pRe = sqlite3_malloc( sizeof(*pRe) ); - if( pRe==0 ){ - return "out of memory"; +/* +** Return TRUE if the cursor has been moved off of the last +** row of output. +*/ +static int fsdirEof(sqlite3_vtab_cursor *cur){ + fsdir_cursor *pCur = (fsdir_cursor*)cur; + return (pCur->zPath==0); +} + +/* +** xFilter callback. +** +** idxNum bit Meaning +** 0x01 PATH=N +** 0x02 DIR=N +** 0x04 LEVELxNextChar = noCase ? re_next_char_nocase : re_next_char; - if( re_resize(pRe, 30) ){ - re_free(pRe); - return "out of memory"; + + assert( (idxNum & 0x01)!=0 && argc>0 ); + zDir = (const char*)sqlite3_value_text(argv[0]); + if( zDir==0 ){ + fsdirSetErrmsg(pCur, "table function fsdir requires a non-NULL argument"); + return SQLITE_ERROR; } - if( zIn[0]=='^' ){ - zIn++; - }else{ - re_append(pRe, RE_OP_ANYSTAR, 0); + i = 1; + if( (idxNum & 0x02)!=0 ){ + assert( argc>i ); + pCur->zBase = (const char*)sqlite3_value_text(argv[i++]); } - pRe->sIn.z = (unsigned char*)zIn; - pRe->sIn.i = 0; - pRe->sIn.mx = (int)strlen(zIn); - zErr = re_subcompile_re(pRe); - if( zErr ){ - re_free(pRe); - return zErr; + if( (idxNum & 0x0c)!=0 ){ + assert( argc>i ); + pCur->mxLvl = sqlite3_value_int(argv[i++]); + if( idxNum & 0x08 ) pCur->mxLvl++; + if( pCur->mxLvl<=0 ) pCur->mxLvl = 1000000000; + }else{ + pCur->mxLvl = 1000000000; } - if( pRe->sIn.i>=pRe->sIn.mx ){ - re_append(pRe, RE_OP_ACCEPT, 0); - *ppRe = pRe; + if( pCur->zBase ){ + pCur->nBase = (int)strlen(pCur->zBase)+1; + pCur->zPath = sqlite3_mprintf("%s/%s", pCur->zBase, zDir); }else{ - re_free(pRe); - return "unrecognized character"; + pCur->zPath = sqlite3_mprintf("%s", zDir); } - /* The following is a performance optimization. If the regex begins with - ** ".*" (if the input regex lacks an initial "^") and afterwards there are - ** one or more matching characters, enter those matching characters into - ** zInit[]. The re_match() routine can then search ahead in the input - ** string looking for the initial match without having to run the whole - ** regex engine over the string. Do not worry about trying to match - ** unicode characters beyond plane 0 - those are very rare and this is - ** just an optimization. */ - if( pRe->aOp[0]==RE_OP_ANYSTAR && !noCase ){ - for(j=0, i=1; j<(int)sizeof(pRe->zInit)-2 && pRe->aOp[i]==RE_OP_MATCH; i++){ - unsigned x = pRe->aArg[i]; - if( x<=0x7f ){ - pRe->zInit[j++] = (unsigned char)x; - }else if( x<=0x7ff ){ - pRe->zInit[j++] = (unsigned char)(0xc0 | (x>>6)); - pRe->zInit[j++] = 0x80 | (x&0x3f); - }else if( x<=0xffff ){ - pRe->zInit[j++] = (unsigned char)(0xe0 | (x>>12)); - pRe->zInit[j++] = 0x80 | ((x>>6)&0x3f); - pRe->zInit[j++] = 0x80 | (x&0x3f); - }else{ - break; + if( pCur->zPath==0 ){ + return SQLITE_NOMEM; + } + if( fileLinkStat(pCur->zPath, &pCur->sStat) ){ + fsdirSetErrmsg(pCur, "cannot stat file: %s", pCur->zPath); + return SQLITE_ERROR; + } + + return SQLITE_OK; +} + +/* +** SQLite will invoke this method one or more times while planning a query +** that uses the generate_series virtual table. This routine needs to create +** a query plan for each invocation and compute an estimated cost for that +** plan. +** +** In this implementation idxNum is used to represent the +** query plan. idxStr is unused. +** +** The query plan is represented by bits in idxNum: +** +** 0x01 The path value is supplied by argv[0] +** 0x02 dir is in argv[1] +** 0x04 maxdepth is in argv[1] or [2] +*/ +static int fsdirBestIndex( + sqlite3_vtab *tab, + sqlite3_index_info *pIdxInfo +){ + int i; /* Loop over constraints */ + int idxPath = -1; /* Index in pIdxInfo->aConstraint of PATH= */ + int idxDir = -1; /* Index in pIdxInfo->aConstraint of DIR= */ + int idxLevel = -1; /* Index in pIdxInfo->aConstraint of LEVEL< or <= */ + int idxLevelEQ = 0; /* 0x08 for LEVEL<= or LEVEL=. 0x04 for LEVEL< */ + int omitLevel = 0; /* omit the LEVEL constraint */ + int seenPath = 0; /* True if an unusable PATH= constraint is seen */ + int seenDir = 0; /* True if an unusable DIR= constraint is seen */ + const struct sqlite3_index_constraint *pConstraint; + + (void)tab; + pConstraint = pIdxInfo->aConstraint; + for(i=0; inConstraint; i++, pConstraint++){ + if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_EQ ){ + switch( pConstraint->iColumn ){ + case FSDIR_COLUMN_PATH: { + if( pConstraint->usable ){ + idxPath = i; + seenPath = 0; + }else if( idxPath<0 ){ + seenPath = 1; + } + break; + } + case FSDIR_COLUMN_DIR: { + if( pConstraint->usable ){ + idxDir = i; + seenDir = 0; + }else if( idxDir<0 ){ + seenDir = 1; + } + break; + } + case FSDIR_COLUMN_LEVEL: { + if( pConstraint->usable && idxLevel<0 ){ + idxLevel = i; + idxLevelEQ = 0x08; + omitLevel = 0; + } + break; + } + } + }else + if( pConstraint->iColumn==FSDIR_COLUMN_LEVEL + && pConstraint->usable + && idxLevel<0 + ){ + if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_LE ){ + idxLevel = i; + idxLevelEQ = 0x08; + omitLevel = 1; + }else if( pConstraint->op==SQLITE_INDEX_CONSTRAINT_LT ){ + idxLevel = i; + idxLevelEQ = 0x04; + omitLevel = 1; } + } + } + if( seenPath || seenDir ){ + /* If input parameters are unusable, disallow this plan */ + return SQLITE_CONSTRAINT; + } + + if( idxPath<0 ){ + pIdxInfo->idxNum = 0; + /* The pIdxInfo->estimatedCost should have been initialized to a huge + ** number. Leave it unchanged. */ + pIdxInfo->estimatedRows = 0x7fffffff; + }else{ + pIdxInfo->aConstraintUsage[idxPath].omit = 1; + pIdxInfo->aConstraintUsage[idxPath].argvIndex = 1; + pIdxInfo->idxNum = 0x01; + pIdxInfo->estimatedCost = 1.0e9; + i = 2; + if( idxDir>=0 ){ + pIdxInfo->aConstraintUsage[idxDir].omit = 1; + pIdxInfo->aConstraintUsage[idxDir].argvIndex = i++; + pIdxInfo->idxNum |= 0x02; + pIdxInfo->estimatedCost /= 1.0e4; + } + if( idxLevel>=0 ){ + pIdxInfo->aConstraintUsage[idxLevel].omit = omitLevel; + pIdxInfo->aConstraintUsage[idxLevel].argvIndex = i++; + pIdxInfo->idxNum |= idxLevelEQ; + pIdxInfo->estimatedCost /= 1.0e4; } - if( j>0 && pRe->zInit[j-1]==0 ) j--; - pRe->nInit = j; } - return pRe->zErr; + + return SQLITE_OK; } /* -** Implementation of the regexp() SQL function. This function implements -** the build-in REGEXP operator. The first argument to the function is the -** pattern and the second argument is the string. So, the SQL statements: -** -** A REGEXP B -** -** is implemented as regexp(B,A). +** Register the "fsdir" virtual table. */ -static void re_sql_func( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - ReCompiled *pRe; /* Compiled regular expression */ - const char *zPattern; /* The regular expression */ - const unsigned char *zStr;/* String being searched */ - const char *zErr; /* Compile error message */ - int setAux = 0; /* True to invoke sqlite3_set_auxdata() */ +static int fsdirRegister(sqlite3 *db){ + static sqlite3_module fsdirModule = { + 0, /* iVersion */ + 0, /* xCreate */ + fsdirConnect, /* xConnect */ + fsdirBestIndex, /* xBestIndex */ + fsdirDisconnect, /* xDisconnect */ + 0, /* xDestroy */ + fsdirOpen, /* xOpen - open a cursor */ + fsdirClose, /* xClose - close a cursor */ + fsdirFilter, /* xFilter - configure scan constraints */ + fsdirNext, /* xNext - advance a cursor */ + fsdirEof, /* xEof - check for end of scan */ + fsdirColumn, /* xColumn - read data */ + fsdirRowid, /* xRowid - read data */ + 0, /* xUpdate */ + 0, /* xBegin */ + 0, /* xSync */ + 0, /* xCommit */ + 0, /* xRollback */ + 0, /* xFindMethod */ + 0, /* xRename */ + 0, /* xSavepoint */ + 0, /* xRelease */ + 0, /* xRollbackTo */ + 0, /* xShadowName */ + 0 /* xIntegrity */ + }; - (void)argc; /* Unused */ - pRe = sqlite3_get_auxdata(context, 0); - if( pRe==0 ){ - zPattern = (const char*)sqlite3_value_text(argv[0]); - if( zPattern==0 ) return; - zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); - if( zErr ){ - re_free(pRe); - sqlite3_result_error(context, zErr, -1); - return; - } - if( pRe==0 ){ - sqlite3_result_error_nomem(context); - return; - } - setAux = 1; + int rc = sqlite3_create_module(db, "fsdir", &fsdirModule, 0); + return rc; +} +#else /* SQLITE_OMIT_VIRTUALTABLE */ +# define fsdirRegister(x) SQLITE_OK +#endif + +/* +** This version of realpath() works on any system. The string +** returned is held in memory allocated using sqlite3_malloc64(). +** The caller is responsible for calling sqlite3_free(). +*/ +static char *portable_realpath(const char *zPath){ +#if !defined(_WIN32) /* BEGIN unix */ + + char *zOut = 0; /* Result */ + char *z; /* Temporary buffer */ +#if defined(PATH_MAX) + char zBuf[PATH_MAX+1]; /* Space for the temporary buffer */ +#endif + + if( zPath==0 ) return 0; +#if defined(PATH_MAX) + z = realpath(zPath, zBuf); + if( z ){ + zOut = sqlite3_mprintf("%s", zBuf); } - zStr = (const unsigned char*)sqlite3_value_text(argv[1]); - if( zStr!=0 ){ - sqlite3_result_int(context, re_match(pRe, zStr, -1)); +#endif /* defined(PATH_MAX) */ + if( zOut==0 ){ + /* Try POSIX.1-2008 malloc behavior */ + z = realpath(zPath, NULL); + if( z ){ + zOut = sqlite3_mprintf("%s", z); + free(z); + } } - if( setAux ){ - sqlite3_set_auxdata(context, 0, pRe, (void(*)(void*))re_free); + return zOut; + +#else /* End UNIX, Begin WINDOWS */ + + wchar_t *zPath16; /* UTF16 translation of zPath */ + char *zOut = 0; /* Result */ + wchar_t *z = 0; /* Temporary buffer */ + + if( zPath==0 ) return 0; + + zPath16 = sqlite3_win32_utf8_to_unicode(zPath); + if( zPath16==0 ) return 0; + z = _wfullpath(NULL, zPath16, 0); + sqlite3_free(zPath16); + if( z ){ + zOut = sqlite3_win32_unicode_to_utf8(z); + free(z); } + return zOut; + +#endif /* End WINDOWS, Begin common code */ } -#if defined(SQLITE_DEBUG) /* -** This function is used for testing and debugging only. It is only available -** if the SQLITE_DEBUG compile-time option is used. +** SQL function: realpath(X) ** -** Compile a regular expression and then convert the compiled expression into -** text and return that text. +** Try to convert file or pathname X into its real, absolute pathname. +** Return NULL if unable. +** +** The file or directory X is not required to exist. The answer is formed +** by calling system realpath() on the prefix of X that does exist and +** appending the tail of X that does not (yet) exist. */ -static void re_bytecode_func( +static void realpathFunc( sqlite3_context *context, int argc, sqlite3_value **argv ){ - const char *zPattern; - const char *zErr; - ReCompiled *pRe; - sqlite3_str *pStr; - int i; - int n; - char *z; - (void)argc; + const char *zPath; /* Original input path */ + char *zCopy; /* An editable copy of zPath */ + char *zOut; /* The result */ + char cSep = 0; /* Separator turned into \000 */ + size_t len; /* Prefix length before cSep */ +#ifdef _WIN32 + const int isWin = 1; +#else + const int isWin = 0; +#endif - zPattern = (const char*)sqlite3_value_text(argv[0]); - if( zPattern==0 ) return; - zErr = re_compile(&pRe, zPattern, sqlite3_user_data(context)!=0); - if( zErr ){ - re_free(pRe); - sqlite3_result_error(context, zErr, -1); - return; - } - if( pRe==0 ){ - sqlite3_result_error_nomem(context); - return; - } - pStr = sqlite3_str_new(0); - if( pStr==0 ) goto re_bytecode_func_err; - if( pRe->nInit>0 ){ - sqlite3_str_appendf(pStr, "INIT "); - for(i=0; inInit; i++){ - sqlite3_str_appendf(pStr, "%02x", pRe->zInit[i]); + (void)argc; + zPath = (const char*)sqlite3_value_text(argv[0]); + if( zPath==0 ) return; + if( zPath[0]==0 ) zPath = "."; + zCopy = sqlite3_mprintf("%s",zPath); + len = strlen(zCopy); + while( len>1 && (zCopy[len-1]=='/' || (isWin && zCopy[len-1]=='\\')) ){ + len--; + } + zCopy[len] = 0; + while( 1 /*exit-by-break*/ ){ + zOut = portable_realpath(zCopy); + zCopy[len] = cSep; + if( zOut ){ + if( cSep ){ + zOut = sqlite3_mprintf("%z%s",zOut,&zCopy[len]); + } + break; + }else{ + size_t i = len-1; + while( i>0 ){ + if( zCopy[i]=='/' || (isWin && zCopy[i]=='\\') ) break; + i--; + } + if( i<=0 ){ + if( zCopy[0]=='/' ){ + zOut = zCopy; + zCopy = 0; + }else if( (zOut = portable_realpath("."))!=0 ){ + zOut = sqlite3_mprintf("%z/%s", zOut, zCopy); + } + break; + } + cSep = zCopy[i]; + zCopy[i] = 0; + len = i; + } + } + sqlite3_free(zCopy); + if( zOut ){ + /* Simplify any "/./" or "/../" that might have snuck into the + ** pathname due to appending of zCopy. We only have to consider + ** unix "/" separators, because the _wfilepath() system call on + ** Windows will have already done this simplification for us. */ + size_t i, j, n; + n = strlen(zOut); + for(i=j=0; i0 && zOut[j-1]!='/' ){ j--; } + if( j>0 ){ j--; } + i += 2; + continue; + } + } + zOut[j++] = zOut[i]; } - sqlite3_str_appendf(pStr, "\n"); - } - for(i=0; (unsigned)inState; i++){ - sqlite3_str_appendf(pStr, "%-8s %4d\n", - ReOpName[(unsigned char)pRe->aOp[i]], pRe->aArg[i]); - } - n = sqlite3_str_length(pStr); - z = sqlite3_str_finish(pStr); - if( n==0 ){ - sqlite3_free(z); - }else{ - sqlite3_result_text(context, z, n-1, sqlite3_free); - } + zOut[j] = 0; -re_bytecode_func_err: - re_free(pRe); + /* Return the result */ + sqlite3_result_text(context, zOut, -1, sqlite3_free); + } } -#endif /* SQLITE_DEBUG */ - -/* -** Invoke this routine to register the regexp() function with the -** SQLite database connection. -*/ #ifdef _WIN32 #endif -int sqlite3_regexp_init( +int sqlite3_fileio_init( sqlite3 *db, char **pzErrMsg, const sqlite3_api_routines *pApi ){ int rc = SQLITE_OK; SQLITE_EXTENSION_INIT2(pApi); - (void)pzErrMsg; /* Unused */ - rc = sqlite3_create_function(db, "regexp", 2, - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, - 0, re_sql_func, 0, 0); + (void)pzErrMsg; /* Unused parameter */ + rc = sqlite3_create_function(db, "readfile", 1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + readfileFunc, 0, 0); if( rc==SQLITE_OK ){ - /* The regexpi(PATTERN,STRING) function is a case-insensitive version - ** of regexp(PATTERN,STRING). */ - rc = sqlite3_create_function(db, "regexpi", 2, - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, - (void*)db, re_sql_func, 0, 0); -#if defined(SQLITE_DEBUG) - if( rc==SQLITE_OK ){ - rc = sqlite3_create_function(db, "regexp_bytecode", 1, - SQLITE_UTF8|SQLITE_INNOCUOUS|SQLITE_DETERMINISTIC, - 0, re_bytecode_func, 0, 0); - } -#endif /* SQLITE_DEBUG */ + rc = sqlite3_create_function(db, "writefile", -1, + SQLITE_UTF8|SQLITE_DIRECTONLY, 0, + writefileFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "lsmode", 1, SQLITE_UTF8, 0, + lsModeFunc, 0, 0); + } + if( rc==SQLITE_OK ){ + rc = fsdirRegister(db); + } + if( rc==SQLITE_OK ){ + rc = sqlite3_create_function(db, "realpath", 1, + SQLITE_UTF8, 0, + realpathFunc, 0, 0); } return rc; } -/************************* End ../ext/misc/regexp.c ********************/ -#ifndef SQLITE_SHELL_FIDDLE -/************************* Begin ../ext/misc/fileio.c ******************/ +/************************* End ext/misc/fileio.c ********************/ +/************************* Begin ext/misc/completion.c ******************/ /* -** 2014-06-13 -** -** The author disclaims copyright to this source code. In place of -** a legal notice, here is a blessing: -** -** May you do good and not evil. -** May you find forgiveness for yourself and forgive others. -** May you share freely, never taking more than you give. -** -****************************************************************************** -** -** This SQLite extension implements SQL functions readfile() and -** writefile(), and eponymous virtual type "fsdir". -** -** WRITEFILE(FILE, DATA [, MODE [, MTIME]]): -** -** If neither of the optional arguments is present, then this UDF -** function writes blob DATA to file FILE. If successful, the number -** of bytes written is returned. If an error occurs, NULL is returned. -** -** If the first option argument - MODE - is present, then it must -** be passed an integer value that corresponds to a POSIX mode -** value (file type + permissions, as returned in the stat.st_mode -** field by the stat() system call). Three types of files may -** be written/created: -** -** regular files: (mode & 0170000)==0100000 -** symbolic links: (mode & 0170000)==0120000 -** directories: (mode & 0170000)==0040000 -** -** For a directory, the DATA is ignored. For a symbolic link, it is -** interpreted as text and used as the target of the link. For a -** regular file, it is interpreted as a blob and written into the -** named file. Regardless of the type of file, its permissions are -** set to (mode & 0777) before returning. -** -** If the optional MTIME argument is present, then it is interpreted -** as an integer - the number of seconds since the unix epoch. The -** modification-time of the target file is set to this value before -** returning. -** -** If three or more arguments are passed to this function and an -** error is encountered, an exception is raised. +** 2017-07-10 ** -** READFILE(FILE): +** The author disclaims copyright to this source code. In place of +** a legal notice, here is a blessing: ** -** Read and return the contents of file FILE (type blob) from disk. +** May you do good and not evil. +** May you find forgiveness for yourself and forgive others. +** May you share freely, never taking more than you give. ** -** FSDIR: +************************************************************************* ** -** Used as follows: +** This file implements an eponymous virtual table that returns suggested +** completions for a partial SQL input. ** -** SELECT * FROM fsdir($path [, $dir]); +** Suggested usage: ** -** Parameter $path is an absolute or relative pathname. If the file that it -** refers to does not exist, it is an error. If the path refers to a regular -** file or symbolic link, it returns a single row. Or, if the path refers -** to a directory, it returns one row for the directory, and one row for each -** file within the hierarchy rooted at $path. +** SELECT DISTINCT candidate COLLATE nocase +** FROM completion($prefix,$wholeline) +** ORDER BY 1; ** -** Each row has the following columns: +** The two query parameters are optional. $prefix is the text of the +** current word being typed and that is to be completed. $wholeline is +** the complete input line, used for context. ** -** name: Path to file or directory (text value). -** mode: Value of stat.st_mode for directory entry (an integer). -** mtime: Value of stat.st_mtime for directory entry (an integer). -** data: For a regular file, a blob containing the file data. For a -** symlink, a text value containing the text of the link. For a -** directory, NULL. +** The raw completion() table might return the same candidate multiple +** times, for example if the same column name is used to two or more +** tables. And the candidates are returned in an arbitrary order. Hence, +** the DISTINCT and ORDER BY are recommended. ** -** If a non-NULL value is specified for the optional $dir parameter and -** $path is a relative path, then $path is interpreted relative to $dir. -** And the paths returned in the "name" column of the table are also -** relative to directory $dir. +** This virtual table operates at the speed of human typing, and so there +** is no attempt to make it fast. Even a slow implementation will be much +** faster than any human can type. ** -** Notes on building this extension for Windows: -** Unless linked statically with the SQLite library, a preprocessor -** symbol, FILEIO_WIN32_DLL, must be #define'd to create a stand-alone -** DLL form of this extension for WIN32. See its use below for details. */ /* #include "sqlite3ext.h" */ SQLITE_EXTENSION_INIT1 -#include -#include #include +#include +#include -#include -#include -#include -#if !defined(_WIN32) && !defined(WIN32) -# include -# include -# include -# include -#else -# include "windows.h" -# include -# include -/* # include "test_windirent.h" */ -# define dirent DIRENT -# ifndef chmod -# define chmod _chmod -# endif -# ifndef stat -# define stat _stat -# endif -# define mkdir(path,mode) _mkdir(path) -# define lstat(path,buf) stat(path,buf) +#ifndef SQLITE_OMIT_VIRTUALTABLE + +#ifndef IsAlnum +#define IsAlnum(X) isalnum((unsigned char)X) #endif -#include -#include -/* -** Structure of the fsdir() table-valued function +/* completion_vtab is a subclass of sqlite3_vtab which will +** serve as the underlying representation of a completion virtual table */ - /* 0 1 2 3 4 5 */ -#define FSDIR_SCHEMA "(name,mode,mtime,data,path HIDDEN,dir HIDDEN)" -#define FSDIR_COLUMN_NAME 0 /* Name of the file */ -#define FSDIR_COLUMN_MODE 1 /* Access mode */ -#define FSDIR_COLUMN_MTIME 2 /* Last modification time */ -#define FSDIR_COLUMN_DATA 3 /* File content */ -#define FSDIR_COLUMN_PATH 4 /* Path to top of search */ -#define FSDIR_COLUMN_DIR 5 /* Path is relative to this directory */ +typedef struct completion_vtab completion_vtab; +struct completion_vtab { + sqlite3_vtab base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this completion vtab */ +}; + +/* completion_cursor is a subclass of sqlite3_vtab_cursor which will +** serve as the underlying representation of a cursor that scans +** over rows of the result +*/ +typedef struct completion_cursor completion_cursor; +struct completion_cursor { + sqlite3_vtab_cursor base; /* Base class - must be first */ + sqlite3 *db; /* Database connection for this cursor */ + int nPrefix, nLine; /* Number of bytes in zPrefix and zLine */ + char *zPrefix; /* The prefix for the word we want to complete */ + char *zLine; /* The whole that we want to complete */ + const char *zCurrentRow; /* Current output row */ + int szRow; /* Length of the zCurrentRow string */ + sqlite3_stmt *pStmt; /* Current statement */ + sqlite3_int64 iRowid; /* The rowid */ + int ePhase; /* Current phase */ + int j; /* inter-phase counter */ +}; +/* Values for ePhase: +*/ +#define COMPLETION_FIRST_PHASE 1 +#define COMPLETION_KEYWORDS 1 +#define COMPLETION_PRAGMAS 2 +#define COMPLETION_FUNCTIONS 3 +#define COMPLETION_COLLATIONS 4 +#define COMPLETION_INDEXES 5 +#define COMPLETION_TRIGGERS 6 +#define COMPLETION_DATABASES 7 +#define COMPLETION_TABLES 8 /* Also VIEWs and TRIGGERs */ +#define COMPLETION_COLUMNS 9 +#define COMPLETION_MODULES 10 +#define COMPLETION_EOF 11 /* -** Set the result stored by context ctx to a blob containing the -** contents of file zName. Or, leave the result unchanged (NULL) -** if the file does not exist or is unreadable. +** The completionConnect() method is invoked to create a new +** completion_vtab that describes the completion virtual table. ** -** If the file exceeds the SQLite blob size limit, through an -** SQLITE_TOOBIG error. +** Think of this routine as the constructor for completion_vtab objects. ** -** Throw an SQLITE_IOERR if there are difficulties pulling the file -** off of disk. +** All this routine needs to do is: +** +** (1) Allocate the completion_vtab object and initialize all fields. +** +** (2) Tell SQLite (via the sqlite3_declare_vtab() interface) what the +** result set of queries against completion will look like. */ -static void readFileContents(sqlite3_context *ctx, const char *zName){ - FILE *in; - sqlite3_int64 nIn; - void *pBuf; - sqlite3 *db; - int mxBlob; +static int completionConnect( + sqlite3 *db, + void *pAux, + int argc, const char *const*argv, + sqlite3_vtab **ppVtab, + char **pzErr +){ + completion_vtab *pNew; + int rc; - in = fopen(zName, "rb"); - if( in==0 ){ - /* File does not exist or is unreadable. Leave the result set to NULL. */ - return; - } - fseek(in, 0, SEEK_END); - nIn = ftell(in); - rewind(in); - db = sqlite3_context_db_handle(ctx); - mxBlob = sqlite3_limit(db, SQLITE_LIMIT_LENGTH, -1); - if( nIn>mxBlob ){ - sqlite3_result_error_code(ctx, SQLITE_TOOBIG); - fclose(in); - return; - } - pBuf = sqlite3_malloc64( nIn ? nIn : 1 ); - if( pBuf==0 ){ - sqlite3_result_error_nomem(ctx); - fclose(in); - return; - } - if( nIn==(sqlite3_int64)fread(pBuf, 1, (size_t)nIn, in) ){ - sqlite3_result_blob64(ctx, pBuf, nIn, sqlite3_free); - }else{ - sqlite3_result_error_code(ctx, SQLITE_IOERR); - sqlite3_free(pBuf); + (void)(pAux); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + (void)(argv); /* Unused parameter */ + (void)(pzErr); /* Unused parameter */ + +/* Column numbers */ +#define COMPLETION_COLUMN_CANDIDATE 0 /* Suggested completion of the input */ +#define COMPLETION_COLUMN_PREFIX 1 /* Prefix of the word to be completed */ +#define COMPLETION_COLUMN_WHOLELINE 2 /* Entire line seen so far */ +#define COMPLETION_COLUMN_PHASE 3 /* ePhase - used for debugging only */ + + sqlite3_vtab_config(db, SQLITE_VTAB_INNOCUOUS); + rc = sqlite3_declare_vtab(db, + "CREATE TABLE x(" + " candidate TEXT," + " prefix TEXT HIDDEN," + " wholeline TEXT HIDDEN," + " phase INT HIDDEN" /* Used for debugging only */ + ")"); + if( rc==SQLITE_OK ){ + pNew = sqlite3_malloc64( sizeof(*pNew) ); + *ppVtab = (sqlite3_vtab*)pNew; + if( pNew==0 ) return SQLITE_NOMEM; + memset(pNew, 0, sizeof(*pNew)); + pNew->db = db; } - fclose(in); + return rc; } /* -** Implementation of the "readfile(X)" SQL function. The entire content -** of the file named X is read and returned as a BLOB. NULL is returned -** if the file does not exist or is unreadable. +** This method is the destructor for completion_cursor objects. */ -static void readfileFunc( - sqlite3_context *context, - int argc, - sqlite3_value **argv -){ - const char *zName; - (void)(argc); /* Unused parameter */ - zName = (const char*)sqlite3_value_text(argv[0]); - if( zName==0 ) return; - readFileContents(context, zName); +static int completionDisconnect(sqlite3_vtab *pVtab){ + sqlite3_free(pVtab); + return SQLITE_OK; } /* -** Set the error message contained in context ctx to the results of -** vprintf(zFmt, ...). +** Constructor for a new completion_cursor object. */ -static void ctxErrorMsg(sqlite3_context *ctx, const char *zFmt, ...){ - char *zMsg = 0; - va_list ap; - va_start(ap, zFmt); - zMsg = sqlite3_vmprintf(zFmt, ap); - sqlite3_result_error(ctx, zMsg, -1); - sqlite3_free(zMsg); - va_end(ap); +static int completionOpen(sqlite3_vtab *p, sqlite3_vtab_cursor **ppCursor){ + completion_cursor *pCur; + pCur = sqlite3_malloc64( sizeof(*pCur) ); + if( pCur==0 ) return SQLITE_NOMEM; + memset(pCur, 0, sizeof(*pCur)); + pCur->db = ((completion_vtab*)p)->db; + *ppCursor = &pCur->base; + return SQLITE_OK; } -#if defined(_WIN32) /* -** This function is designed to convert a Win32 FILETIME structure into the -** number of seconds since the Unix Epoch (1970-01-01 00:00:00 UTC). +** Reset the completion_cursor. */ -static sqlite3_uint64 fileTimeToUnixTime( - LPFILETIME pFileTime -){ - SYSTEMTIME epochSystemTime; - ULARGE_INTEGER epochIntervals; - FILETIME epochFileTime; - ULARGE_INTEGER fileIntervals; - - memset(&epochSystemTime, 0, sizeof(SYSTEMTIME)); - epochSystemTime.wYear = 1970; - epochSystemTime.wMonth = 1; - epochSystemTime.wDay = 1; - SystemTimeToFileTime(&epochSystemTime, &epochFileTime); - epochIntervals.LowPart = epochFileTime.dwLowDateTime; - epochIntervals.HighPart = epochFileTime.dwHighDateTime; - - fileIntervals.LowPart = pFileTime->dwLowDateTime; - fileIntervals.HighPart = pFileTime->dwHighDateTime; +static void completionCursorReset(completion_cursor *pCur){ + sqlite3_free(pCur->zPrefix); pCur->zPrefix = 0; pCur->nPrefix = 0; + sqlite3_free(pCur->zLine); pCur->zLine = 0; pCur->nLine = 0; + sqlite3_finalize(pCur->pStmt); pCur->pStmt = 0; + pCur->j = 0; +} - return (fileIntervals.QuadPart - epochIntervals.QuadPart) / 10000000; +/* +** Destructor for a completion_cursor. +*/ +static int completionClose(sqlite3_vtab_cursor *cur){ + completionCursorReset((completion_cursor*)cur); + sqlite3_free(cur); + return SQLITE_OK; } +/* +** Advance a completion_cursor to its next row of output. +** +** The ->ePhase, ->j, and ->pStmt fields of the completion_cursor object +** record the current state of the scan. This routine sets ->zCurrentRow +** to the current row of output and then returns. If no more rows remain, +** then ->ePhase is set to COMPLETION_EOF which will signal the virtual +** table that has reached the end of its scan. +** +** The current implementation just lists potential identifiers and +** keywords and filters them by zPrefix. Future enhancements should +** take zLine into account to try to restrict the set of identifiers and +** keywords based on what would be legal at the current point of input. +*/ +static int completionNext(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + int eNextPhase = 0; /* Next phase to try if current phase reaches end */ + int iCol = -1; /* If >=0, step pCur->pStmt and use the i-th column */ + int rc; + pCur->iRowid++; + while( pCur->ePhase!=COMPLETION_EOF ){ + switch( pCur->ePhase ){ + case COMPLETION_KEYWORDS: { + if( pCur->j >= sqlite3_keyword_count() ){ + pCur->zCurrentRow = 0; + pCur->ePhase = COMPLETION_DATABASES; + }else{ + sqlite3_keyword_name(pCur->j++, &pCur->zCurrentRow, &pCur->szRow); + } + iCol = -1; + break; + } + case COMPLETION_DATABASES: { + if( pCur->pStmt==0 ){ + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, + &pCur->pStmt, 0); + } + iCol = 1; + eNextPhase = COMPLETION_TABLES; + break; + } + case COMPLETION_TABLES: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + sqlite3_str* pStr = sqlite3_str_new(pCur->db); + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + sqlite3_str_appendf(pStr, + "%s" + "SELECT name FROM \"%w\".sqlite_schema", + zSep, zDb + ); + zSep = " UNION "; + } + rc = sqlite3_finalize(pS2); + zSql = sqlite3_str_finish(pStr); + if( zSql==0 ) return SQLITE_NOMEM; + if( rc==SQLITE_OK ){ + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + } + sqlite3_free(zSql); + if( rc ) return rc; + } + iCol = 0; + eNextPhase = COMPLETION_COLUMNS; + break; + } + case COMPLETION_COLUMNS: { + if( pCur->pStmt==0 ){ + sqlite3_stmt *pS2; + sqlite3_str *pStr = sqlite3_str_new(pCur->db); + char *zSql = 0; + const char *zSep = ""; + sqlite3_prepare_v2(pCur->db, "PRAGMA database_list", -1, &pS2, 0); + while( sqlite3_step(pS2)==SQLITE_ROW ){ + const char *zDb = (const char*)sqlite3_column_text(pS2, 1); + sqlite3_str_appendf(pStr, + "%s" + "SELECT pti.name FROM \"%w\".sqlite_schema AS sm" + " JOIN pragma_table_xinfo(sm.name,%Q) AS pti" + " WHERE sm.type='table'", + zSep, zDb, zDb + ); + zSep = " UNION "; + } + rc = sqlite3_finalize(pS2); + zSql = sqlite3_str_finish(pStr); + if( zSql==0 ) return SQLITE_NOMEM; + if( rc==SQLITE_OK ){ + sqlite3_prepare_v2(pCur->db, zSql, -1, &pCur->pStmt, 0); + } + sqlite3_free(zSql); + if( rc ) return rc; + } + iCol = 0; + eNextPhase = COMPLETION_EOF; + break; + } + } + if( iCol<0 ){ + /* This case is when the phase presets zCurrentRow */ + if( pCur->zCurrentRow==0 ) continue; + }else{ + if( sqlite3_step(pCur->pStmt)==SQLITE_ROW ){ + /* Extract the next row of content */ + pCur->zCurrentRow = (const char*)sqlite3_column_text(pCur->pStmt, iCol); + pCur->szRow = sqlite3_column_bytes(pCur->pStmt, iCol); + }else{ + /* When all rows are finished, advance to the next phase */ + rc = sqlite3_finalize(pCur->pStmt); + pCur->pStmt = 0; + pCur->ePhase = eNextPhase; + if( rc ) return rc; + continue; + } + } + if( pCur->nPrefix==0 ) break; + if( pCur->nPrefix<=pCur->szRow + && sqlite3_strnicmp(pCur->zPrefix, pCur->zCurrentRow, pCur->nPrefix)==0 + ){ + break; + } + } -#if defined(FILEIO_WIN32_DLL) && (defined(_WIN32) || defined(WIN32)) -# /* To allow a standalone DLL, use this next replacement function: */ -# undef sqlite3_win32_utf8_to_unicode -# define sqlite3_win32_utf8_to_unicode utf8_to_utf16 -# -LPWSTR utf8_to_utf16(const char *z){ - int nAllot = MultiByteToWideChar(CP_UTF8, 0, z, -1, NULL, 0); - LPWSTR rv = sqlite3_malloc(nAllot * sizeof(WCHAR)); - if( rv!=0 && 0 < MultiByteToWideChar(CP_UTF8, 0, z, -1, rv, nAllot) ) - return rv; - sqlite3_free(rv); - return 0; + return SQLITE_OK; } -#endif /* -** This function attempts to normalize the time values found in the stat() -** buffer to UTC. This is necessary on Win32, where the runtime library -** appears to return these values as local times. +** Return values of columns for the row at which the completion_cursor +** is currently pointing. */ -static void statTimesToUtc( - const char *zPath, - struct stat *pStatBuf +static int completionColumn( + sqlite3_vtab_cursor *cur, /* The cursor */ + sqlite3_context *ctx, /* First argument to sqlite3_result_...() */ + int i /* Which column to return */ ){ - HANDLE hFindFile; - WIN32_FIND_DATAW fd; - LPWSTR zUnicodeName; - extern LPWSTR sqlite3_win32_utf8_to_unicode(const char*); - zUnicodeName = sqlite3_win32_utf8_to_unicode(zPath); - if( zUnicodeName ){ - memset(&fd, 0, sizeof(WIN32_FIND_DATAW)); - hFindFile = FindFirstFileW(zUnicodeName, &fd); - if( hFindFile!=NULL ){ - pStatBuf->st_ctime = (time_t)fileTimeToUnixTime(&fd.ftCreationTime); - pStatBuf->st_atime = (time_t)fileTimeToUnixTime(&fd.ftLastAccessTime); - pStatBuf->st_mtime = (time_t)fileTimeToUnixTime(&fd.ftLastWriteTime); - FindClose(hFindFile); + completion_cursor *pCur = (completion_cursor*)cur; + switch( i ){ + case COMPLETION_COLUMN_CANDIDATE: { + sqlite3_result_text(ctx, pCur->zCurrentRow, pCur->szRow,SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PREFIX: { + sqlite3_result_text(ctx, pCur->zPrefix, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_WHOLELINE: { + sqlite3_result_text(ctx, pCur->zLine, -1, SQLITE_TRANSIENT); + break; + } + case COMPLETION_COLUMN_PHASE: { + sqlite3_result_int(ctx, pCur->ePhase); + break; } - sqlite3_free(zUnicodeName); } + return SQLITE_OK; } -#endif /* -** This function is used in place of stat(). On Windows, special handling -** is required in order for the included time to be returned as UTC. On all -** other systems, this function simply calls stat(). +** Return the rowid for the current row. In this implementation, the +** rowid is the same as the output value. */ -static int fileStat( - const char *zPath, - struct stat *pStatBuf -){ -#if defined(_WIN32) - int rc = stat(zPath, pStatBuf); - if( rc==0 ) statTimesToUtc(zPath, pStatBuf); - return rc; -#else - return stat(zPath, pStatBuf); -#endif +static int completionRowid(sqlite3_vtab_cursor *cur, sqlite_int64 *pRowid){ + completion_cursor *pCur = (completion_cursor*)cur; + *pRowid = pCur->iRowid; + return SQLITE_OK; } /* -** This function is used in place of lstat(). On Windows, special handling -** is required in order for the included time to be returned as UTC. On all -** other systems, this function simply calls lstat(). +** Return TRUE if the cursor has been moved off of the last +** row of output. */ -static int fileLinkStat( - const char *zPath, - struct stat *pStatBuf -){ -#if defined(_WIN32) - int rc = lstat(zPath, pStatBuf); - if( rc==0 ) statTimesToUtc(zPath, pStatBuf); - return rc; -#else - return lstat(zPath, pStatBuf); -#endif +static int completionEof(sqlite3_vtab_cursor *cur){ + completion_cursor *pCur = (completion_cursor*)cur; + return pCur->ePhase >= COMPLETION_EOF; } /* -** Argument zFile is the name of a file that will be created and/or written -** by SQL function writefile(). This function ensures that the directory -** zFile will be written to exists, creating it if required. The permissions -** for any path components created by this function are set in accordance -** with the current umask. -** -** If an OOM condition is encountered, SQLITE_NOMEM is returned. Otherwise, -** SQLITE_OK is returned if the directory is successfully created, or -** SQLITE_ERROR otherwise. +** This method is called to "rewind" the completion_cursor object back +** to the first row of output. This method is always called at least +** once prior to any call to completionColumn() or completionRowid() or +** completionEof(). */ -static int makeDirectory( - const char *zFile +static int completionFilter( + sqlite3_vtab_cursor *pVtabCursor, + int idxNum, const char *idxStr, + int argc, sqlite3_value **argv ){ - char *zCopy = sqlite3_mprintf("%s", zFile); - int rc = SQLITE_OK; - - if( zCopy==0 ){ - rc = SQLITE_NOMEM; - }else{ - int nCopy = (int)strlen(zCopy); - int i = 1; - - while( rc==SQLITE_OK ){ - struct stat sStat; - int rc2; + completion_cursor *pCur = (completion_cursor *)pVtabCursor; + int iArg = 0; + (void)(idxStr); /* Unused parameter */ + (void)(argc); /* Unused parameter */ + completionCursorReset(pCur); + if( idxNum & 1 ){ + pCur->nPrefix = sqlite3_value_bytes(argv[iArg]); + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + pCur->nPrefix = (int)strlen(pCur->zPrefix); + } + iArg = 1; + } + if( idxNum & 2 ){ + pCur->nLine = sqlite3_value_bytes(argv[iArg]); + if( pCur->nLine>0 ){ + pCur->zLine = sqlite3_mprintf("%s", sqlite3_value_text(argv[iArg])); + if( pCur->zLine==0 ) return SQLITE_NOMEM; + pCur->nLine = (int)strlen(pCur->zLine); + } + } + if( pCur->zLine!=0 && pCur->zPrefix==0 ){ + int i = pCur->nLine; + while( i>0 && (IsAlnum(pCur->zLine[i-1]) || pCur->zLine[i-1]=='_') ){ + i--; + } + pCur->nPrefix = pCur->nLine - i; + if( pCur->nPrefix>0 ){ + pCur->zPrefix = sqlite3_mprintf("%.*s", pCur->nPrefix, pCur->zLine + i); + if( pCur->zPrefix==0 ) return SQLITE_NOMEM; + pCur->nPrefix = (int)strlen(pCur->zPrefix); + } + } + pCur->iRowid = 0; + pCur->ePhase = COMPLETION_FIRST_PHASE; + return completionNext(pVtabCursor); +} - for(; zCopy[i]!='/' && i