diff --git a/server/lib/classes/backup.inc.php b/server/lib/classes/backup.inc.php index 1685f8da51016f706434fe6078e5f855f756dbe7..486493a20e6a92671c666542b062c0a10d8d6715 100644 --- a/server/lib/classes/backup.inc.php +++ b/server/lib/classes/backup.inc.php @@ -736,11 +736,12 @@ class backup //First check that all records in database have related files and delete records without files on disk $args = array(); $args_domains = array(); + $args_domains_with_backups = array(); $server_config = $app->getconf->get_server_config($server_id, 'server'); $backup_dir = trim($server_config['backup_dir']); $sql = "SELECT * FROM web_backup WHERE server_id = ?"; $sql_domains = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE server_id = ? AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')"; - $sql_domains_with_backups = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE domain_id in (SELECT * FROM web_backup WHERE server_id = ?" . ((!empty($backup_type)) ? " AND backup_type = ?" : "") . ") AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')"; + $sql_domains_with_backups = "SELECT domain_id,document_root,system_user,system_group,backup_interval FROM web_domain WHERE domain_id in (SELECT parent_domain_id FROM web_backup WHERE server_id = ?" . ((!empty($backup_type)) ? " AND backup_type = ?" : "") . ") AND (type = 'vhost' OR type = 'vhostsubdomain' OR type = 'vhostalias')"; array_push($args, $server_id); array_push($args_domains, $server_id); array_push($args_domains_with_backups, $server_id); @@ -759,7 +760,7 @@ class backup } array_unshift($args, $sql); array_unshift($args_domains, $sql_domains); - array_unshift($args_domains_with_backups, $sql_domains); + array_unshift($args_domains_with_backups, $sql_domains_with_backups); $db_list = array($app->db); if ($app->db->dbHost != $app->dbmaster->dbHost) @@ -779,7 +780,7 @@ class backup } // Cleanup backup files with missing web_backup entries (runs on all servers) - $domains = $app->dbmaster->queryAllRecords($args_domains_with_backups); + $domains = call_user_func_array(array($app->dbmaster, "queryAllRecords"), $args_domains_with_backups); foreach ($domains as $rec) { $domain_id = $rec['domain_id']; $domain_backup_dir = $backup_dir . '/web' . $domain_id; @@ -788,29 +789,29 @@ class backup if (!empty($files)) { // leave out server_id here, in case backup storage is shared between servers $sql = "SELECT backup_id, filename FROM web_backup WHERE parent_domain_id = ?"; + $untracked_backup_files = array(); foreach ($db_list as $db) { - $backup_record_exists = false; - $backups = $db->queryAllRecords($sql, $server_id, $domain_id); + $backups = $db->queryAllRecords($sql, $domain_id); foreach ($backups as $backup) { - if (in_array($backup['filename'],$files)) { - $backup_record_exists = true; + if (!in_array($backup['filename'],$files)) { + $untracked_backup_files[] = $backup['filename']; } } - if (!$backup_record_exists) { - $backup_file = $backup_dir . '/web' . $domain_id . '/' . $backup['filename']; - $app->log('Backup file ' . $backup_file . ' is not contained in database, deleting this file from disk', LOGLEVEL_DEBUG); - @unlink($backup_file); - } + } + array_unique( $untracked_backup_files ); + foreach ($untracked_backup_files as $f) { + $backup_file = $backup_dir . '/web' . $domain_id . '/' . $f; + $app->log('Backup file ' . $backup_file . ' is not contained in database, deleting this file from disk', LOGLEVEL_DEBUG); + @unlink($backup_file); } } } // This cleanup only runs on web servers - $domains = $app->db->queryAllRecords($args_domains); + $domains = call_user_func_array(array($app->db, "queryAllRecords"), $args_domains); foreach ($domains as $rec) { $domain_id = $rec['domain_id']; $domain_backup_dir = $backup_dir . '/web' . $domain_id; - $files = self::get_files($domain_backup_dir); // Remove backupdir symlink and create as directory instead if (is_link($backup_download_dir) || !is_dir($backup_download_dir)) { @@ -836,7 +837,7 @@ class backup $now = time(); while (false !== ($entry = $dir_handle->read())) { $full_filename = $backup_download_dir . '/' . $entry; - if ($entry != '.' && $entry != '..' && is_file($full_filename)) { + if ($entry != '.' && $entry != '..' && is_file($full_filename) && ! is_link($full_filename)) { // delete files older than 3 days if ($now - filemtime($full_filename) >= 60 * 60 * 24 * 3) { $app->log('Backup file ' . $full_filename . ' is too old, deleting this file from disk', LOGLEVEL_DEBUG); @@ -919,16 +920,24 @@ class backup * Generates excludes list for compressors * @param string[] $backup_excludes * @param string $arg + * @param string $pre + * @param string $post * @return string * @author Ramil Valitov <ramilvalitov@gmail.com> */ - protected static function generateExcludeList($backup_excludes, $arg) + protected static function generateExcludeList($backup_excludes, $arg, $pre='', $post='') { - $excludes = implode(" " . $arg, $backup_excludes); - if (!empty($excludes)) { - $excludes = $arg . $excludes; + $excludes = ""; + foreach ($backup_excludes as $ex) { + # pass through escapeshellarg if not already done + if ( preg_match( "/^'.+'$/", $ex ) ) { + $excludes .= "${arg}${pre}${ex}${post} "; + } else { + $excludes .= "${arg}" . escapeshellarg("${pre}${ex}${post}") . " "; + } } - return $excludes; + + return trim( $excludes ); } /** @@ -987,12 +996,14 @@ class backup if (!empty($password)) { $zip_options .= ' --password ' . escapeshellarg($password); } + $excludes = self::generateExcludeList($backup_excludes, '-x '); + $excludes .= " " . self::generateExcludeList($backup_excludes, '-x ', '', '/*'); if ($backup_mode == 'user_zip') { //Standard casual behaviour of ISPConfig - $app->system->exec_safe($find_user_files . ' | zip ' . $zip_options . ' -b ? ' . $excludes . ' --symlinks ? -@', $web_path, $web_user, $web_group, $http_server_user, $backup_tmp, $web_backup_dir . '/' . $web_backup_file); + $app->system->exec_safe($find_user_files . ' | zip ' . $zip_options . ' -b ? --symlinks ? -@ ' . $excludes, $web_path, $web_user, $web_group, $http_server_user, $backup_tmp, $web_backup_dir . '/' . $web_backup_file); } else { - //Use cd to have a correct directory structure inside the archive, extra options to zip hidden (dot) files - $app->system->exec_safe('cd ? && zip ' . $zip_options . ' -b ? ' . $excludes . ' --symlinks -r ? * .* -x "../*"', $web_path, $backup_tmp, $web_backup_dir . '/' . $web_backup_file); + //Use cd to have a correct directory structure inside the archive, zip current directory "." to include hidden (dot) files + $app->system->exec_safe('cd ? && zip ' . $zip_options . ' -b ? --symlinks -r ? . ' . $excludes, $web_path, $backup_tmp, $web_backup_dir . '/' . $web_backup_file); } $exit_code = $app->system->last_exec_retcode(); // zip can return 12(due to harmless warnings) and still create valid backups @@ -1237,8 +1248,8 @@ class backup //* Remove old backups self::backups_garbage_collection($server_id, 'mysql', $domain_id); $prefix_list = array( - 'db_'.escapeshellarg($db_name).'_', - 'manual-db_'.escapeshellarg($db_name).'_', + "db_${db_name}_", + "manual-db_${db_name}_", ); self::clearBackups($server_id, $domain_id, intval($rec['backup_copies']), $db_backup_dir, $prefix_list); } else { @@ -1313,7 +1324,7 @@ class backup # default exclusions $backup_excludes = array( - escapeshellarg('./backup\*'), + './backup*', './bin', './dev', './etc', './lib', './lib32', './lib64', './opt', './sys', './usr', './var', './proc', './run', './tmp', );