Commit 79092eee authored by anarcat's avatar anarcat

handle mysqldump errors again

we rewrite the dump code completely, stealing bits from
safe_shell_exec() for the DB credentials, to directly do the search
and replace of certain patterns ourselves. this simplifies the
pipeline and should also improve performance (because we avoid forking
sed twice), unless some memory leaks exist in PHP (which is quite
possible). we process the dump one line at a time so memory usage
shouldn't be higher.

the previous code would not catch errors from mysqldump because the
"sed" pipeline would succeed and therefore hide error from earlier in
the pipeline. we would need "set -o pipefail" in bash to workaround
this issue, but that is not portable and, let's be honest, there
already enough crap here as it is to warrant a cleanup.

previous attempt at fixing this issue (#2098289) are sometimes
unsuccesful because some dumps fail later than 1024 bytes. we still
keep that check just in case.

the bug (#1324466) was introduced in 571b63de, present since the 1.1

this should be refactored to make safe_shell_exec() allow for a hook
to modify the dumpfile inline, but for now i just wanted to port a
working prototype in place.
parent 2a6d5a61
......@@ -124,8 +124,74 @@ class Provision_Service_db_mysql extends Provision_Service_db_pdo {
// non-readable by the webserver.
// Mixed copy-paste of drush_shell_exec and provision_shell_exec.
$cmd = sprintf("mysqldump --defaults-file=/dev/fd/3 --single-transaction --quick --no-autocommit %s | sed 's|/\\*!50001 CREATE ALGORITHM=UNDEFINED \\*/|/\\*!50001 CREATE \\*/|g; s|/\\*!50017 DEFINER=`[^`]*`@`[^`]*`\s*\\*/||g' | sed '/\\*!50013 DEFINER=.*/ d' > %s/database.sql", escapeshellcmd(drush_get_option('db_name')), escapeshellcmd(d()->site_path));
$success = $this->safe_shell_exec($cmd, drush_get_option('db_host'), urldecode(drush_get_option('db_user')), urldecode(drush_get_option('db_passwd')));
$cmd = sprintf("mysqldump --defaults-file=/dev/fd/3 --single-transaction --quick --no-autocommit %s", escapeshellcmd(drush_get_option('db_name')));
$db_host = drush_get_option('db_host');
$db_user = urldecode(drush_get_option('db_user'));
$db_passwd = urldecode(drush_get_option('db_passwd')));
// duplicate safe_shell_exec() because we want to replace the output
// inline
$mycnf = sprintf('[client]
', $db_host, $db_user, $db_passwd, $this->server->db_port);
// fail if db file already exists
$dumpfile = fopen(d()->site_path . '/database.sql', 'x');
if ($dump_file === FALSE) {
drush_set_error('PROVISION_BACKUP_FAILED', dt('Could not write database backup file mysqldump'));
else {
$process = proc_open($cmd, $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[3], $mycnf);
// okay, at this point we have opened a pipe to that mysqldump
// command, now we want to read it one line at a time and do our
// replacement
while (($buffer = fgets($pipes[1], 4096)) !== false) {
// remove DEFINER entries
// XXX: this should be anchored at ^
// original sed regex: /\\*!50013 DEFINER=.*/d
if (preg_match('#/\\*!50013 DEFINER=.*/#', $buffer)) {
// remove another kind of DEFINER line
// original sed regex: s|/\\*!50017 DEFINER=`[^`]*`@`[^`]*`\s*\\*/||g
// XXX: should also be anchored
// XXX: why the hell is there *another* DEFINER regex here?!
$buffer = preg_replace('#/\\*!50017 DEFINER=`[^`]*`@`[^`]*`\s*\\*/#', '', $buffer);
if (is_null($buffer)) {
// preg exploded in our face, oops.
drush_set_error('PROVISION_BACKUP_FAILED', dt('Error while running regular expression'));
// remove broken CREATE ALGORITHM entries
// original sed regex: s|/\\*!50001 CREATE ALGORITHM=UNDEFINED \\*/|/\\*!50001 CREATE \\*/|g
// XXX: should also be anchored
$buffer = preg_replace('#/\\*!50001 CREATE ALGORITHM=UNDEFINED \\*/#', '/\\*!50001 CREATE \\*/', $buffer);
// write the resulting line in the backup file
if (fwrite($dumpfile, $buffer) === FALSE) {
drush_set_error('PROVISION_BACKUP_FAILED', dt('Could not write database backup file mysqldump'));
// catch errors returned by mysqldump
// close stderr as well
$err = fread($pipes[2], 4096);
if (proc_close($process) != 0) {
drush_set_error('PROVISION_BACKUP_FAILED', dt('Could not write database backup file mysqldump (error: %msg)', array('%msg' => $err)));
else {
drush_set_error('PROVISION_BACKUP_FAILED', dt('Could not run mysqldump for backups'));
$dump_size_too_small = filesize(d()->site_path . '/database.sql') < 1024;
if ((!$success || $dump_size_too_small) && !drush_get_option('force', FALSE)) {
Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment