2019-10-25 19:51:40 -04:00
# include <map>
# include "file.h"
# include "logger.h"
# include "config.h"
# ifndef WIN32
# include <stdio.h>
# endif
using namespace std ;
2019-12-07 16:27:20 -05:00
# ifndef WIN32
# define EXPERIMENTAL_FS
# endif
2019-10-25 19:51:40 -04:00
# ifdef EXPERIMENTAL_FS
# include <experimental/filesystem>
2019-12-07 16:27:20 -05:00
# include <iostream>
namespace fs = std : : experimental : : filesystem ;
2019-10-25 19:51:40 -04:00
# else
# include <filesystem>
namespace fs = std : : filesystem ;
# endif
using namespace log_helper ;
std : : map < std : : string , std : : string > move_back ;
std : : map < std : : string , std : : string > backup_mapping ;
bool rename_or_move ( const fs : : path & source , const fs : : path & target , std : : string & error ) {
try {
fs : : rename ( source , target ) ;
return true ;
} catch ( const fs : : filesystem_error & ex ) {
# ifndef WIN32
if ( ex . code ( ) = = errc : : cross_device_link ) {
/* try with move command */
char buffer [ 2048 ] ;
auto length = snprintf ( buffer , 2048 , R " (%s " % s " " % s " ) " , " mv " , source . c_str ( ) , target . c_str ( ) ) ; /* build the move command */
if ( length < 1 | | length > = 2049 ) {
error = " failed to prepare move command! " ;
return false ;
}
auto code = system ( buffer ) ;
if ( code ! = 0 ) {
error = " move command resulted in " + to_string ( code ) ;
return false ;
}
return true ;
}
# endif
error = " rename returned error code " + to_string ( ex . code ( ) . value ( ) ) + " ( " + ex . code ( ) . message ( ) + " ) " ;
return false ;
}
}
bool file : : move ( std : : string & error , const std : : string & source , const std : : string & target ) {
auto source_path = fs : : u8path ( source ) ;
if ( ! fs : : exists ( source_path ) ) {
error = " source file does not exists " ;
return false ;
}
source_path = fs : : absolute ( source_path ) ;
if ( target . empty ( ) | | target = = " /dev/null " ) {
/* delete the file or directory */
logger : : debug ( " Deleting file " + argument_t < fs : : path : : value_type > : : value , source_path . c_str ( ) ) ;
if ( config : : backup ) {
auto backup_target = file : : register_backup ( source_path . string ( ) ) ;
if ( backup_target . empty ( ) ) {
drop_backup ( backup_target ) ;
error = " failed to register backup " ;
return false ;
}
if ( ! rename_or_move ( source_path , fs : : u8path ( backup_target ) , error ) ) {
drop_backup ( backup_target ) ;
error = " failed to move file to backup target ( " + error + " ) " ;
return false ;
}
} else {
if ( fs : : is_directory ( source_path ) ) {
try {
if ( ! fs : : remove_all ( source_path ) )
throw fs : : filesystem_error ( " invalid result " , error_code ( ) ) ;
} catch ( const fs : : filesystem_error & ex ) {
error = " failed to delete directory ( " + string ( ex . what ( ) ) + " ) " ;
return false ;
}
} else {
try {
if ( ! fs : : remove ( source_path ) )
throw fs : : filesystem_error ( " invalid result " , error_code ( ) ) ;
} catch ( const fs : : filesystem_error & ex ) {
error = " failed to delete file ( " + string ( ex . what ( ) ) + " ) " ;
return false ;
}
}
}
return true ;
}
auto target_path = fs : : u8path ( target ) ;
target_path = fs : : absolute ( target_path ) ;
if ( ! fs : : is_directory ( target_path . parent_path ( ) ) ) {
if ( ! fs : : exists ( target_path . parent_path ( ) ) ) {
try {
fs : : create_directories ( target_path . parent_path ( ) ) ;
} catch ( const fs : : filesystem_error & ex ) {
2019-10-27 16:50:25 -04:00
error = " failed to create target directories ( " + std : : string { ex . what ( ) } + " ) " ;
2019-10-25 19:51:40 -04:00
return false ;
}
} else {
error = " target isn't a directory! " ;
return false ;
}
}
logger : : debug ( " Moving file " + argument_t < fs : : path : : value_type > : : value + " to " + argument_t < fs : : path : : value_type > : : value , source_path . c_str ( ) , target_path . c_str ( ) ) ;
if ( fs : : exists ( target_path ) ) {
logger : : debug ( " Target file already exists. Deleting it " ) ;
if ( config : : backup ) {
auto path = file : : register_backup ( target_path . string ( ) ) ;
if ( path . empty ( ) ) {
drop_backup ( path ) ;
error = " failed to create backup path " ;
return false ;
}
if ( ! rename_or_move ( target_path , fs : : u8path ( path ) , error ) ) {
drop_backup ( path ) ;
error = " failed to delete old target file ( " + error + " ) " ;
return false ;
}
} else {
try {
if ( fs : : is_directory ( target_path ) )
fs : : remove_all ( target_path ) ;
else
fs : : remove ( target_path ) ;
} catch ( const fs : : filesystem_error & ex ) {
error = " failed to delete old target file: " + string ( ex . what ( ) ) ;
return false ;
}
}
}
move_back [ target_path . string ( ) ] = source_path . string ( ) ;
if ( ! rename_or_move ( source_path , target_path , error ) ) {
error = " failed to move file ( " + error + " ) " ;
return false ;
}
return true ;
}
string random_string ( size_t length ) {
static const char alphanum [ ] =
" 0123456789 "
" ABCDEFGHIJKLMNOPQRSTUVWXYZ "
" abcdefghijklmnopqrstuvwxyz " ;
string result ;
result . resize ( length , ' \0 ' ) ;
for ( char & c : result )
c = alphanum [ rand ( ) % ( sizeof ( alphanum ) - 1 ) ] ;
return result ;
}
std : : string file : : register_backup ( const std : : string & source ) {
{
auto path = fs : : u8path ( config : : backup_directory ) ;
if ( ! fs : : exists ( path ) ) {
try {
fs : : create_directories ( path ) ;
} catch ( const fs : : filesystem_error & ex ) {
logger : : error ( " Failed to create backup directory \" " + argument_t < fs : : path : : value_type > : : value + " \" : %s " , path . c_str ( ) , ex . what ( ) ) ;
return " " ;
}
}
}
std : : string key ;
while ( backup_mapping . count ( ( key = random_string ( 20 ) ) ) > 0 ) ;
backup_mapping [ key ] = source ;
return fs : : absolute ( fs : : u8path ( config : : backup_directory ) / key ) . string ( ) ;
}
void file : : drop_backup ( const std : : string & source ) {
for ( const auto & entry : backup_mapping ) {
if ( entry . second = = source ) {
backup_mapping . erase ( entry . first ) ;
return ;
}
}
}
void file : : rollback ( ) {
if ( ! config : : backup )
return ;
logger : : info ( " Rollbacking %d moved files " , move_back . size ( ) ) ;
2019-10-27 16:50:25 -04:00
for ( const auto & [ backup , original ] : move_back ) {
logger : : debug ( " Attempting to moveback %s to %s " , backup . c_str ( ) , original . c_str ( ) ) ;
2019-10-25 19:51:40 -04:00
try {
2019-10-27 16:50:25 -04:00
fs : : rename ( fs : : u8path ( backup ) , fs : : u8path ( original ) ) ;
2019-10-25 19:51:40 -04:00
} catch ( const fs : : filesystem_error & ex ) {
2019-10-27 16:50:25 -04:00
logger : : warn ( " Failed to moveback file from %s to %s (%s) " , backup . c_str ( ) , original . c_str ( ) , ex . what ( ) ) ;
2019-10-25 19:51:40 -04:00
continue ;
}
logger : : debug ( " => success " ) ;
}
logger : : info ( " Rollbacking %d deleted files " , backup_mapping . size ( ) ) ;
2019-10-27 16:50:25 -04:00
for ( const auto & [ backup , original ] : backup_mapping ) {
logger : : debug ( " Attempting to restore %s to %s " , backup . c_str ( ) , original . c_str ( ) ) ;
2019-10-25 19:51:40 -04:00
try {
2019-10-27 16:50:25 -04:00
auto source = fs : : absolute ( fs : : u8path ( config : : backup_directory ) / backup ) ;
2019-10-25 19:51:40 -04:00
if ( ! fs : : exists ( source ) ) {
2019-10-27 16:50:25 -04:00
logger : : warn ( " Failed to restore file %s (Source file missing) " , original . c_str ( ) ) ;
2019-10-25 19:51:40 -04:00
continue ;
}
2019-10-27 16:50:25 -04:00
auto target = fs : : u8path ( original ) ;
2019-10-25 19:51:40 -04:00
fs : : rename ( source , target ) ;
} catch ( const fs : : filesystem_error & ex ) {
2019-10-27 16:50:25 -04:00
logger : : warn ( " Failed to restore file %s (%s) " , original . c_str ( ) , ex . what ( ) ) ;
2019-10-25 19:51:40 -04:00
continue ;
}
logger : : debug ( " => success " ) ;
}
logger : : info ( " Rollback done " ) ;
}
2019-10-27 16:50:25 -04:00
void file : : commit ( ) {
if ( ! config : : backup )
return ;
try {
if ( ! fs : : remove_all ( config : : backup_directory ) )
throw fs : : filesystem_error ( " invalid result " , error_code ( ) ) ;
} catch ( const fs : : filesystem_error & ex ) {
logger : : warn ( " Failed to cleanup backup directory ( " + string { ex . what ( ) } + " ) " ) ;
}
backup_mapping . clear ( ) ;
}
2019-10-25 19:51:40 -04:00
# ifdef WIN32
bool file : : file_locked ( const std : : string & file ) {
auto file_handle = CreateFile (
( LPCSTR ) file . c_str ( ) , /* file name*/
( DWORD ) GENERIC_WRITE ,
( DWORD ) 0 , /* we dont want to share */
( LPSECURITY_ATTRIBUTES ) nullptr ,
2019-10-27 16:50:25 -04:00
( DWORD ) OPEN_EXISTING , /* file should be available */
2019-10-25 19:51:40 -04:00
FILE_ATTRIBUTE_NORMAL ,
nullptr
) ;
if ( file_handle = = INVALID_HANDLE_VALUE ) {
if ( GetLastError ( ) = = ERROR_SHARING_VIOLATION )
return true ; /* file is beeing used */
wchar_t buf [ 256 ] ;
2019-10-27 16:50:25 -04:00
FormatMessageW ( FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS , nullptr , GetLastError ( ) , MAKELANGID ( LANG_NEUTRAL , SUBLANG_DEFAULT ) , buf , ( sizeof ( buf ) / sizeof ( wchar_t ) ) , nullptr ) ;
buf [ 255 ] = L ' \0 ' ; /* even if FormatMessageW fails we've a 0 terminator */
auto r = wcschr ( buf , L ' \r ' ) ;
if ( r ) * r = L ' \0 ' ;
2019-10-25 19:51:40 -04:00
logger : : info ( " Failed to open file! (%S) (%d) " , buf , GetLastError ( ) ) ;
return true ;
}
CloseHandle ( file_handle ) ;
return false ;
}
# else
bool file : : file_locked ( const std : : string & file ) {
auto handle = fopen ( file . c_str ( ) , " a+ " ) ;
if ( handle ) {
fclose ( handle ) ;
return false ;
}
return true ;
}
2020-12-02 12:08:49 -05:00
# endif
# ifdef WIN32
bool CanAccessFolder ( LPCTSTR folderName , DWORD genericAccessRights )
{
bool bRet = false ;
DWORD length = 0 ;
if ( ! : : GetFileSecurity ( folderName , OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION , nullptr , 0 , & length ) & & ERROR_INSUFFICIENT_BUFFER = = : : GetLastError ( ) ) {
auto security = static_cast < PSECURITY_DESCRIPTOR > ( : : malloc ( length ) ) ;
if ( security & & : : GetFileSecurity ( folderName , OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION , security , length , & length ) ) {
HANDLE hToken = NULL ;
if ( : : OpenProcessToken ( : : GetCurrentProcess ( ) , TOKEN_IMPERSONATE | TOKEN_QUERY |
TOKEN_DUPLICATE | STANDARD_RIGHTS_READ , & hToken ) ) {
HANDLE hImpersonatedToken = NULL ;
if ( : : DuplicateToken ( hToken , SecurityImpersonation , & hImpersonatedToken ) ) {
GENERIC_MAPPING mapping = { 0xFFFFFFFF } ;
PRIVILEGE_SET privileges = { 0 } ;
DWORD grantedAccess = 0 , privilegesLength = sizeof ( privileges ) ;
BOOL result = FALSE ;
mapping . GenericRead = FILE_GENERIC_READ ;
mapping . GenericWrite = FILE_GENERIC_WRITE ;
mapping . GenericExecute = FILE_GENERIC_EXECUTE ;
mapping . GenericAll = FILE_ALL_ACCESS ;
: : MapGenericMask ( & genericAccessRights , & mapping ) ;
if ( : : AccessCheck ( security , hImpersonatedToken , genericAccessRights ,
& mapping , & privileges , & privilegesLength , & grantedAccess , & result ) ) {
bRet = ( result = = TRUE ) ;
}
: : CloseHandle ( hImpersonatedToken ) ;
}
: : CloseHandle ( hToken ) ;
}
: : free ( security ) ;
}
}
return bRet ;
}
# endif
bool file : : directory_writeable ( const std : : string & path ) {
# ifdef WIN32
return CanAccessFolder ( path . c_str ( ) , GENERIC_WRITE ) ;
# else
/* TODO: Check for file permissions? Is this method even needed? */
return false ;
# endif
}