2021-05-12 17:44:39 -07:00
< ? php
namespace App\Console\Commands ;
use Illuminate\Console\Command ;
use ZipArchive ;
2024-05-29 04:38:15 -07:00
use Illuminate\Support\Facades\Log ;
2021-05-12 17:44:39 -07:00
2023-12-14 06:33:23 -08:00
class SQLStreamer {
2023-12-19 07:31:59 -08:00
private $input ;
private $output ;
2023-12-14 06:33:23 -08:00
// embed the prefix here?
2023-12-19 07:31:59 -08:00
public ? string $prefix ;
2023-12-14 06:33:23 -08:00
2023-12-19 07:31:59 -08:00
private bool $reading_beginning_of_line = true ;
2023-12-18 09:03:26 -08:00
2023-12-19 07:31:59 -08:00
public static $buffer_size = 1024 * 1024 ; // use a 1MB buffer, ought to work fine for most cases?
2021-05-12 17:44:39 -07:00
2023-12-19 07:31:59 -08:00
public array $tablenames = [];
private bool $should_guess = false ;
private bool $statement_is_permitted = false ;
2021-05-12 17:44:39 -07:00
2023-12-19 07:31:59 -08:00
public function __construct ( $input , $output , string $prefix = null )
2021-05-12 17:44:39 -07:00
{
2023-12-19 07:31:59 -08:00
$this -> input = $input ;
$this -> output = $output ;
$this -> prefix = $prefix ;
2021-05-12 17:44:39 -07:00
}
2023-12-19 07:31:59 -08:00
public function parse_sql ( string $line ) : string {
// take into account the 'start of line or not' setting as an instance variable?
2023-12-14 04:38:59 -08:00
// 'continuation' lines for a permitted statement are PERMITTED.
2024-07-25 10:07:25 -07:00
// remove *only* line-feeds & carriage-returns; helpful for regexes against lines from
// Windows dumps
$line = trim ( $line , " \r \n " );
2023-12-19 07:31:59 -08:00
if ( $this -> statement_is_permitted && $line [ 0 ] === ' ' ) {
2024-07-25 10:07:25 -07:00
return $line . " \n " ; //re-add the newline
2023-12-14 04:38:59 -08:00
}
$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 ,
2023-12-19 07:31:59 -08:00
// "/^\\) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;/" => false, // FIXME not sure what to do here?
2024-07-25 10:07:25 -07:00
" /^ \\ )[a-zA-Z0-9_= ]*; $ / " => false ,
// ^^^^^^ that bit should *exit* the 'permitted' block
" /^ \\ (.* \\ )[,;] $ / " => false , //older MySQL dump style with one set of values per line
/* we * could * have made the ^ INSERT INTO blah VALUES $ turn on the capturing state , and closed it with
a ^ ( blahblah ); $ but it 's cleaner to not have to manage the state machine. We' re just going to
assume that ( blahblah ), or ( blahblah ); are values for INSERT and are always acceptable . */
2023-12-14 04:38:59 -08:00
];
foreach ( $allowed_statements as $statement => $statechange ) {
// $this->info("Checking regex: $statement...\n");
$matches = [];
if ( preg_match ( $statement , $line , $matches )) {
2023-12-19 07:31:59 -08:00
$this -> statement_is_permitted = $statechange ;
2023-12-14 04:38:59 -08:00
// 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 ]) {
2023-12-19 07:31:59 -08:00
// print "Found a tablename! It's: ".$matches[2]."\n";
if ( $this -> should_guess ) {
@ $this -> tablenames [ $matches [ 2 ]] += 1 ;
2023-12-14 04:38:59 -08:00
continue ; //oh? FIXME
} else {
2023-12-19 07:31:59 -08:00
$cleaned_tablename = \DB :: getTablePrefix () . preg_replace ( '/^' . $this -> prefix . '/' , '' , $matches [ 2 ]);
2023-12-14 04:38:59 -08:00
$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?
2023-12-19 07:31:59 -08:00
// print "RETURNING LINE: $line";
2024-07-25 10:07:25 -07:00
return $line . " \n " ; //re-add newline
2023-12-14 04:38:59 -08:00
}
}
// all that is not allowed is denied.
return " " ;
}
2023-12-19 07:31:59 -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 ( $input ) : string
2023-12-14 04:38:59 -08:00
{
2023-12-19 07:31:59 -08:00
$parser = new self ( $input , null );
$parser -> should_guess = true ;
$parser -> line_aware_piping (); // <----- THIS is doing the heavy lifting!
2023-12-14 04:38:59 -08:00
2023-12-19 07:31:59 -08:00
$check_tables = [ 'settings' => null , 'migrations' => null /* 'assets' => null */ ]; //TODO - move to statics?
2023-12-14 04:38:59 -08:00
//can't use 'users' because the 'accessories_users' table?
// can't use 'assets' because 'ver1_components_assets'
foreach ( $check_tables as $check_table => $_ignore ) {
2023-12-19 07:31:59 -08:00
foreach ( $parser -> tablenames as $tablename => $_count ) {
2023-12-14 04:38:59 -08:00
// 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 ();
}
}
}
2023-12-19 07:31:59 -08:00
return $guessed_prefix ;
}
public function line_aware_piping () : int
{
$bytes_read = 0 ;
if ( ! $this -> input ) {
2024-02-15 05:27:18 -08:00
throw new \Exception ( " No Input available for line_aware_piping " );
2023-12-19 07:31:59 -08:00
}
2024-02-15 05:27:18 -08:00
while (( $buffer = fgets ( $this -> input , SQLStreamer :: $buffer_size )) !== false ) {
$bytes_read += strlen ( $buffer );
if ( $this -> reading_beginning_of_line ) {
2024-05-29 04:38:15 -07:00
// Log::debug("Buffer is: '$buffer'");
2024-02-15 05:27:18 -08:00
$cleaned_buffer = $this -> parse_sql ( $buffer );
if ( $this -> output ) {
$bytes_written = fwrite ( $this -> output , $cleaned_buffer );
if ( $bytes_written === false ) {
throw new \Exception ( " Unable to write to pipe " );
2023-12-19 07:31:59 -08:00
}
}
}
2024-02-15 05:27:18 -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 " ) {
$this -> reading_beginning_of_line = true ;
} else {
$this -> reading_beginning_of_line = false ;
}
}
return $bytes_read ;
2023-12-19 07:31:59 -08:00
}
}
class RestoreFromBackup extends Command
{
/**
* The name and signature of the console command .
*
* @ var string
*/
2024-02-15 05:27:18 -08:00
// FIXME - , stripping prefixes and nonstandard SQL statements. Without --prefix, guess and return the correct prefix to strip
2023-12-19 07:31:59 -08:00
protected $signature = ' snipeit : restore
{ -- force : Skip the danger prompt ; assuming you enter " y " }
2024-02-15 05:27:18 -08:00
{ filename : The zip file to be migrated }
2023-12-19 07:31:59 -08:00
{ -- no - progress : Don\ ' t show a progress bar }
2024-02-15 05:27:18 -08:00
{ -- sanitize - guess - prefix : Guess and output the table - prefix needed to " sanitize " the SQL }
2024-07-25 10:07:25 -07:00
{ -- sanitize - with - prefix = : " Sanitize " the SQL , using the passed - in table prefix ( can be learned from -- sanitize - guess - prefix ) . Pass as just \ ' -- sanitize - with - prefix = \ ' to use no prefix }
{ -- sql - stdout - only : ONLY " Sanitize " the SQL and print it to stdout - useful for debugging - probably requires -- sanitize - with - prefix = } ' ;
2023-12-19 07:31:59 -08:00
/**
* The console command description .
*
* @ var string
*/
protected $description = 'Restore from a previously created Snipe-IT backup file' ;
/**
* Create a new command instance .
*
* @ return void
*/
public function __construct ()
{
parent :: __construct ();
2023-12-14 04:38:59 -08:00
}
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 ()
{
$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
2024-05-29 04:38:15 -07:00
Log :: debug ( " Current working directory is: $dir , changing directory to: " . base_path ());
2021-11-10 12:44:19 -08:00
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
}
2024-02-15 05:27:18 -08:00
if ( ! $this -> option ( 'force' ) && ! $this -> option ( 'sanitize-guess-prefix' ) && ! $this -> confirm ( 'Are you sure you wish to restore from the given backup file? This can lead to MASSIVE DATA LOSS!' )) {
2021-06-10 13:15:52 -07:00
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' ) {
2024-05-29 04:38:15 -07: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 ) {
2024-02-15 05:27:18 -08:00
$last_pos = strrpos ( $raw_path , $dir . '/' );
2021-06-10 13:15:52 -07:00
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?
2024-02-15 05:27:18 -08: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' ,
2024-02-15 05:27:18 -08:00
'zip' , 'rar' , 'xls' , 'xlsx' , 'lic' , 'xml' , 'rtf' , 'webp' , 'key' , 'ico' ,];
2021-06-10 13:15:52 -07:00
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 ));
2024-02-15 05:27:18 -08: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
}
2024-02-15 05:27:18 -08:00
$sql_stat = $za -> statIndex ( $sqlfile_indices [ 0 ]);
//$this->info("SQL Stat is: ".print_r($sql_stat,true));
$sql_contents = $za -> getStream ( $sql_stat [ 'name' ]); // maybe copy *THIS* thing?
// OKAY, now that we *found* the sql file if we're doing just the guess-prefix thing, we can do that *HERE* I think?
if ( $this -> option ( 'sanitize-guess-prefix' )) {
$prefix = SQLStreamer :: guess_prefix ( $sql_contents );
$this -> line ( $prefix );
return $this -> info ( " Re-run this command with '--sanitize-with-prefix= " . $prefix . " ' to see an attempt to sanitze your SQL. " );
}
2024-07-25 10:07:25 -07:00
// If we're doing --sql-stdout-only, handle that now so we don't have to open pipes to mysql and all of that silliness
if ( $this -> option ( 'sql-stdout-only' )) {
$sql_importer = new SQLStreamer ( $sql_contents , STDOUT , $this -> option ( 'sanitize-with-prefix' ));
$bytes_read = $sql_importer -> line_aware_piping ();
return $this -> warn ( " $bytes_read total bytes read " );
//TODO - it'd be nice to dump this message to STDERR so that STDOUT is just pure SQL,
// which would be good for redirecting to a file, and not having to trim the last line off of it
}
2021-05-12 17:44:39 -07:00
//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!
2024-02-15 05:27:18 -08:00
// FIXME - this feels like it wants to go somewhere else?
// and it doesn't seem 'right' - if you can't get a stream to the .sql file,
// why do we care what's happening with pipes and stdout and stderr?!
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 ;
}
2022-03-16 11:05:47 -07:00
2022-05-17 17:01:23 -07:00
try {
2024-02-15 05:27:18 -08:00
if ( $this -> option ( 'sanitize-with-prefix' ) === null ) {
// "Legacy" direct-piping
$bytes_read = 0 ;
while (( $buffer = fgets ( $sql_contents , SQLStreamer :: $buffer_size )) !== false ) {
2024-02-13 08:45:24 -08:00
$bytes_read += strlen ( $buffer );
2024-05-29 04:38:15 -07:00
// Log::debug("Buffer is: '$buffer'");
2024-02-13 08:45:24 -08:00
$bytes_written = fwrite ( $pipes [ 0 ], $buffer );
if ( $bytes_written === false ) {
throw new Exception ( " Unable to write to pipe " );
}
}
} else {
2024-02-15 05:27:18 -08:00
$sql_importer = new SQLStreamer ( $sql_contents , $pipes [ 0 ], $this -> option ( 'sanitize-with-prefix' ));
2024-02-13 08:45:24 -08:00
$bytes_read = $sql_importer -> line_aware_piping ();
}
2022-05-17 17:01:23 -07:00
} catch ( \Exception $e ) {
2024-05-29 04:38:15 -07:00
Log :: error ( " Error during restore!!!! " . $e -> getMessage ());
2023-12-19 07:31:59 -08:00
// FIXME - put these back and/or put them in the right places?!
2022-05-17 17:01:23 -07:00
$err_out = fgets ( $pipes [ 1 ]);
$err_err = fgets ( $pipes [ 2 ]);
2024-05-29 04:38:15 -07:00
Log :: error ( " Error OUTPUT: " . $err_out );
2022-05-17 17:01:23 -07:00
$this -> info ( $err_out );
2024-05-29 04:38:15 -07:00
Log :: error ( " Error ERROR : " . $err_err );
2022-05-17 17:01:23 -07:00
$this -> error ( $err_err );
throw $e ;
2021-05-12 17:44:39 -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));
2024-07-25 10:40:26 -07:00
if ( ! is_dir ( $file_details [ 'dest' ])) {
mkdir ( $file_details [ 'dest' ], 0755 , true ); //0755 is what Laravel uses, so we do that
}
2021-06-10 13:15:52 -07:00
$migrated_file = fopen ( $file_details [ 'dest' ] . '/' . basename ( $pretty_file_name ), 'w' );
2024-02-15 05:27:18 -08:00
while (( $buffer = fgets ( $fp , SQLStreamer :: $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
}
}
}