spawnEditor was moved to ZBackupBase

master
Vladimir Stackov 2014-12-30 19:27:31 +03:00
parent 479b5b3c71
commit bee7efc9e5
5 changed files with 227 additions and 198 deletions

214
config.cc
View File

@ -1,34 +1,22 @@
// Copyright (c) 2012-2014 Konstantin Isakov <ikm@zbackup.org> and ZBackup contributors, see CONTRIBUTORS
// Part of ZBackup. Licensed under GNU GPLv2 or later + OpenSSL, see LICENSE
#include <sys/wait.h>
#include <cerrno>
#include "zbackup_base.hh"
#include "zbackup.pb.h"
#include "sptr.hh"
#include "config.hh"
#include "file.hh"
#include "debug.hh"
#if defined(_PATH_VI)
# define EDITOR _PATH_VI
#else
# define EDITOR "/bin/vi"
#endif
#ifndef _PATH_BSHELL
# define _PATH_BSHELL "/bin/sh"
#endif
#define MAX_TEMPSTR 131072
#include "config.hh"
ZConfig::ZConfig( string const & storageDir, string const & password ):
ZBackupBase( storageDir, password, true )
{
}
bool ZConfig::parse( const string & str, google::protobuf::Message * mutable_message )
{
return google::protobuf::TextFormat::ParseFromString( str, mutable_message );
}
string ZConfig::toString( google::protobuf::Message const & message )
{
std::string str;
@ -42,188 +30,34 @@ void ZConfig::show()
printf( "%s", toString( extendedStorageInfo.config() ).c_str() );
}
bool ZConfig::edit()
bool ZConfig::validate( const string & configData, const string & newConfigData )
{
ConfigInfo newConfig;
return parse( newConfigData, &newConfig );
}
bool ZConfig::editInteractively()
{
string configData( toString( extendedStorageInfo.config() ) );
string newConfigData( configData );
/* Turn off signals. */
(void) signal( SIGHUP, SIG_IGN );
(void) signal( SIGINT, SIG_IGN );
(void) signal( SIGQUIT, SIG_IGN );
sptr< TemporaryFile > tmpFile = tmpMgr.makeTemporaryFile();
const char * tmpFileName = tmpFile->getFileName().c_str();
sptr< File> tmpConfig = new File( tmpFileName, File::WriteOnly );
tmpConfig->writeRecords( configData.c_str(), configData.size(), 1 );
again:
tmpConfig->rewind();
if ( tmpConfig->error() )
{
verbosePrintf( "Error while writing config to %s\n", tmpFileName );
fatal:
tmpFile.reset();
exit( EXIT_FAILURE );
}
char * editorEnv;
string editor;
if ( ( ( editorEnv = getenv( "VISUAL" ) ) == NULL || *editorEnv == '\0' ) &&
( ( editorEnv = getenv( "EDITOR" ) ) == NULL || *editorEnv == '\0' ) )
editor.assign( EDITOR );
else
editor.assign( editorEnv );
/* we still have the file open. editors will generally rewrite the
* original file rather than renaming/unlinking it and starting a
* new one; even backup files are supposed to be made by copying
* rather than by renaming. if some editor does not support this,
* then don't use it. the security problems are more severe if we
* close and reopen the file around the edit.
*/
string shellArgs;
shellArgs += editor;
shellArgs += " ";
shellArgs += tmpFileName;
pid_t pid, xpid;
switch ( pid = fork() )
{
case -1:
perror( "fork" );
goto fatal;
case 0:
/* child */
execlp( _PATH_BSHELL, _PATH_BSHELL, "-c", shellArgs.c_str(), (char *) 0 );
perror( editor.c_str() );
exit( EXIT_FAILURE );
/*NOTREACHED*/
default:
/* parent */
break;
}
/* parent */
int waiter;
for ( ; ; )
{
xpid = waitpid( pid, &waiter, 0 );
if ( xpid == -1 )
{
if ( errno != EINTR )
verbosePrintf( "waitpid() failed waiting for PID %ld from \"%s\": %s\n",
(long) pid, editor.c_str(), strerror( errno ) );
}
else
if (xpid != pid)
{
verbosePrintf( "wrong PID (%ld != %ld) from \"%s\"\n",
(long) xpid, (long) pid, editor.c_str() );
goto fatal;
}
else
if ( WIFEXITED( waiter ) && WEXITSTATUS( waiter ) )
{
verbosePrintf( "\"%s\" exited with status %d\n",
editor.c_str(), WEXITSTATUS( waiter ) );
goto fatal;
}
else
if ( WIFSIGNALED( waiter ) )
{
verbosePrintf( "\"%s\" killed; signal %d (%score dumped)\n",
editor.c_str(), WTERMSIG( waiter ),
WCOREDUMP( waiter ) ? "" : "no ");
goto fatal;
}
else
break;
}
(void) signal( SIGHUP, SIG_DFL );
(void) signal( SIGINT, SIG_DFL );
(void) signal( SIGQUIT, SIG_DFL );
tmpConfig->close();
tmpConfig = new File( tmpFileName, File::ReadOnly );
string newConfigData;
newConfigData.resize( tmpConfig->size() );
tmpConfig->read( &newConfigData[ 0 ], newConfigData.size() );
if ( !spawnEditor( newConfigData, &validate ) )
return false;
ConfigInfo newConfig;
bool isChanged = false;
int ret = 0;
if ( !parse( newConfigData, &newConfig ) )
ret = -1;
else
return false;
if ( toString( extendedStorageInfo.config() ) == toString( newConfig ) )
{
if ( toString( extendedStorageInfo.config() ) == toString( newConfig ) )
{
verbosePrintf( "No changes made to config\n" );
goto end;
}
else
verbosePrintf( "Updating configuration...\n" );
verbosePrintf( "No changes made to config\n" );
return false;
}
switch ( ret )
{
case 0:
goto success;
case -1:
for ( ; ; )
{
printf( "Do you want to retry the same edit? " );
fflush( stdout );
verbosePrintf( "Updating configuration...\n" );
string input;
input.resize( MAX_TEMPSTR );
if ( fgets( &input[ 0 ], input.size(), stdin ) == 0L )
continue;
switch ( input[ 0 ] )
{
case 'y':
case 'Y':
goto again;
case 'n':
case 'N':
goto abandon;
default:
fprintf( stderr, "Enter Y or N\n" );
}
}
/*NOTREACHED*/
case -2:
abandon:
verbosePrintf( "Configuration is kept intact\n" );
goto end;
success:
extendedStorageInfo.mutable_config()->CopyFrom( newConfig );
verbosePrintf(
extendedStorageInfo.mutable_config()->CopyFrom( newConfig );
verbosePrintf(
"Configuration successfully updated!\n"
"Updated configuration:\n\n%s", toString( extendedStorageInfo.config() ).c_str() );
isChanged = true;
goto end;
default:
verbosePrintf( "panic: bad switch()\n" );
goto fatal;
}
end:
tmpConfig.reset();
tmpFile.reset();
return isChanged;
}
bool ZConfig::parse( const string & str, google::protobuf::Message * mutable_message )
{
return google::protobuf::TextFormat::ParseFromString( str, mutable_message );
return true;
}

View File

@ -17,13 +17,17 @@ public:
// Print current configuration to screen
void show();
// Edit current configuration in default editor
// Edit current configuration
// returns true if configuration is changed
bool edit();
bool editInteractively();
// Validator for user-supplied configuration
static bool validate( const string &, const string & );
static bool parse( const string & str, google::protobuf::Message * mutable_message );
private:
string toString( google::protobuf::Message const & message );
bool parse( const string & str, google::protobuf::Message * mutable_message );
};
#endif

View File

@ -695,7 +695,7 @@ int main( int argc, char *argv[] )
if ( args.size() > 2 && strcmp( args[ fieldAct ], "edit" ) == 0 )
{
if ( zc.edit() )
if ( zc.editInteractively() )
zc.saveExtendedStorageInfo();
}
else

View File

@ -1,12 +1,25 @@
// Copyright (c) 2012-2014 Konstantin Isakov <ikm@zbackup.org> and ZBackup contributors, see CONTRIBUTORS
// Part of ZBackup. Licensed under GNU GPLv2 or later + OpenSSL, see LICENSE
#include <sys/wait.h>
#include <cerrno>
#include "zbackup_base.hh"
#include "storage_info_file.hh"
#include "compression.hh"
#include "debug.hh"
#if defined(_PATH_VI)
# define EDITOR _PATH_VI
#else
# define EDITOR "/bin/vi"
#endif
#ifndef _PATH_BSHELL
# define _PATH_BSHELL "/bin/sh"
#endif
using std::string;
Paths::Paths( string const & storageDir ): storageDir( storageDir )
@ -75,11 +88,13 @@ StorageInfo ZBackupBase::loadStorageInfo()
return storageInfo;
}
ExtendedStorageInfo ZBackupBase::loadExtendedStorageInfo( EncryptionKey const & encryptionkey )
ExtendedStorageInfo ZBackupBase::loadExtendedStorageInfo(
EncryptionKey const & encryptionkey )
{
ExtendedStorageInfo extendedStorageInfo;
ExtendedStorageInfoFile::load( getExtendedStorageInfoPath(), encryptionkey, extendedStorageInfo );
ExtendedStorageInfoFile::load( getExtendedStorageInfoPath(), encryptionkey,
extendedStorageInfo );
return extendedStorageInfo;
}
@ -180,5 +195,178 @@ void ZBackupBase::setPassword( string const & password )
void ZBackupBase::saveExtendedStorageInfo()
{
ExtendedStorageInfoFile::save( getExtendedStorageInfoPath(), encryptionkey, extendedStorageInfo );
ExtendedStorageInfoFile::save( getExtendedStorageInfoPath(), encryptionkey,
extendedStorageInfo );
}
bool ZBackupBase::spawnEditor( string & data, bool( * validator )
( string const &, string const & ) )
{
// Based on ideas found in cronie-1.4.4-12.el6.i686
// Initially it was just a copy-paste from edit_cmd (crontab.c)
/* Turn off signals. */
(void) signal( SIGHUP, SIG_IGN );
(void) signal( SIGINT, SIG_IGN );
(void) signal( SIGQUIT, SIG_IGN );
sptr< TemporaryFile > tmpFile = tmpMgr.makeTemporaryFile();
const char * tmpFileName = tmpFile->getFileName().c_str();
sptr< File> tmpDataFile = new File( tmpFileName, File::WriteOnly );
tmpDataFile->writeRecords( data.c_str(), data.size(), 1 );
again:
tmpDataFile->rewind();
if ( tmpDataFile->error() )
{
verbosePrintf( "Error while writing data to %s\n", tmpFileName );
fatal:
tmpFile.reset();
exit( EXIT_FAILURE );
}
char * editorEnv;
string editor;
if ( ( ( editorEnv = getenv( "VISUAL" ) ) == NULL || *editorEnv == '\0' ) &&
( ( editorEnv = getenv( "EDITOR" ) ) == NULL || *editorEnv == '\0' ) )
editor.assign( EDITOR );
else
editor.assign( editorEnv );
/* we still have the file open. editors will generally rewrite the
* original file rather than renaming/unlinking it and starting a
* new one; even backup files are supposed to be made by copying
* rather than by renaming. if some editor does not support this,
* then don't use it. the security problems are more severe if we
* close and reopen the file around the edit.
*/
string shellArgs;
shellArgs += editor;
shellArgs += " ";
shellArgs += tmpFileName;
pid_t pid, xpid;
switch ( pid = fork() )
{
case -1:
perror( "fork" );
goto fatal;
case 0:
/* child */
dPrintf( "Spawning editor: %s %s %s %s\n", _PATH_BSHELL, _PATH_BSHELL,
"-c", shellArgs.c_str() );
execlp( _PATH_BSHELL, _PATH_BSHELL, "-c", shellArgs.c_str(), (char *) 0 );
perror( editor.c_str() );
exit( EXIT_FAILURE );
/*NOTREACHED*/
default:
/* parent */
break;
}
/* parent */
int waiter;
for ( ; ; )
{
xpid = waitpid( pid, &waiter, 0 );
if ( xpid == -1 )
{
if ( errno != EINTR )
verbosePrintf( "waitpid() failed waiting for PID %ld from \"%s\": %s\n",
(long) pid, editor.c_str(), strerror( errno ) );
}
else
if (xpid != pid)
{
verbosePrintf( "wrong PID (%ld != %ld) from \"%s\"\n",
(long) xpid, (long) pid, editor.c_str() );
goto fatal;
}
else
if ( WIFEXITED( waiter ) && WEXITSTATUS( waiter ) )
{
verbosePrintf( "\"%s\" exited with status %d\n",
editor.c_str(), WEXITSTATUS( waiter ) );
goto fatal;
}
else
if ( WIFSIGNALED( waiter ) )
{
verbosePrintf( "\"%s\" killed; signal %d (%score dumped)\n",
editor.c_str(), WTERMSIG( waiter ),
WCOREDUMP( waiter ) ? "" : "no ");
goto fatal;
}
else
break;
}
(void) signal( SIGHUP, SIG_DFL );
(void) signal( SIGINT, SIG_DFL );
(void) signal( SIGQUIT, SIG_DFL );
tmpDataFile->close();
tmpDataFile = new File( tmpFileName, File::ReadOnly );
string newData;
newData.resize( tmpDataFile->size() );
tmpDataFile->read( &newData[ 0 ], newData.size() );
bool isChanged = false;
bool valid = validator( data, newData );
switch ( valid )
{
case true:
goto success;
case false:
for ( ; ; )
{
fprintf( stderr, "Supplied data is not valid\n" );
fflush( stderr );
printf( "Do you want to retry the same edit? " );
fflush( stdout );
string input;
input.resize( 131072 ); // Should I choose another magic value?
if ( fgets( &input[ 0 ], input.size(), stdin ) == 0L )
continue;
switch ( input[ 0 ] )
{
case 'y':
case 'Y':
goto again;
case 'n':
case 'N':
goto abandon;
default:
fprintf( stderr, "Enter Y or N\n" );
}
}
/*NOTREACHED*/
case -2:
abandon:
verbosePrintf( "Data is kept intact\n" );
goto end;
success:
isChanged = true;
data.assign( newData );
goto end;
default:
verbosePrintf( "panic: bad switch()\n" );
goto fatal;
}
end:
tmpDataFile.reset();
tmpFile.reset();
return isChanged;
}

View File

@ -58,6 +58,10 @@ public:
void setPassword( std::string const & password );
// returns true if data is changed
bool spawnEditor( std::string & data, bool( * validator )
( string const &, string const & ) );
StorageInfo storageInfo;
EncryptionKey encryptionkey;
ExtendedStorageInfo extendedStorageInfo;
@ -69,5 +73,4 @@ private:
ExtendedStorageInfo loadExtendedStorageInfo( EncryptionKey const & );
};
#endif