2021-05-12 17:44:39 -07:00
< ? php
namespace App\Console\Commands ;
use Illuminate\Console\Command ;
use ZipArchive ;
2023-12-14 06:33:23 -08:00
class SQLStreamer {
private stream $input ;
private ? stream $output ;
// embed the prefix here?
2023-12-18 09:03:26 -08:00
public string $prefix ; //maybe make it reach-able-into-able?
// another thing to note - 'wrapping' this prefix value into the object doesn't really help us - as STDIN and STDOUT
// (and ZIP streams) can't be rewound, I don't think (well, ZIP ones can't, for sure)
//
2023-12-14 06:33:23 -08:00
2023-12-18 09:03:26 -08:00
private bool $at_start_of_line = true ;
public function __construct ( stream $input , ? stream $output , string $prefix = null ) // should we have a prefix here in the constructor?
2023-12-14 06:33:23 -08:00
{
// Iknow, I know, I'm getting there! Don't yell at me :(
$this -> $input = $input ;
$this -> $output = $output ;
2023-12-18 09:03:26 -08:00
$this -> $prefix = $prefix ;
2023-12-14 06:33:23 -08:00
}
2023-12-18 09:03:26 -08:00
public function parse_sql ( string $line ) : string {
// take into account the 'start of line or not' setting as an instance variable?
return " " ;
2023-12-14 06:33:23 -08:00
}
2023-12-18 09:03:26 -08:00
//this is used in exactly *TWO* places, and in both cases should return a prefix I think?
// first - if you do the --sanitize-only one (which is mostly for testing/development)
// next - when you run *without* a guessed prefix, this is run first to figure out the prefix
// I think we have to *duplicate* the call to be able to run it again?
public static function guess_prefix ( stream $input ) : string {
// does *this* go and change the instance settings to assign a prefix?
// spin up a new instance of my own class, and run the parse_sql thing with the guess option?
// (or maybe set the guess option directly?)
2023-12-14 06:33:23 -08:00
}
}
2021-05-12 17:44:39 -07:00
class RestoreFromBackup extends Command
{
/**
* The name and signature of the console command .
*
* @ var string
*/
protected $signature = ' snipeit : restore
2022-03-29 08:28:43 -07:00
{ -- force : Skip the danger prompt ; assuming you enter " y " }
2023-12-14 04:38:59 -08:00
{ filename ? : The zip file to be migrated }
{ -- no - progress : Don\ ' t show a progress bar }
{ -- sanitize - only : Sanitize and return SQL from STDIN }
{ -- prefix = : Don\ 't guess DB table prefix; use the passed-in one (or none if just \'--prefix=\' is passed) }' ;
2021-05-12 17:44:39 -07:00
/**
* The console command description .
*
* @ var string
*/
2021-11-10 00:07:32 -08:00
protected $description = 'Restore from a previously created Snipe-IT backup file' ;
2021-05-12 17:44:39 -07:00
/**
* Create a new command instance .
*
* @ return void
*/
public function __construct ()
{
parent :: __construct ();
}
2021-11-10 12:44:19 -08:00
public static $buffer_size = 1024 * 1024 ; // use a 1MB buffer, ought to work fine for most cases?
2023-12-14 04:38:59 -08:00
public static $prefix = null ;
public static $tablenames = [];
public static function parse_sql ( string $line , bool $should_guess = false ) : string
{
2023-12-14 06:33:23 -08:00
static $is_permitted = false ; // this 'static' is a code-smell.
2023-12-14 04:38:59 -08:00
// 'continuation' lines for a permitted statement are PERMITTED.
if ( $is_permitted && $line [ 0 ] === ' ' ) {
return $line ;
}
$table_regex = '`?([a-zA-Z0-9_]+)`?' ;
$allowed_statements = [
" /^(DROP TABLE (?:IF EXISTS )?)` $table_regex (.*) $ / " => false ,
" /^(CREATE TABLE ) $table_regex (.*) $ / " => true , //sets up 'continuation'
" /^(LOCK TABLES ) $table_regex (.*) $ / " => false ,
" /^(INSERT INTO ) $table_regex (.*) $ / " => false ,
" /^UNLOCK TABLES/ " => false ,
" /^ \\ ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/ " => false , // FIXME not sure what to do here?
// ^^^^^^ that bit should *exit* the 'perimitted' black
];
foreach ( $allowed_statements as $statement => $statechange ) {
// $this->info("Checking regex: $statement...\n");
$matches = [];
if ( preg_match ( $statement , $line , $matches )) {
$is_permitted = $statechange ;
// matches are: 1 => first part of the statement, 2 => tablename, 3 => rest of statement
// (with of course 0 being "the whole match")
if ( @ $matches [ 2 ]) {
print " Found a tablename! It's: " . $matches [ 2 ] . " \n " ;
if ( $should_guess ) {
@ self :: $tablenames [ $matches [ 2 ]] += 1 ;
continue ; //oh? FIXME
} else {
$cleaned_tablename = \DB :: getTablePrefix () . preg_replace ( '/^' . self :: $prefix , '' , $matches [ 2 ]);
$line = preg_replace ( $statement , '$1`' . $cleaned_tablename . '`$3' , $line );
}
} else {
// no explicit tablename in this one, leave the line alone
}
//how do we *replace* the tablename?
print " RETURNING LINE: $line " ;
return $line ;
}
}
// all that is not allowed is denied.
return " " ;
}
public function guess_prefix ( $stream ) //FIXME - oustream? I dunno
{
// try to determine prefix, I guess?
// should probably turn this into an instance method? Not sure.
// something where we can run this, pipe output to /dev/null? Dunno.
$bytes_read = 0 ;
2023-12-14 06:33:23 -08:00
$reading_beginning_of_line = true ; //this is weird, because I kinda think it needs to 'leak'
// Like, if $reading_beginning_of_line is FALSE, we should be continuing whatever was happening before?
// e.g. you did a big giant chunk of INSERT statements -
// and it turned into *TWO* fgets's. That's fine, right?
// The *second* fgets should be treated how the *first* fgets was.
2023-12-14 04:38:59 -08:00
while (( $buffer = fgets ( $stream , self :: $buffer_size )) !== false ) { // FIXME this is copied, can we re-use?
$bytes_read += strlen ( $buffer );
if ( $reading_beginning_of_line ) {
$cleaned_buffer = self :: parse_sql ( $buffer , true ); //this isn't quite right?
fputs ( STDOUT , $cleaned_buffer );
}
// if we got a newline at the end of this, then the _next_ read is the beginning of a line
if ( $buffer [ strlen ( $buffer ) - 1 ] === " \n " ) {
$reading_beginning_of_line = true ;
} else {
$reading_beginning_of_line = false ;
}
}
print_r ( self :: $tablenames );
$check_tables = [ 'settings' => null , 'migrations' => null /* 'assets' => null */ ];
//can't use 'users' because the 'accessories_users' table?
// can't use 'assets' because 'ver1_components_assets'
foreach ( $check_tables as $check_table => $_ignore ) {
foreach ( self :: $tablenames as $tablename => $_count ) {
// print "Comparing $tablename to $check_table\n";
if ( str_ends_with ( $tablename , $check_table )) {
// print "Found one!\n";
$check_tables [ $check_table ] = substr ( $tablename , 0 , - strlen ( $check_table ));
}
}
}
$guessed_prefix = null ;
foreach ( $check_tables as $clean_table => $prefix_guess ) {
if ( is_null ( $prefix_guess )) {
print ( " Couldn't find table $clean_table\n " );
die ();
}
if ( is_null ( $guessed_prefix )) {
$guessed_prefix = $prefix_guess ;
} else {
if ( $guessed_prefix != $prefix_guess ) {
print ( " Prefix mismatch! Had guessed $guessed_prefix but got $prefix_guess\n " );
die ();
}
}
}
print " CALCULATED PREFIX: $guessed_prefix\n " ;
self :: $prefix = $guessed_prefix ;
print_r ( $check_tables );
}
2021-11-10 12:44:19 -08:00
2021-05-12 17:44:39 -07:00
/**
* Execute the console command .
*
* @ return mixed
*/
public function handle ()
{
2023-12-14 04:38:59 -08:00
if ( $this -> option ( 'prefix' ) ) {
self :: $prefix = $this -> option ( 'prefix' );
}
if ( $this -> option ( 'sanitize-only' )) {
if ( ! self :: $prefix ) {
$this -> guess_prefix ( STDIN );
}
// for 'sanitize-only' - do we have to do something weird here, piping stuff around to stdin and stdout?
print " FINAL PREFIX IS: " . self :: $prefix . " \n " ;
return true ;
2023-12-14 06:33:23 -08:00
// so this, to me, feels like *one* mode of the whatever-streamer we have here.
// we run *this* one with the guess first, then the 'real' one, and hand STDOUT over.
2023-12-14 04:38:59 -08:00
}
2021-05-12 17:44:39 -07:00
$dir = getcwd ();
2021-11-10 12:44:19 -08:00
if ( $dir != base_path () ) { // usually only the case when running via webserver, not via command-line
\Log :: debug ( " Current working directory is: $dir , changing directory to: " . base_path ());
chdir ( base_path ()); // TODO - is this *safe* to change on a running script?!
}
2021-05-12 17:44:39 -07:00
//
$filename = $this -> argument ( 'filename' );
2021-06-10 13:15:52 -07:00
if ( ! $filename ) {
return $this -> error ( 'Missing required filename' );
2021-05-12 17:44:39 -07:00
}
2021-06-10 13:15:52 -07:00
if ( ! $this -> option ( 'force' ) && ! $this -> confirm ( 'Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!' )) {
return $this -> error ( 'Data loss not confirmed' );
2021-05-12 17:44:39 -07:00
}
if ( config ( 'database.default' ) != 'mysql' ) {
2021-06-10 13:15:52 -07:00
return $this -> error ( 'DB_CONNECTION must be MySQL in order to perform a restore. Detected: ' . config ( 'database.default' ));
2021-05-12 17:44:39 -07:00
}
$za = new ZipArchive ();
2021-06-30 14:53:08 -07:00
$errcode = $za -> open ( $filename /* , ZipArchive::RDONLY */ ); // that constant only exists in PHP 7.4 and higher
2021-05-12 17:44:39 -07:00
if ( $errcode !== true ) {
$errors = [
2021-06-10 13:15:52 -07:00
ZipArchive :: ER_EXISTS => 'File already exists.' ,
ZipArchive :: ER_INCONS => 'Zip archive inconsistent.' ,
ZipArchive :: ER_INVAL => 'Invalid argument.' ,
ZipArchive :: ER_MEMORY => 'Malloc failure.' ,
2021-11-10 00:07:32 -08:00
ZipArchive :: ER_NOENT => 'No such file (' . $filename . ') in directory ' . $dir . '.' ,
2021-06-10 13:15:52 -07:00
ZipArchive :: ER_NOZIP => 'Not a zip archive.' ,
2021-05-12 17:44:39 -07:00
ZipArchive :: ER_OPEN => " Can't open file. " ,
2021-06-10 13:15:52 -07:00
ZipArchive :: ER_READ => 'Read error.' ,
ZipArchive :: ER_SEEK => 'Seek error.' ,
2021-05-12 17:44:39 -07:00
];
2021-06-10 13:15:52 -07:00
return $this -> error ( 'Could not access file: ' . $filename . ' - ' . array_key_exists ( $errcode , $errors ) ? $errors [ $errcode ] : " Unknown reason: $errcode " );
2021-05-12 17:44:39 -07:00
}
2022-03-16 11:02:07 -07:00
2021-05-12 17:44:39 -07:00
$private_dirs = [
2023-12-07 06:08:15 -08:00
'storage/private_uploads/accessories' ,
'storage/private_uploads/assetmodels' ,
2021-05-21 15:23:23 -07:00
'storage/private_uploads/assets' , // these are asset _files_, not the pictures.
2021-05-12 17:44:39 -07:00
'storage/private_uploads/audits' ,
2023-12-07 06:08:15 -08:00
'storage/private_uploads/components' ,
'storage/private_uploads/consumables' ,
'storage/private_uploads/eula-pdfs' ,
2021-05-12 17:44:39 -07:00
'storage/private_uploads/imports' ,
'storage/private_uploads/licenses' ,
2021-06-10 13:15:52 -07:00
'storage/private_uploads/signatures' ,
2023-12-07 06:08:15 -08:00
'storage/private_uploads/users' ,
2021-05-12 17:44:39 -07:00
];
$private_files = [
'storage/oauth-private.key' ,
2021-06-10 13:15:52 -07:00
'storage/oauth-public.key' ,
2021-05-12 17:44:39 -07:00
];
$public_dirs = [
2023-12-07 06:08:15 -08:00
'public/uploads/accessories' ,
'public/uploads/assets' , // these are asset _pictures_, not asset files
'public/uploads/avatars' ,
//'public/uploads/barcodes', // we don't want this, let the barcodes be regenerated
'public/uploads/categories' ,
2021-05-12 17:44:39 -07:00
'public/uploads/companies' ,
'public/uploads/components' ,
'public/uploads/consumables' ,
'public/uploads/departments' ,
'public/uploads/locations' ,
2021-06-10 13:15:52 -07:00
'public/uploads/manufacturers' ,
2023-12-07 06:08:15 -08:00
'public/uploads/models' ,
'public/uploads/suppliers' ,
2021-05-12 17:44:39 -07:00
];
2021-06-10 13:15:52 -07:00
2021-05-12 17:44:39 -07:00
$public_files = [
2021-05-21 15:55:37 -07:00
'public/uploads/logo.*' ,
2021-05-21 15:23:23 -07:00
'public/uploads/setting-email_logo*' ,
'public/uploads/setting-label_logo*' ,
'public/uploads/setting-logo*' ,
'public/uploads/favicon.*' ,
2021-06-10 13:15:52 -07:00
'public/uploads/favicon-uploaded.*' ,
2021-05-12 17:44:39 -07:00
];
$all_files = $private_dirs + $public_dirs ;
$sqlfiles = [];
$sqlfile_indices = [];
$interesting_files = [];
2021-05-21 15:23:23 -07:00
$boring_files = [];
2021-06-10 13:15:52 -07:00
for ( $i = 0 ; $i < $za -> numFiles ; $i ++ ) {
2021-05-12 17:44:39 -07:00
$stat_results = $za -> statIndex ( $i );
// echo "index: $i\n";
// print_r($stat_results);
2021-06-10 13:15:52 -07:00
2021-05-12 17:44:39 -07:00
$raw_path = $stat_results [ 'name' ];
2021-06-10 13:15:52 -07:00
if ( strpos ( $raw_path , '\\' ) !== false ) { //found a backslash, swap it to forward-slash
$raw_path = strtr ( $raw_path , '\\' , '/' );
2021-05-12 17:44:39 -07:00
//print "Translating file: ".$stat_results['name']." to: ".$raw_path."\n";
}
2021-06-10 13:15:52 -07:00
2021-05-21 15:23:23 -07:00
// skip macOS resource fork files (?!?!?!)
2021-06-10 13:15:52 -07:00
if ( strpos ( $raw_path , '__MACOSX' ) !== false && strpos ( $raw_path , '._' ) !== false ) {
2021-05-21 15:23:23 -07:00
//print "SKIPPING macOS Resource fork file: $raw_path\n";
$boring_files [] = $raw_path ;
continue ;
}
2023-01-24 18:19:26 -08:00
if ( @ pathinfo ( $raw_path , PATHINFO_EXTENSION ) == 'sql' ) {
2021-11-10 17:08:04 -08:00
\Log :: debug ( " Found a sql file! " );
2021-05-12 17:44:39 -07:00
$sqlfiles [] = $raw_path ;
$sqlfile_indices [] = $i ;
2021-05-21 15:23:23 -07:00
continue ;
2021-05-12 17:44:39 -07:00
}
2021-06-10 13:15:52 -07:00
foreach ( array_merge ( $private_dirs , $public_dirs ) as $dir ) {
$last_pos = strrpos ( $raw_path , $dir . '/' );
if ( $last_pos !== false ) {
2021-05-12 17:44:39 -07:00
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $dir - last_pos+strlen(\$dir) is: ".($last_pos+strlen($dir))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
2021-05-21 15:23:23 -07:00
//print("We would copy $raw_path to $dir.\n"); //FIXME append to a path?
2021-05-12 17:44:39 -07:00
$interesting_files [ $raw_path ] = [ 'dest' => $dir , 'index' => $i ];
2021-05-21 15:23:23 -07:00
continue 2 ;
2021-06-10 13:15:52 -07:00
if ( $last_pos + strlen ( $dir ) + 1 == strlen ( $raw_path )) {
2021-05-12 17:44:39 -07:00
// we don't care about that; we just want files with the appropriate prefix
//print("FOUND THE EXACT DIRECTORY: $dir AT: $raw_path!!!\n");
}
}
}
2021-06-10 13:15:52 -07:00
$good_extensions = [ 'png' , 'gif' , 'jpg' , 'svg' , 'jpeg' , 'doc' , 'docx' , 'pdf' , 'txt' ,
'zip' , 'rar' , 'xls' , 'xlsx' , 'lic' , 'xml' , 'rtf' , 'webp' , 'key' , 'ico' , ];
foreach ( array_merge ( $private_files , $public_files ) as $file ) {
$has_wildcard = ( strpos ( $file , '*' ) !== false );
if ( $has_wildcard ) {
$file = substr ( $file , 0 , - 1 ); //trim last character (which should be the wildcard)
2021-05-21 15:23:23 -07:00
}
2021-06-10 13:15:52 -07:00
$last_pos = strrpos ( $raw_path , $file ); // no trailing slash!
if ( $last_pos !== false ) {
2021-05-21 15:23:23 -07:00
$extension = strtolower ( pathinfo ( $raw_path , PATHINFO_EXTENSION ));
2021-06-10 13:15:52 -07:00
if ( ! in_array ( $extension , $good_extensions )) {
$this -> warn ( 'Potentially unsafe file ' . $raw_path . ' is being skipped' );
2021-05-21 15:23:23 -07:00
$boring_files [] = $raw_path ;
continue 2 ;
}
2021-05-12 17:44:39 -07:00
//print("INTERESTING - last_pos is $last_pos when searching $raw_path for $file - last_pos+strlen(\$file) is: ".($last_pos+strlen($file))." and strlen(\$rawpath) is: ".strlen($raw_path)."\n");
2021-05-21 15:23:23 -07:00
//no wildcards found in $file, process 'normally'
2021-06-10 13:15:52 -07:00
if ( $last_pos + strlen ( $file ) == strlen ( $raw_path ) || $has_wildcard ) { //again, no trailing slash. or this is a wildcard and we just take it.
2021-05-21 15:23:23 -07:00
// print("FOUND THE EXACT FILE: $file AT: $raw_path!!!\n"); //we *do* care about this, though.
2021-06-10 13:15:52 -07:00
$interesting_files [ $raw_path ] = [ 'dest' => dirname ( $file ), 'index' => $i ];
2021-05-21 15:23:23 -07:00
continue 2 ;
2021-05-12 17:44:39 -07:00
}
}
}
2021-05-21 15:23:23 -07:00
$boring_files [] = $raw_path ; //if we've gotten to here and haven't continue'ed our way into the next iteration, we don't want this file
} // end of pre-processing the ZIP file for-loop
2021-05-12 17:44:39 -07:00
// print_r($interesting_files);exit(-1);
2021-06-10 13:15:52 -07:00
if ( count ( $sqlfiles ) != 1 ) {
return $this -> error ( 'There should be exactly *one* sql backup file found, found: ' . ( count ( $sqlfiles ) == 0 ? 'None' : implode ( ', ' , $sqlfiles )));
2021-05-12 17:44:39 -07:00
}
2021-06-10 13:15:52 -07:00
if ( strpos ( $sqlfiles [ 0 ], 'db-dumps' ) === false ) {
2021-05-12 17:44:39 -07:00
//return $this->error("SQL backup file is missing 'db-dumps' component of full pathname: ".$sqlfiles[0]);
//older Snipe-IT installs don't have the db-dumps subdirectory component
}
//how to invoke the restore?
$pipes = [];
$env_vars = getenv ();
2021-06-10 13:15:52 -07:00
$env_vars [ 'MYSQL_PWD' ] = config ( 'database.connections.mysql.password' );
2021-11-10 12:44:19 -08:00
// TODO notes: we are stealing the dump_binary_path (which *probably* also has your copy of the mysql binary in it. But it might not, so we might need to extend this)
// we unilaterally prepend a slash to the `mysql` command. This might mean your path could look like /blah/blah/blah//mysql - which should be fine. But maybe in some environments it isn't?
2023-01-10 13:06:47 -08:00
$mysql_binary = config ( 'database.connections.mysql.dump.dump_binary_path' ) . \DIRECTORY_SEPARATOR . 'mysql' . ( \DIRECTORY_SEPARATOR == '\\' ? " .exe " : " " );
2021-11-10 17:08:04 -08:00
if ( ! file_exists ( $mysql_binary ) ) {
return $this -> error ( " mysql tool at: ' $mysql_binary ' does not exist, cannot restore. Please edit DB_DUMP_PATH in your .env to point to a directory that contains the mysqldump and mysql binary " );
}
$proc_results = proc_open ( " $mysql_binary -h " . escapeshellarg ( config ( 'database.connections.mysql.host' )) . ' -u ' . escapeshellarg ( config ( 'database.connections.mysql.username' )) . ' ' . escapeshellarg ( config ( 'database.connections.mysql.database' )), // yanked -p since we pass via ENV
2021-06-10 13:15:52 -07:00
[ 0 => [ 'pipe' , 'r' ], 1 => [ 'pipe' , 'w' ], 2 => [ 'pipe' , 'w' ]],
2021-05-12 17:44:39 -07:00
$pipes ,
null ,
$env_vars ); // this is not super-duper awesome-secure, but definitely more secure than showing it on the CLI, or dropping temporary files with passwords in them.
2021-06-10 13:15:52 -07:00
if ( $proc_results === false ) {
return $this -> error ( 'Unable to invoke mysql via CLI' );
2021-05-12 17:44:39 -07:00
}
2023-12-18 09:03:26 -08:00
// I'm not sure about these?
2022-05-17 17:01:23 -07:00
stream_set_blocking ( $pipes [ 1 ], false ); // use non-blocking reads for stdout
stream_set_blocking ( $pipes [ 2 ], false ); // use non-blocking reads for stderr
2021-05-12 17:44:39 -07:00
// $this->info("Stdout says? ".fgets($pipes[1])); //FIXME: I think we might need to set non-blocking mode to use this properly?
// $this->info("Stderr says? ".fgets($pipes[2])); //FIXME: ditto, same.
// should we read stdout?
// fwrite($pipes[0],config("database.connections.mysql.password")."\n"); //this doesn't work :(
//$sql_contents = fopen($sqlfiles[0], "r"); //NOPE! This isn't a real file yet, silly-billy!
$sql_stat = $za -> statIndex ( $sqlfile_indices [ 0 ]);
2021-05-21 15:23:23 -07:00
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
2023-12-14 04:38:59 -08:00
$sql_contents = $za -> getStream ( $sql_stat [ 'name' ]); /// duplicate this?
2021-05-12 17:44:39 -07:00
if ( $sql_contents === false ) {
$stdout = fgets ( $pipes [ 1 ]);
$this -> info ( $stdout );
$stderr = fgets ( $pipes [ 2 ]);
$this -> info ( $stderr );
2021-06-10 13:15:52 -07:00
2021-05-12 17:44:39 -07:00
return false ;
}
2023-12-14 04:38:59 -08:00
$sql_prefix_sniff = $za -> getStream ( $sql_stat [ 'name' ]); /// duplicate this?
self :: guess_prefix ( $sql_prefix_sniff );
2021-11-10 17:08:04 -08:00
$bytes_read = 0 ;
2023-12-14 04:38:59 -08:00
$reading_beginning_of_line = true ;
2022-03-16 11:05:47 -07:00
2023-12-14 06:33:23 -08:00
// so this whole bit seems like a dupe - but it's similar as the one that guesses tyings.
// The difference is our out-stream is now the pipe to mysql
2022-05-17 17:01:23 -07:00
try {
while (( $buffer = fgets ( $sql_contents , self :: $buffer_size )) !== false ) {
$bytes_read += strlen ( $buffer );
2023-12-14 04:38:59 -08:00
if ( $reading_beginning_of_line ) {
//check allowlist?
// \Log::debug("Buffer is: '$buffer'");
2022-05-17 17:01:23 -07:00
$bytes_written = fwrite ( $pipes [ 0 ], $buffer );
2023-12-14 04:38:59 -08:00
if ( $bytes_written === false ) {
throw new Exception ( " Unable to write to pipe " );
}
2022-05-17 17:01:23 -07:00
}
2023-12-14 04:38:59 -08:00
// if we got a newline at the end of this, then the _next_ read is the beginning of a line
if ( $buffer [ strlen ( $buffer ) - 1 ] === " \n " ) {
$reading_beginning_of_line = true ;
} else {
$reading_beginning_of_line = false ;
}
2022-03-16 11:05:47 -07:00
}
2022-05-17 17:01:23 -07:00
} catch ( \Exception $e ) {
\Log :: error ( " Error during restore!!!! " . $e -> getMessage ());
$err_out = fgets ( $pipes [ 1 ]);
$err_err = fgets ( $pipes [ 2 ]);
\Log :: error ( " Error OUTPUT: " . $err_out );
$this -> info ( $err_out );
\Log :: error ( " Error ERROR : " . $err_err );
$this -> error ( $err_err );
throw $e ;
2021-05-12 17:44:39 -07:00
}
2022-03-16 11:05:47 -07:00
2021-11-10 17:08:04 -08:00
if ( ! feof ( $sql_contents ) || $bytes_read == 0 ) {
return $this -> error ( " Not at end of file for sql file, or zero bytes read. aborting! " );
}
2021-05-12 17:44:39 -07:00
fclose ( $pipes [ 0 ]);
fclose ( $sql_contents );
2021-06-30 14:53:08 -07:00
$this -> line ( stream_get_contents ( $pipes [ 1 ]));
2021-05-12 17:44:39 -07:00
fclose ( $pipes [ 1 ]);
2021-06-30 14:53:08 -07:00
$this -> error ( stream_get_contents ( $pipes [ 2 ]));
2021-05-12 17:44:39 -07:00
fclose ( $pipes [ 2 ]);
2021-06-30 14:53:08 -07:00
2021-05-12 17:44:39 -07:00
//wait, have to do fclose() on all pipes first?
$close_results = proc_close ( $proc_results );
2021-06-10 13:15:52 -07:00
if ( $close_results != 0 ) {
return $this -> error ( 'There may have been a problem with the database import: Error number ' . $close_results );
2021-05-12 17:44:39 -07:00
}
2021-06-10 13:15:52 -07:00
2021-05-12 17:44:39 -07:00
//and now copy the files over too (right?)
//FIXME - we don't prune the filesystem space yet!!!!
2021-06-10 13:15:52 -07:00
if ( $this -> option ( 'no-progress' )) {
2021-05-21 15:23:23 -07:00
$bar = null ;
} else {
$bar = $this -> output -> createProgressBar ( count ( $interesting_files ));
}
2021-06-10 13:15:52 -07:00
foreach ( $interesting_files as $pretty_file_name => $file_details ) {
2021-05-12 17:44:39 -07:00
$ugly_file_name = $za -> statIndex ( $file_details [ 'index' ])[ 'name' ];
$fp = $za -> getStream ( $ugly_file_name );
2021-05-21 15:23:23 -07:00
//$this->info("Weird problem, here are file details? ".print_r($file_details,true));
2021-06-10 13:15:52 -07:00
$migrated_file = fopen ( $file_details [ 'dest' ] . '/' . basename ( $pretty_file_name ), 'w' );
2021-11-10 12:44:19 -08:00
while (( $buffer = fgets ( $fp , self :: $buffer_size )) !== false ) {
2021-06-10 13:15:52 -07:00
fwrite ( $migrated_file , $buffer );
2021-05-12 17:44:39 -07:00
}
fclose ( $migrated_file );
fclose ( $fp );
2021-05-21 15:23:23 -07:00
//$this->info("Wrote $ugly_file_name to $pretty_file_name");
2021-06-10 13:15:52 -07:00
if ( $bar ) {
2021-05-21 15:23:23 -07:00
$bar -> advance ();
}
}
2021-06-10 13:15:52 -07:00
if ( $bar ) {
2021-05-21 15:23:23 -07:00
$bar -> finish ();
2021-06-10 13:15:52 -07:00
$this -> line ( '' );
2021-05-21 15:23:23 -07:00
} else {
2021-06-10 13:15:52 -07:00
$this -> info ( count ( $interesting_files ) . ' files were succesfully transferred' );
2021-05-21 15:23:23 -07:00
}
2021-06-10 13:15:52 -07:00
foreach ( $boring_files as $boring_file ) {
$this -> warn ( $boring_file . ' was skipped.' );
2021-05-12 17:44:39 -07:00
}
}
}