mirror of https://github.com/vitalif/zbackup
Added concept of config editor
parent
cb8a98a651
commit
f86de5662a
241
config.cc
241
config.cc
|
@ -1,9 +1,32 @@
|
|||
// 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 <signal.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include <errno.h>
|
||||
#include <utime.h>
|
||||
|
||||
#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
|
||||
|
||||
ZConfig::ZConfig( string const & storageDir, string const & password ):
|
||||
ZBackupBase( storageDir, password, true )
|
||||
{
|
||||
|
@ -22,6 +45,224 @@ void ZConfig::print()
|
|||
printf( "%s", toString( extendedStorageInfo.config() ).c_str() );
|
||||
}
|
||||
|
||||
void ZConfig::edit()
|
||||
{
|
||||
// TODO: Rewrite that copy-paste from cronie on C++
|
||||
char q[MAX_TEMPSTR], *editor;
|
||||
struct stat statbuf;
|
||||
struct utimbuf utimebuf;
|
||||
int waiter, ret;
|
||||
pid_t pid, xpid;
|
||||
FILE *legacyTmpNewConfig;
|
||||
|
||||
sptr< TemporaryFile > tmpFile = tmpMgr.makeTemporaryFile();
|
||||
const char * tmpFileName = tmpFile->getFileName().c_str();
|
||||
|
||||
if ( !( legacyTmpNewConfig = fopen( tmpFileName, "w" ) ) )
|
||||
{
|
||||
if ( errno != ENOENT )
|
||||
{
|
||||
perror( tmpFileName );
|
||||
exit( EXIT_FAILURE );
|
||||
}
|
||||
}
|
||||
|
||||
fputs( toString( extendedStorageInfo.config() ).c_str(), legacyTmpNewConfig );
|
||||
|
||||
if ( fflush( legacyTmpNewConfig ) < EXIT_SUCCESS )
|
||||
{
|
||||
perror( tmpFileName );
|
||||
exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
/* Set it to 1970 */
|
||||
utimebuf.actime = 0;
|
||||
utimebuf.modtime = 0;
|
||||
utime( tmpFileName, &utimebuf );
|
||||
|
||||
again:
|
||||
rewind( legacyTmpNewConfig );
|
||||
if ( ferror( legacyTmpNewConfig ) )
|
||||
{
|
||||
fprintf( stderr, "Error while writing new config to %s\n", tmpFileName );
|
||||
|
||||
fatal:
|
||||
unlink( tmpFileName );
|
||||
exit( EXIT_FAILURE );
|
||||
}
|
||||
|
||||
if ( ( ( editor = getenv( "VISUAL" )) == NULL || *editor == '\0' ) &&
|
||||
( ( editor = getenv("EDITOR")) == NULL || *editor == '\0') )
|
||||
{
|
||||
editor = EDITOR;
|
||||
}
|
||||
|
||||
/* 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;
|
||||
|
||||
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 );
|
||||
exit( EXIT_FAILURE );
|
||||
/*NOTREACHED*/
|
||||
default:
|
||||
/* parent */
|
||||
break;
|
||||
}
|
||||
|
||||
/* parent */
|
||||
for ( ; ; )
|
||||
{
|
||||
xpid = waitpid( pid, &waiter, 0 );
|
||||
if ( xpid == -1 )
|
||||
{
|
||||
if ( errno != EINTR )
|
||||
fprintf(stderr,
|
||||
"waitpid() failed waiting for PID %ld from \"%s\": %s\n",
|
||||
(long) pid, editor, strerror( errno ) );
|
||||
}
|
||||
else
|
||||
if (xpid != pid)
|
||||
{
|
||||
fprintf( stderr, "wrong PID (%ld != %ld) from \"%s\"\n",
|
||||
(long) xpid, (long) pid, editor);
|
||||
goto fatal;
|
||||
}
|
||||
else
|
||||
if ( WIFEXITED( waiter ) && WEXITSTATUS( waiter ) )
|
||||
{
|
||||
fprintf(stderr, "\"%s\" exited with status %d\n",
|
||||
editor, WEXITSTATUS( waiter ) );
|
||||
goto fatal;
|
||||
}
|
||||
else
|
||||
if ( WIFSIGNALED( waiter ) )
|
||||
{
|
||||
fprintf(stderr,
|
||||
"\"%s\" killed; signal %d (%score dumped)\n",
|
||||
editor, WTERMSIG( waiter ),
|
||||
WCOREDUMP( waiter ) ? "" : "no ");
|
||||
goto fatal;
|
||||
}
|
||||
else
|
||||
break;
|
||||
}
|
||||
(void) signal( SIGHUP, SIG_DFL );
|
||||
(void) signal( SIGINT, SIG_DFL );
|
||||
(void) signal( SIGQUIT, SIG_DFL );
|
||||
|
||||
/* lstat doesn't make any harm, because
|
||||
* the file is stat'ed only when config is touched
|
||||
*/
|
||||
if ( lstat( tmpFileName, &statbuf ) < 0 )
|
||||
{
|
||||
perror( "lstat" );
|
||||
goto fatal;
|
||||
}
|
||||
|
||||
if ( !S_ISREG( statbuf.st_mode ) )
|
||||
{
|
||||
fprintf( stderr, "Illegal config\n" );
|
||||
goto remove;
|
||||
}
|
||||
|
||||
if ( statbuf.st_mtime == 0 )
|
||||
{
|
||||
fprintf( stderr, "No changes made to config\n" );
|
||||
goto remove;
|
||||
}
|
||||
|
||||
fprintf( stderr, "Installing new config\n" );
|
||||
fclose( legacyTmpNewConfig );
|
||||
|
||||
if ( !( legacyTmpNewConfig = fopen( tmpFileName, "r+" ) ) )
|
||||
{
|
||||
perror("cannot read new config");
|
||||
goto remove;
|
||||
}
|
||||
if ( legacyTmpNewConfig == 0L )
|
||||
{
|
||||
perror("fopen");
|
||||
goto fatal;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
File tmpNewConfig( tmpFileName, File::Update );
|
||||
ConfigInfo newConfig;
|
||||
string newConfigData;
|
||||
newConfigData.resize( tmpNewConfig.size() );
|
||||
tmpNewConfig.read( &newConfigData[ 0 ], newConfigData.size() );
|
||||
parse( newConfigData, &newConfig );
|
||||
tmpNewConfig.close();
|
||||
ret = 0;
|
||||
}
|
||||
catch ( std::exception & e )
|
||||
{
|
||||
ret = -1;
|
||||
}
|
||||
|
||||
switch ( ret )
|
||||
{
|
||||
case 0:
|
||||
break;
|
||||
case -1:
|
||||
for ( ; ; )
|
||||
{
|
||||
printf( "Do you want to retry the same edit? " );
|
||||
fflush( stdout );
|
||||
q[ 0 ] = '\0';
|
||||
if ( fgets( q, sizeof q, stdin ) == 0L )
|
||||
continue;
|
||||
|
||||
switch ( q[ 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:
|
||||
fprintf( stderr, "edits left in %s\n", tmpFileName );
|
||||
goto done;
|
||||
default:
|
||||
fprintf( stderr, "panic: bad switch() in replace_cmd()\n" );
|
||||
goto fatal;
|
||||
}
|
||||
|
||||
remove:
|
||||
tmpFile.reset();
|
||||
done:
|
||||
verbosePrintf(
|
||||
"Configuration successfully updated!\n"
|
||||
"Current repo configuration:\n" );
|
||||
|
||||
// printf( "%s", toString( extendedStorageInfo.config() ).c_str() );
|
||||
}
|
||||
|
||||
bool ZConfig::parse( const string & str, google::protobuf::Message * mutable_message )
|
||||
{
|
||||
return google::protobuf::TextFormat::ParseFromString( str, mutable_message );
|
||||
|
|
|
@ -23,6 +23,9 @@ public:
|
|||
// Print current configuration to screen
|
||||
void print();
|
||||
|
||||
// Edit current configuration in default editor
|
||||
void edit();
|
||||
|
||||
private:
|
||||
string toString( google::protobuf::Message const & message );
|
||||
bool parse( const string & str, google::protobuf::Message * mutable_message );
|
||||
|
|
33
zbackup.cc
33
zbackup.cc
|
@ -498,23 +498,26 @@ int main( int argc, char *argv[] )
|
|||
" --silent (default is verbose)\n"
|
||||
" --threads <number> (default is %zu on your system)\n"
|
||||
" --cache-size <number> MB (default is %zu)\n"
|
||||
" --exchange [backups|bundles|index] (can be\n"
|
||||
" --exchange <backups|bundles|index> (can be\n"
|
||||
" specified multiple times)\n"
|
||||
" --compression <compression> <lzma|lzo> (default is lzma)\n"
|
||||
" --help|-h show this message\n"
|
||||
" -o <Option> (for detailed options overview\n"
|
||||
" try to run with -o help)\n"
|
||||
" Commands:\n"
|
||||
" init <storage path> - initializes new storage;\n"
|
||||
" backup <backup file name> - performs a backup from stdin;\n"
|
||||
" restore <backup file name> - restores a backup to stdout;\n"
|
||||
" init <storage path> - initializes new storage\n"
|
||||
" backup <backup file name> - performs a backup from stdin\n"
|
||||
" restore <backup file name> - restores a backup to stdout\n"
|
||||
" export <source storage path> <destination storage path> -\n"
|
||||
" performs export from source to destination storage;\n"
|
||||
" performs export from source to destination storage\n"
|
||||
" import <source storage path> <destination storage path> -\n"
|
||||
" performs import from source to destination storage;\n"
|
||||
" gc <storage path> - performs chunk garbage collection;\n"
|
||||
" passwd <storage path> - changes repository info file passphrase;\n"
|
||||
" info <storage path> - shows information about storage;\n"
|
||||
" config <storage path> - performs configuration manipulations.\n"
|
||||
" For export/import storage path must be valid (initialized) storage.\n"
|
||||
" performs import from source to destination storage\n"
|
||||
" gc <storage path> - performs chunk garbage collection\n"
|
||||
" passwd <storage path> - changes repository info file passphrase\n"
|
||||
" info <storage path> - shows information about storage\n"
|
||||
" config <storage path> [edit|print] - performs configuration"
|
||||
" manipulations\n"
|
||||
" For export/import storage path must be valid (initialized) storage\n"
|
||||
"", *argv,
|
||||
defaultThreads, defaultCacheSizeMb );
|
||||
return EXIT_FAILURE;
|
||||
|
@ -671,7 +674,7 @@ int main( int argc, char *argv[] )
|
|||
if ( strcmp( args[ 0 ], "config" ) == 0 )
|
||||
{
|
||||
// Show repo info
|
||||
if ( args.size() != 2 )
|
||||
if ( args.size() < 2 || args.size() > 3 )
|
||||
{
|
||||
fprintf( stderr, "Usage: %s %s <storage path>\n",
|
||||
*argv, args[ 0 ] );
|
||||
|
@ -680,7 +683,11 @@ int main( int argc, char *argv[] )
|
|||
|
||||
ZConfig zc( ZBackupBase::deriveStorageDirFromBackupsFile( args[ 1 ], true ),
|
||||
passwords[ 0 ] );
|
||||
zc.print();
|
||||
|
||||
if ( args.size() > 2 && strcmp( args[ 2 ], "edit" ) == 0 )
|
||||
zc.edit();
|
||||
else
|
||||
zc.print();
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
Loading…
Reference in New Issue