mirror of
https://github.com/uroni/urbackup_backend.git
synced 2025-10-26 11:36:50 +00:00
624 lines
16 KiB
C++
624 lines
16 KiB
C++
/*************************************************************************
|
|
* UrBackup - Client/Server backup system
|
|
* Copyright (C) 2011-2016 Martin Raiber
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
**************************************************************************/
|
|
|
|
#include "file_metadata.h"
|
|
#include "../Interface/Server.h"
|
|
#include "../Interface/File.h"
|
|
#include "../urbackupcommon/os_functions.h"
|
|
#include "../stringtools.h"
|
|
#include "../common/data.h"
|
|
#include "../fileservplugin/chunk_settings.h"
|
|
#include "../urbackupcommon/fileclient/FileClientChunked.h"
|
|
#include <assert.h>
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include "chunk_hasher.h"
|
|
|
|
namespace
|
|
{
|
|
const char ID_GRANT_ACCESS=0;
|
|
const char ID_DENY_ACCESS=1;
|
|
|
|
const unsigned int METADATA_MAGIC=0xA4F04E41;
|
|
|
|
|
|
|
|
bool write_metadata(IFile* out, INotEnoughSpaceCallback *cb, const FileMetadata& metadata, int64& written)
|
|
{
|
|
CWData data;
|
|
data.addUInt(METADATA_MAGIC);
|
|
data.addChar(0);
|
|
metadata.serialize(data);
|
|
written=0;
|
|
|
|
_u32 metadata_size = static_cast<_u32>(data.getDataSize());
|
|
|
|
metadata_size = little_endian(metadata_size);
|
|
|
|
if(!writeRepeatFreeSpace(out, reinterpret_cast<char*>(&metadata_size), sizeof(metadata_size), cb))
|
|
{
|
|
Server->Log("Error writing file metadata to file \""+out->getFilename()+"\" -1", LL_ERROR);
|
|
return false;
|
|
}
|
|
written+=sizeof(metadata_size);
|
|
|
|
if(!writeRepeatFreeSpace(out, data.getDataPtr(), data.getDataSize(), cb))
|
|
{
|
|
Server->Log("Error writing file metadata to file \""+out->getFilename()+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
written+=data.getDataSize();
|
|
|
|
return true;
|
|
}
|
|
|
|
_u32 get_metadata_size(IFile* in)
|
|
{
|
|
_u32 metadata_size_and_magic[2];
|
|
if(in->Read(reinterpret_cast<char*>(&metadata_size_and_magic), sizeof(metadata_size_and_magic))!=sizeof(metadata_size_and_magic) ||
|
|
metadata_size_and_magic[0]==0)
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+in->getFilename()+"\" -2", LL_DEBUG);
|
|
return 0;
|
|
}
|
|
|
|
if(little_endian(metadata_size_and_magic[1])!=METADATA_MAGIC)
|
|
{
|
|
Server->Log("Metadata magic wrong in file \""+in->getFilename(), LL_DEBUG);
|
|
return 0;
|
|
}
|
|
|
|
return little_endian(metadata_size_and_magic[0]);
|
|
}
|
|
|
|
bool read_metadata_values(IFile* in, FileMetadata& metadata)
|
|
{
|
|
_u32 metadata_size_and_magic[2];
|
|
if(in->Read(reinterpret_cast<char*>(&metadata_size_and_magic), sizeof(metadata_size_and_magic))!=sizeof(metadata_size_and_magic) ||
|
|
metadata_size_and_magic[0]==0)
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+in->getFilename()+"\" -2", LL_DEBUG);
|
|
return false;
|
|
}
|
|
|
|
if(little_endian(metadata_size_and_magic[1])!=METADATA_MAGIC)
|
|
{
|
|
Server->Log("Metadata magic wrong in file \""+in->getFilename(), LL_DEBUG);
|
|
return false;
|
|
}
|
|
|
|
metadata_size_and_magic[0] = little_endian(metadata_size_and_magic[0]);
|
|
|
|
std::vector<char> buffer;
|
|
buffer.resize(metadata_size_and_magic[0]);
|
|
|
|
if(in->Read(&buffer[0], metadata_size_and_magic[0]-sizeof(unsigned int))!=metadata_size_and_magic[0]-sizeof(unsigned int))
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+in->getFilename()+"\" -3", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
CRData data(&buffer[0], buffer.size());
|
|
|
|
bool ok=true;
|
|
char version = 0;
|
|
ok &= data.getChar(&version);
|
|
ok &= metadata.read(data);
|
|
|
|
if(version!=0)
|
|
{
|
|
Server->Log("Unknown metadata version at \""+in->getFilename()+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
if(!ok)
|
|
{
|
|
Server->Log("Malformed metadata at \""+in->getFilename()+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
|
|
bool write_file_metadata(IFile* out, INotEnoughSpaceCallback *cb, const FileMetadata& metadata, bool overwrite_existing, int64& truncate_to_bytes)
|
|
{
|
|
int64 hashfilesize;
|
|
int64 hashdata_size;
|
|
bool needs_truncate=false;
|
|
truncate_to_bytes=-1;
|
|
|
|
if(out->Size()==0)
|
|
{
|
|
hashfilesize=little_endian((int64)-1);
|
|
out->Seek(0);
|
|
|
|
if(!writeRepeatFreeSpace(out, reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize), cb))
|
|
{
|
|
Server->Log("Error writing file metadata to file \""+out->getFilename()+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
hashdata_size = get_hashdata_size(hashfilesize);
|
|
}
|
|
else
|
|
{
|
|
if(!out->Seek(0))
|
|
{
|
|
Server->Log("Error seeking in metadata file \""+out->getFilename()+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
if(out->Read(reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize))!=sizeof(hashfilesize))
|
|
{
|
|
Server->Log("Error reading hashfilesize from metadata file \""+out->getFilename()+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
hashfilesize=little_endian(hashfilesize);
|
|
|
|
hashdata_size = get_hashdata_size(hashfilesize);
|
|
|
|
int64 size_should=hashdata_size;
|
|
|
|
if(out->Size()!=size_should)
|
|
{
|
|
if(!overwrite_existing)
|
|
{
|
|
Server->Log("File \""+out->getFilename()+"\" has wrong size. Should="+convert(size_should)+" is="+convert(out->Size())+". Error writing metadata to file. -1", LL_WARNING);
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
if(!out->Seek(hashdata_size))
|
|
{
|
|
Server->Log("Cannot seek to "+convert(hashdata_size)+"in \""+out->getFilename()+"\". Error writing metadata to file.", LL_WARNING);
|
|
return false;
|
|
}
|
|
_u32 metadata_size = get_metadata_size(out);
|
|
|
|
size_should+=metadata_size+sizeof(_u32);
|
|
|
|
if(out->Size()>size_should)
|
|
{
|
|
if(!out->Seek(size_should))
|
|
{
|
|
Server->Log("Cannot seek to "+convert(size_should)+" (2) in \""+out->getFilename()+"\". Error writing metadata to file.", LL_WARNING);
|
|
return false;
|
|
}
|
|
|
|
int64 os_metadata_size;
|
|
if(out->Read(reinterpret_cast<char*>(&os_metadata_size), sizeof(os_metadata_size))!=sizeof(os_metadata_size))
|
|
{
|
|
Server->Log("Error reading os metadata size. Error writing metadata to file.", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
size_should+=little_endian(os_metadata_size);
|
|
|
|
if(out->Size()!=size_should)
|
|
{
|
|
Server->Log("File \""+out->getFilename()+"\" has wrong size. Should="+convert(size_should)+" is="+convert(out->Size())+". Error writing metadata to file. -3", LL_WARNING);
|
|
return false;
|
|
}
|
|
|
|
needs_truncate=true;
|
|
}
|
|
else if(out->Size()!=size_should)
|
|
{
|
|
Server->Log("File \""+out->getFilename()+"\" has wrong size. Should="+convert(size_should)+" is="+convert(out->Size())+". Error writing metadata to file. -2", LL_WARNING);
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!out->Seek(hashdata_size))
|
|
{
|
|
Server->Log("Cannot seek to "+convert(hashdata_size)+"in \""+out->getFilename()+"\". Error writing metadata to file. -2", LL_WARNING);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
|
|
int64 written;
|
|
bool ret=write_metadata(out, cb, metadata, written);
|
|
|
|
if(needs_truncate)
|
|
{
|
|
truncate_to_bytes = hashdata_size+written;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool write_file_metadata(const std::string& out_fn, INotEnoughSpaceCallback *cb, const FileMetadata& metadata, bool overwrite_existing)
|
|
{
|
|
std::unique_ptr<IFile> out(Server->openFile(os_file_prefix(out_fn), MODE_RW_CREATE));
|
|
|
|
if(!out.get())
|
|
{
|
|
Server->Log("Error writing file metadata to file \""+out_fn+"\"", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
int64 truncate_to_bytes;
|
|
bool ret = write_file_metadata(out.get(), cb, metadata, overwrite_existing, truncate_to_bytes);
|
|
|
|
if(ret && truncate_to_bytes>0)
|
|
{
|
|
out.reset();
|
|
return os_file_truncate(out_fn, truncate_to_bytes);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
bool is_metadata_only(IFile* hash_file)
|
|
{
|
|
int64 hashfilesize;
|
|
|
|
hash_file->Seek(0);
|
|
if(hash_file->Read(reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize))!=sizeof(hashfilesize))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return hashfilesize==-1;
|
|
}
|
|
|
|
bool read_metadata(const std::string& in_fn, FileMetadata& metadata)
|
|
{
|
|
std::unique_ptr<IFile> in(Server->openFile(os_file_prefix(in_fn)));
|
|
|
|
if(!in.get())
|
|
{
|
|
Server->Log("Error reading file metadata from file \""+in_fn+"\"", LL_DEBUG);
|
|
return false;
|
|
}
|
|
|
|
return read_metadata(in.get(), metadata);
|
|
}
|
|
|
|
bool read_metadata(IFile* in, FileMetadata& metadata)
|
|
{
|
|
int64 hashfilesize;
|
|
|
|
in->Seek(0);
|
|
if(in->Read(reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize))!=sizeof(hashfilesize))
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+in->getFilename()+"\"", LL_DEBUG);
|
|
return false;
|
|
}
|
|
|
|
hashfilesize=little_endian(hashfilesize);
|
|
|
|
if(hashfilesize!=-1)
|
|
{
|
|
in->Seek(get_hashdata_size(hashfilesize));
|
|
}
|
|
|
|
return read_metadata_values(in, metadata);
|
|
}
|
|
|
|
std::string escape_metadata_fn( const std::string& fn )
|
|
{
|
|
if(fn.find(metadata_dir_fn)==0)
|
|
{
|
|
std::string num_str = fn.substr(sizeof(metadata_dir_fn)/sizeof(metadata_dir_fn[0])-1);
|
|
if(num_str.empty())
|
|
{
|
|
return fn+"0";
|
|
}
|
|
|
|
size_t num_num = std::count_if(num_str.begin(), num_str.end(), str_isnumber);
|
|
if(num_num!=num_str.size())
|
|
{
|
|
return fn;
|
|
}
|
|
|
|
return std::string(metadata_dir_fn)+convert(watoi64(num_str)+1);
|
|
}
|
|
else
|
|
{
|
|
return fn;
|
|
}
|
|
}
|
|
|
|
std::string unescape_metadata_fn( const std::string& fn )
|
|
{
|
|
if(fn.find(metadata_dir_fn)==0)
|
|
{
|
|
std::string num_str = fn.substr(sizeof(metadata_dir_fn)/sizeof(metadata_dir_fn[0])-1);
|
|
if(num_str.empty())
|
|
{
|
|
return fn;
|
|
}
|
|
|
|
size_t num_num = std::count_if(num_str.begin(), num_str.end(), str_isnumber);
|
|
if(num_num!=num_str.size())
|
|
{
|
|
return fn;
|
|
}
|
|
|
|
int64 c = watoi64(num_str)-1;
|
|
|
|
if(c==0)
|
|
{
|
|
return metadata_dir_fn;
|
|
}
|
|
else
|
|
{
|
|
return std::string(metadata_dir_fn)+convert(c);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
return fn;
|
|
}
|
|
}
|
|
|
|
int64 os_metadata_offset( IFile* meta_file )
|
|
{
|
|
int64 hashfilesize;
|
|
|
|
meta_file->Seek(0);
|
|
if(meta_file->Read(reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize))!=sizeof(hashfilesize))
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+meta_file->getFilename()+"\"", LL_DEBUG);
|
|
return -1;
|
|
}
|
|
|
|
hashfilesize=little_endian(hashfilesize);
|
|
|
|
int64 meta_offset = 0;
|
|
|
|
if(hashfilesize!=-1)
|
|
{
|
|
meta_offset = get_hashdata_size(hashfilesize);
|
|
meta_file->Seek(meta_offset);
|
|
}
|
|
else
|
|
{
|
|
meta_offset=sizeof(int64);
|
|
}
|
|
|
|
if(!meta_file->Seek(meta_offset))
|
|
{
|
|
Server->Log("Error seeking to metadata in \""+meta_file->getFilename()+"\"", LL_DEBUG);
|
|
return -1;
|
|
}
|
|
|
|
_u32 metadata_size_and_magic[2];
|
|
if(meta_file->Read(reinterpret_cast<char*>(&metadata_size_and_magic), sizeof(metadata_size_and_magic))!=sizeof(metadata_size_and_magic) ||
|
|
metadata_size_and_magic[0]==0)
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+meta_file->getFilename()+"\" -2", LL_DEBUG);
|
|
return -1;
|
|
}
|
|
|
|
if(little_endian(metadata_size_and_magic[1])!=METADATA_MAGIC)
|
|
{
|
|
Server->Log("Metadata magic wrong in file \""+meta_file->getFilename(), LL_DEBUG);
|
|
return -1;
|
|
}
|
|
|
|
return meta_offset + sizeof(_u32) + little_endian(metadata_size_and_magic[0]);
|
|
}
|
|
|
|
bool copy_os_metadata( const std::string& in_fn, const std::string& out_fn, INotEnoughSpaceCallback *cb)
|
|
{
|
|
std::unique_ptr<IFile> in_f(Server->openFile(os_file_prefix(in_fn), MODE_READ));
|
|
std::unique_ptr<IFile> out_f(Server->openFile(os_file_prefix(out_fn), MODE_RW));
|
|
|
|
if(in_f.get()==NULL)
|
|
{
|
|
Server->Log("Error opening metadata file \""+in_fn+"\" (input)", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
if(out_f.get()==NULL)
|
|
{
|
|
Server->Log("Error opening metadata file \""+out_fn+"\" (output)", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
int64 offset = os_metadata_offset(in_f.get());
|
|
|
|
if(offset==-1)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if(!in_f->Seek(offset))
|
|
{
|
|
Server->Log("Error seeking to os metadata in \""+in_fn+"\" (input)", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
if(!out_f->Seek(out_f->Size()))
|
|
{
|
|
Server->Log("Error seeking to os metadata in \""+out_fn+"\" (output)", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
std::vector<char> buffer;
|
|
buffer.resize(32768);
|
|
|
|
_u32 read;
|
|
do
|
|
{
|
|
read = in_f->Read(buffer.data(), static_cast<_u32>(buffer.size()));
|
|
|
|
if(!writeRepeatFreeSpace(out_f.get(), buffer.data(), read, cb))
|
|
{
|
|
Server->Log("Error while writing os metadata to \""+out_fn+"\" (output)", LL_ERROR);
|
|
return false;
|
|
}
|
|
|
|
} while (read>0);
|
|
|
|
return true;
|
|
}
|
|
|
|
int64 read_hashdata_size( IFile* meta_file )
|
|
{
|
|
int64 hashfilesize;
|
|
|
|
meta_file->Seek(0);
|
|
if(meta_file->Read(reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize))!=sizeof(hashfilesize))
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \""+meta_file->getFilename()+"\"", LL_DEBUG);
|
|
return -1;
|
|
}
|
|
|
|
return little_endian(hashfilesize);
|
|
}
|
|
|
|
int64 get_hashdata_size(IFile * meta_file)
|
|
{
|
|
int64 hashfilesize;
|
|
|
|
meta_file->Seek(0);
|
|
if (meta_file->Read(reinterpret_cast<char*>(&hashfilesize), sizeof(hashfilesize)) != sizeof(hashfilesize))
|
|
{
|
|
Server->Log("Error reading file metadata hashfilesize from \"" + meta_file->getFilename() + "\"", LL_DEBUG);
|
|
return 0;
|
|
}
|
|
|
|
hashfilesize = little_endian(hashfilesize);
|
|
|
|
if (hashfilesize>=0)
|
|
{
|
|
return get_hashdata_size(hashfilesize);
|
|
}
|
|
else
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
void FileMetadata::serialize( CWData& data ) const
|
|
{
|
|
data.addString(shahash);
|
|
data.addString(orig_path);
|
|
data.addString(file_permissions);
|
|
data.addVarInt(last_modified);
|
|
data.addVarInt(created);
|
|
data.addVarInt(accessed);
|
|
data.addVarInt(rsize);
|
|
}
|
|
|
|
bool FileMetadata::read( CRData& data )
|
|
{
|
|
bool ok=true;
|
|
ok &= data.getStr(&shahash);
|
|
ok &= data.getStr(&orig_path);
|
|
ok &= data.getStr(&file_permissions);
|
|
ok &= data.getVarInt(&last_modified);
|
|
ok &= data.getVarInt(&created);
|
|
ok &= data.getVarInt(&accessed);
|
|
ok &= data.getVarInt(&rsize);
|
|
|
|
if(ok)
|
|
{
|
|
exist=true;
|
|
}
|
|
|
|
return ok;
|
|
}
|
|
|
|
bool FileMetadata::read( str_map& extra_params )
|
|
{
|
|
str_map::iterator it_orig_path = extra_params.find("orig_path");
|
|
if(it_orig_path!=extra_params.end())
|
|
{
|
|
orig_path = (it_orig_path->second);
|
|
has_orig_path=true;
|
|
}
|
|
|
|
str_map::iterator it_shahash = extra_params.find("sha512");
|
|
if(it_shahash!=extra_params.end())
|
|
{
|
|
shahash = base64_decode_dash(it_shahash->second);
|
|
}
|
|
|
|
it_shahash = extra_params.find("thash");
|
|
if (it_shahash != extra_params.end())
|
|
{
|
|
shahash = base64_decode_dash(it_shahash->second);
|
|
}
|
|
|
|
if(has_orig_path || !shahash.empty())
|
|
{
|
|
exist=true;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FileMetadata::hasPermission(int64 id, bool& denied) const
|
|
{
|
|
return hasPermission(file_permissions, id, denied);
|
|
}
|
|
|
|
bool FileMetadata::hasPermission( const std::string& permissions, int64 id, bool& denied )
|
|
{
|
|
CRData perm(permissions.data(),
|
|
permissions.size());
|
|
|
|
char action;
|
|
while(perm.getChar(&action))
|
|
{
|
|
int64 pid;
|
|
if(!perm.getVarInt(&pid))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
switch(action)
|
|
{
|
|
case ID_GRANT_ACCESS:
|
|
if(pid==0 || pid==id)
|
|
{
|
|
return true;
|
|
}
|
|
break;
|
|
case ID_DENY_ACCESS:
|
|
if(pid==0 || pid==id)
|
|
{
|
|
denied=true;
|
|
return false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FileMetadata::set_shahash( const std::string& the_shahash )
|
|
{
|
|
shahash=the_shahash;
|
|
}
|
|
|
|
void FileMetadata::set_orig_path( const std::string& the_orig_path )
|
|
{
|
|
orig_path = the_orig_path;
|
|
}
|