function restoreLocal($backupSnapshot, $path)
 {
     $this->validate($backupSnapshot);
     // Sanitise the path
     if ($path == '/' || strlen($path) == 0) {
         throw new Exception('backupRestorer->restoreLocal: ' . "Error: Detected unsafe path to restore to - {$path}");
     }
     // Now check that it exists - if not try create it
     if (!file_exists($path)) {
         if (!mkdir($path)) {
             throw new Exception('backupRestorer->restoreLocal: ' . "Error: Attempted to create directory for restore, but failed -- {$path}");
         }
         // Set permissions to be rwx owner and rx group.
         if (!@chmod($path, 0750)) {
             throw new Exception('backupRestorer->restoreLocal: ' . "Error: Attempted to change permissions for newly created restore path, but failed -- {$path}");
         }
     } else {
         if (!is_dir($path)) {
             throw new Exception('backupRestorer->restoreLocal: ' . "Error: Specified path location is not a directory -- {$path}");
         }
     }
     if (!is_writeable($path)) {
         throw new Exception('backupRestorer->restoreLocal: ' . "Error: The specified path location is no writeable -- {$path}");
     }
     // Strip trailing space (and spaces if there are any for some weird reason
     $path = rtrim($path, '/ ');
     switch (get_class($backupSnapshot)) {
         // Handle a regular backupSnapshot class
         case 'backupSnapshot':
             // Get the group for the backup and find the seed for that group.
             $group = $backupSnapshot->getSnapshotGroup();
             $scheduledBackup = $backupSnapshot->getScheduledBackup();
             // Find the seed of the backupSnapshot
             $seedSnapshot = $group->getSeed();
             // Copy the seed to the right place
             $seedPath = $seedSnapshot->getPath();
             if ($seedSnapshot->id == $backupSnapshot->id) {
                 $msg = 'Copying snapshot from ' . $seedPath . ' to path ' . $path;
             } else {
                 $msg = 'Copying seed snapshot from ' . $seedPath . ' to path ' . $path;
             }
             $this->infolog->write($msg, XBM_LOG_INFO);
             $copyCommand = 'cp -R ' . $seedPath . '/* ' . $path . '/';
             exec($copyCommand, $output, $returnVar);
             if ($returnVar != 0) {
                 throw new Exception('backupRestorer->restoreLocal: ' . "Error: Failed to copy from {$seedPath} to path {$path} using command: {$copyCommand} -- Got output:\n" . implode("\n", $output));
             }
             $this->infolog->write('Done copying.', XBM_LOG_INFO);
             // If the seed is what we wanted to restore, we're done!
             if ($seedSnapshot->id == $backupSnapshot->id) {
                 return true;
             }
             $xbBinary = $scheduledBackup->getXtraBackupBinary();
             // Proceed with applying any incrementals needed to get to the snapshot we want!
             $snapshotMerger = new backupSnapshotMerger();
             $parentSnapshot = $seedSnapshot;
             do {
                 $deltaSnapshot = $parentSnapshot->getChild();
                 $deltaPath = $deltaSnapshot->getPath();
                 $this->infolog->write('Merging incremental snapshot deltas from ' . $deltaPath . ' into path ' . $path, XBM_LOG_INFO);
                 try {
                     $snapshotMerger->mergePaths($path, $deltaPath, $xbBinary);
                 } catch (MergeException $mergeEx) {
                     throw new MergeException($mergeEx->getMessage(), $mergeEx->getErrorMessage(), $deltaSnapshot);
                 }
                 $this->infolog->write('Done merging.', XBM_LOG_INFO);
                 $parentSnapshot = $deltaSnapshot;
             } while ($deltaSnapshot->id != $backupSnapshot->id);
             break;
         case 'materializedSnapshot':
             $snapPath = $backupSnapshot->getPath();
             $msg = 'Copying snapshot from ' . $snapPath . ' to path ' . $path;
             $this->infolog->write($msg, XBM_LOG_INFO);
             $copyCommand = 'cp -R ' . $snapPath . '/* ' . $path . '/';
             exec($copyCommand, $output, $returnVar);
             if ($returnVar != 0) {
                 throw new Exception('backupRestorer->restoreLocal: ' . "Error: Failed to copy from {$snapPath} to path {$path} using command: {$copyCommand} -- Got output:\n" . implode("\n", $output));
             }
             $this->infolog->write('Done copying.', XBM_LOG_INFO);
             break;
         default:
             throw new ProcessingException('backupRestorer->restoreLocal: ' . "Error: Unrecognized class of backup snapshot to restore.");
             break;
     }
     $this->infolog->write('Local restore to ' . $path . ' complete!', XBM_LOG_INFO);
     return true;
 }
 function materializeLatest($scheduledBackup = false)
 {
     global $config;
     // Validate
     if ($scheduledBackup === false || !is_object($scheduledBackup)) {
         throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Expected a scheduledBackup object to be passed as a parameter, but did not get one.");
     }
     // Find the latest backup snapshot for the scheduledBackup
     $snapshotGroups = $scheduledBackup->getSnapshotGroupsNewestToOldest();
     if (sizeOf($snapshotGroups) == 0) {
         throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Expected to find at least one snapshot group for the scheduledBackup, but got none.");
     }
     // Get the latest snapshot and its info..
     $latestSnapshot = $snapshotGroups[0]->getMostRecentCompletedBackupSnapshot();
     $latestSnapInfo = $latestSnapshot->getInfo();
     $latestSnapPath = $latestSnapshot->getPath();
     // Attempt to get the materialized snapshot for the scheduled Backup into "prevMaterialized"
     $prevMaterialized = $scheduledBackup->getMostRecentCompletedMaterializedSnapshot();
     // if we DO get one then we need to create a new materializedSnapshot
     if ($prevMaterialized != false) {
         // Set state to UPDATING
         $prevMaterialized->setStatus('UPDATING');
         // Get the backup snapshot that the prevMaterialized is linked to
         $prevSnapshot = $prevMaterialized->getBackupSnapshot();
         // and its info..
         $prevSnapInfo = $prevSnapshot->getInfo();
         $prevSnapPath = $prevSnapshot->getPath();
         // if the materialized is already the latest
         if ($latestSnapshot->id == $prevSnapshot->id) {
             // Just exit - nothing to do.
             return true;
         }
         // otherwise proceed - initialize a new materializedSnapshot
         $newMaterialized = new materializedSnapshot();
         $newMaterialized->setLogStream($this->log);
         try {
             $newMaterialized->init($scheduledBackup, $latestSnapshot);
             $materialPath = $newMaterialized->getPath();
             // Is the group for the materializedSnapshot the same as the latest snapshot?
             if ($latestSnapInfo['snapshot_group_num'] == $prevSnapInfo['snapshot_group_num']) {
                 // if yes - is the backupSnapshot the prevMaterialized is linked to a SEED?
                 if ($prevSnapInfo['type'] == 'SEED') {
                     // if yes then --
                     // we need to copy that seed to the new path first
                     $copyCommand = 'cp -R ' . $prevSnapPath . '/* ' . $materialPath . '/';
                     exec($copyCommand, $output, $returnVar);
                     if ($returnVar != 0) {
                         throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Failed to copy from {$prevSnapPath} to path {$materialPath} using command: {$copyCommand} -- Got output:\n" . implode("\n", $output));
                     }
                 } else {
                     // If the prevSnapshot is not a SEED then we need to move it to $materialPath
                     if (!rename($prevMaterialized->getPath(), $materialPath)) {
                         throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: Failed to move {$prevSnapPath} to {$materialPath}.");
                     }
                 }
                 // now just roll forward, applying deltas step by step until we reach the current backup
                 $xbBinary = $scheduledBackup->getXtraBackupBinary();
                 $snapshotMerger = new backupSnapshotMerger();
                 $parentSnapshot = $prevSnapshot;
                 do {
                     $deltaSnapshot = $parentSnapshot->getChild();
                     $deltaPath = $deltaSnapshot->getPath();
                     $this->infolog->write('Merging incremental snapshot deltas from ' . $deltaPath . ' into path ' . $materialPath, XBM_LOG_INFO);
                     try {
                         $snapshotMerger->mergePaths($materialPath, $deltaPath, $xbBinary);
                     } catch (MergeException $mergeEx) {
                         // This snapshot merge failed during materialize
                         // We should remove the bad snapshot and it's children
                         // Get the group for the delta that just failed
                         $group = $deltaSnapshot->getSnapshotGroup();
                         // Get the snaps in the group from newest to oldest
                         $snapshotsForGroup = $group->getAllSnapshotsNewestToOldest();
                         // Iterate over them newest to oldest, destroying them, until we meet the one that failed
                         foreach ($snapshotsForGroup as $snap) {
                             $snap->destroy();
                             if ($snap->id == $deltaSnapshot->id) {
                                 break;
                             }
                         }
                         throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: An error occurred while trying to materialze the latest snapshot. A set of deltas" . " could not be merged successfully.\nThe offending incremental backup and any other backup snapshots that depend upon it have been deleted.\n" . $mergeEx->getErrorMessage());
                     }
                     $this->infolog->write('Done merging.', XBM_LOG_INFO);
                     $parentSnapshot = $deltaSnapshot;
                 } while ($deltaSnapshot->id != $latestSnapshot->id);
             } else {
                 // if groups for snapshot differ, is the backupSnapshot that we need to update to a SEED?
                 if ($latestSnapInfo['type'] == 'SEED') {
                     // if yes, just create the symlink
                     $newMaterialized->symlinkToSnapshot($latestSnapshot);
                 } else {
                     // if no, copy the SEED for the snapshotGroup of the new snapshot to the new path, then apply deltas
                     $backupRestorer = new backupRestorer();
                     $backupRestorer->setInfoLogStream($this->infolog);
                     $backupRestorer->setLogStream($this->log);
                     try {
                         $backupRestorer->restoreLocal($latestSnap, $materialPath);
                     } catch (MergeException $mergeEx) {
                         // This snapshot merge failed during materialize
                         // We should remove the bad snapshot and it's children
                         $failSnap = $mergeEx->getFailedSnapshot();
                         // Get the group for the delta that just failed
                         $group = $failSnap->getSnapshotGroup();
                         // Get the snaps in the group from newest to oldest
                         $snapshotsForGroup = $group->getAllSnapshotsNewestToOldest();
                         // Iterate over them newest to oldest, destroying them, until we meet the one that failed
                         foreach ($snapshotsForGroup as $snap) {
                             $snap->destroy();
                             if ($snap->id == $failSnap->id) {
                                 break;
                             }
                         }
                         throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: An error occurred while trying to materialze the latest snapshot. A set of deltas" . " could not be merged successfully.\nThe offending incremental backup and any other backup snapshots that depend upon it have been deleted.\n" . $mergeEx->getErrorMessage());
                     }
                 }
             }
             // destroy the old snapshot
             $prevMaterialized->destroy();
         } catch (Exception $e) {
             $prevMaterialized->destroy();
             $newMaterialized->setStatus('FAILED');
             if ($config['SYSTEM']['cleanup_on_failure'] == true) {
                 $newMaterialized->deleteFiles();
             }
             throw $e;
         }
     } else {
         // If no previous materialized snapshot exists at all..
         // Initialize a new materialized snapshot
         $newMaterialized = new materializedSnapshot();
         $newMaterialized->setLogStream($this->log);
         try {
             $newMaterialized->init($scheduledBackup, $latestSnapshot);
             $materialPath = $newMaterialized->getPath();
             // if we do not get one then check if the snapshot is a SEED
             if ($latestSnapInfo['type'] == 'SEED') {
                 // if yes - just create a symlink to it
                 $newMaterialized->symlinkToSnapshot($latestSnapshot);
             } else {
                 // if no - use the regular restore function to restore it to the target path
                 $backupRestorer = new backupRestorer();
                 $backupRestorer->setInfoLogStream($this->infolog);
                 $backupRestorer->setLogStream($this->log);
                 try {
                     $backupRestorer->restoreLocal($latestSnapshot, $materialPath);
                 } catch (MergeException $mergeEx) {
                     // This snapshot merge failed during materialize
                     // We should remove the bad snapshot and it's children
                     $failSnap = $mergeEx->getFailedSnapshot();
                     // Get the group for the delta that just failed
                     $group = $failSnap->getSnapshotGroup();
                     // Get the snaps in the group from newest to oldest
                     $snapshotsForGroup = $group->getAllSnapshotsNewestToOldest();
                     // Iterate over them newest to oldest, destroying them, until we meet the one that failed
                     foreach ($snapshotsForGroup as $snap) {
                         $snap->destroy();
                         if ($snap->id == $failSnap->id) {
                             break;
                         }
                     }
                     throw new Exception('materializedSnapshotManager->materializeLatest: ' . "Error: An error occurred while trying to materialze the latest snapshot. A set of deltas" . " could not be merged successfully.\nThe offending incremental backup and any other backup snapshots that depend upon it have been deleted.\n" . $mergeEx->getErrorMessage());
                 }
             }
         } catch (Exception $e) {
             $newMaterialized->setStatus('FAILED');
             if ($config['SYSTEM']['cleanup_on_failure'] == true) {
                 $newMaterialized->deleteFiles();
             }
             throw $e;
         }
     }
     $newMaterialized->setStatus('COMPLETED');
 }
 function applyRetentionPolicy(backupJob $job)
 {
     global $config;
     $scheduledBackup = $job->getScheduledBackup();
     $this->infolog->write("Checking to see if any snapshots need to be merged into the seed backup.", XBM_LOG_INFO);
     if (!is_object($scheduledBackup)) {
         throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: This function requires a scheduledBackup object as a parameter.");
     }
     $conn = dbConnection::getInstance($this->log);
     $sql = "SELECT backup_snapshot_id FROM backup_snapshots WHERE status='COMPLETED' AND scheduled_backup_id=" . $scheduledBackup->id . " AND snapshot_time IS NOT NULL ORDER BY snapshot_time ASC";
     if (!($res = $conn->query($sql))) {
         throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}");
     }
     // Get info for this scheduledBackup
     $info = $scheduledBackup->getInfo();
     // Get the params/options for this scheduledBackup
     $params = $scheduledBackup->getParameters();
     // Validate them
     $this->validateParams($params);
     // Build service objects for later use
     $snapshotGetter = new backupSnapshotGetter();
     $snapshotMerger = new backupSnapshotMerger();
     // Check to see if the number of rows we have is more than the number of snapshots we should have at a max
     while ($res->num_rows > $params['max_snapshots']) {
         // Grab the first row - it is the SEED
         if (!($row = $res->fetch_array())) {
             throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Could not retrieve the object ID for the seed of Scheduled Backup ID " . $scheduledBackup->id);
         }
         $seedSnapshot = $snapshotGetter->getById($row['backup_snapshot_id']);
         // Grab the second row - it is the DELTA to be collapsed.
         if (!($row = $res->fetch_array())) {
             throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Could not retrieve the object ID for the seed of Scheduled Backup ID " . $scheduledBackup->id);
         }
         $deltaSnapshot = $snapshotGetter->getById($row['backup_snapshot_id']);
         $this->infolog->write("Merging deltas in Backup Snapshot ID #" . $deltaSnapshot->id . " with Backup Snapshot ID #" . $seedSnapshot->id . ".", XBM_LOG_INFO);
         // Merge them together
         $snapshotMerger->mergeSnapshots($seedSnapshot, $deltaSnapshot);
         // Check to see what merge work is needed now.
         $sql = "SELECT backup_snapshot_id FROM backup_snapshots WHERE status='COMPLETED' AND scheduled_backup_id=" . $scheduledBackup->id . " AND snapshot_time IS NOT NULL ORDER BY snapshot_time ASC";
         if (!($res = $conn->query($sql))) {
             throw new Exception('continuousIncrementalBackupTaker->applyRetentionPolicy: ' . "Error: Query: {$sql} \nFailed with MySQL Error: {$conn->error}");
         }
     }
     return true;
 }