Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Cannot retrieve contributors at this time

1677 lines (1470 sloc) 54.856 kb
<?php
/*
Copyright 2009-2011 Guillaume Boudreau, Andrew Hopkinson
This file is part of Greyhole.
Greyhole is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Greyhole is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Greyhole. If not, see <http://www.gnu.org/licenses/>.
*/
define('PERF', 9);
define('TEST', 8);
define('DEBUG', 7);
define('INFO', 6);
define('WARN', 4);
define('ERROR', 3);
define('CRITICAL', 2);
$action = 'initialize';
date_default_timezone_set(date_default_timezone_get());
set_error_handler("gh_error_handler");
register_shutdown_function("gh_shutdown");
umask(0);
setlocale(LC_COLLATE, "en_US.UTF-8");
setlocale(LC_CTYPE, "en_US.UTF-8");
if (!defined('PHP_VERSION_ID')) {
$version = explode('.', PHP_VERSION);
define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2]));
}
$constarray = get_defined_constants(true);
foreach($constarray['user'] as $key => $val) {
eval(sprintf('$_CONSTANTS[\'%s\'] = ' . (is_int($val) || is_float($val) ? '%s' : "'%s'") . ';', addslashes($key), addslashes($val)));
}
// Cached df results
$last_df_time = 0;
$last_dfs = array();
$sleep_before_task = array();
if (!isset($config_file)) {
$config_file = '/etc/greyhole.conf';
}
if (!isset($smb_config_file)) {
$smb_config_file = '/etc/samba/smb.conf';
}
$trash_share_names = array('Greyhole Attic', 'Greyhole Trash', 'Greyhole Recycle Bin');
function recursive_include_parser($file) {
$regex = '/^[ \t]*include[ \t]*=[ \t]*([^#\r\n]+)/im';
$ok_to_execute = FALSE;
if (is_array($file) && count($file) > 1) {
$file = $file[1];
}
$file = trim($file);
if (file_exists($file)) {
if (is_executable($file)) {
$perms = fileperms($file);
// Not user-writable, or owned by root
$ok_to_execute = !($perms & 0x0080) || fileowner($file) === 0;
// Not group-writable, or group owner is root
$ok_to_execute &= !($perms & 0x0010) || filegroup($file) === 0;
// Not world-writable
$ok_to_execute &= !($perms & 0x0002);
if (!$ok_to_execute) {
gh_log(WARN, "Config file '{$file}' is executable but file permissions are insecure, only the file's contents will be included.");
}
}
$contents = $ok_to_execute ? shell_exec(escapeshellcmd($file)) : file_get_contents($file);
return preg_replace_callback($regex, 'recursive_include_parser', $contents);
} else {
return false;
}
}
function parse_config() {
global $_CONSTANTS, $log_level, $storage_pool_drives, $shares_options, $minimum_free_space_pool_drives, $df_command, $config_file, $smb_config_file, $sticky_files, $db_options, $frozen_directories, $trash_share_names, $max_queued_tasks, $memory_limit, $delete_moves_to_trash, $greyhole_log_file, $email_to, $log_memory_usage, $check_for_open_files, $allow_multiple_sp_per_device, $df_cache_time;
$deprecated_options = array(
'delete_moves_to_attic' => 'delete_moves_to_trash',
'storage_pool_directory' => 'storage_pool_drive',
'dir_selection_groups' => 'drive_selection_groups',
'dir_selection_algorithm' => 'drive_selection_algorithm'
);
$parsing_drive_selection_groups = FALSE;
$shares_options = array();
$storage_pool_drives = array();
$frozen_directories = array();
$config_text = recursive_include_parser($config_file);
// Defaults
$log_level = DEBUG;
$greyhole_log_file = '/var/log/greyhole.log';
$email_to = 'root';
$log_memory_usage = FALSE;
$check_for_open_files = TRUE;
$allow_multiple_sp_per_device = FALSE;
$df_cache_time = 15;
$delete_moves_to_trash = TRUE;
$memory_limit = '128M';
foreach (explode("\n", $config_text) as $line) {
if (preg_match("/^[ \t]*([^=\t]+)[ \t]*=[ \t]*([^#]+)/", $line, $regs)) {
$name = trim($regs[1]);
$value = trim($regs[2]);
if ($name[0] == '#') {
continue;
}
foreach ($deprecated_options as $old_name => $new_name) {
if (mb_strpos($name, $old_name) !== FALSE) {
$fixed_name = str_replace($old_name, $new_name, $name);
#gh_log(WARN, "Deprecated option found in greyhole.conf: $name. You should change that to: $fixed_name");
$name = $fixed_name;
}
}
$parsing_drive_selection_groups = FALSE;
switch($name) {
case 'log_level':
$log_level = $_CONSTANTS[$value];
break;
case 'delete_moves_to_trash': // or delete_moves_to_attic
case 'log_memory_usage':
case 'check_for_open_files':
case 'allow_multiple_sp_per_device':
global ${$name};
${$name} = trim($value) === '1' || mb_stripos($value, 'yes') !== FALSE || mb_stripos($value, 'true') !== FALSE;
break;
case 'storage_pool_drive': // or storage_pool_directory
if (preg_match("/(.*) ?, ?min_free ?: ?([0-9]+) ?([gmk])b?/i", $value, $regs)) {
$storage_pool_drives[] = trim($regs[1]);
if (strtolower($regs[3]) == 'g') {
$minimum_free_space_pool_drives[trim($regs[1])] = (float) trim($regs[2]);
} else if (strtolower($regs[3]) == 'm') {
$minimum_free_space_pool_drives[trim($regs[1])] = (float) trim($regs[2]) / 1024.0;
} else if (strtolower($regs[3]) == 'k') {
$minimum_free_space_pool_drives[trim($regs[1])] = (float) trim($regs[2]) / 1024.0 / 1024.0;
}
}
break;
case 'wait_for_exclusive_file_access':
$shares = explode(',', str_replace(' ', '', $value));
foreach ($shares as $share) {
$shares_options[$share]['wait_for_exclusive_file_access'] = TRUE;
}
break;
case 'sticky_files':
$last_sticky_files_dir = trim($value, '/');
$sticky_files[$last_sticky_files_dir] = array();
break;
case 'stick_into':
$sticky_files[$last_sticky_files_dir][] = '/' . trim($value, '/');
break;
case 'frozen_directory':
$frozen_directories[] = trim($value, '/');
break;
case 'memory_limit':
ini_set('memory_limit',$value);
$memory_limit = $value;
break;
case 'drive_selection_groups': // or dir_selection_groups
if (preg_match("/(.+):(.*)/", $value, $regs)) {
global $drive_selection_groups;
$group_name = trim($regs[1]);
$drive_selection_groups[$group_name] = array_map('trim', explode(',', $regs[2]));
$parsing_drive_selection_groups = TRUE;
}
break;
case 'drive_selection_algorithm': // or dir_selection_algorithm
global $drive_selection_algorithm;
$drive_selection_algorithm = DriveSelection::parse($value, @$drive_selection_groups);
break;
default:
if (mb_strpos($name, 'num_copies') === 0) {
$share = mb_substr($name, 11, mb_strlen($name)-12);
if (mb_stripos($value, 'max') === 0) {
$value = 9999;
}
$shares_options[$share]['num_copies'] = (int) $value;
} else if (mb_strpos($name, 'delete_moves_to_trash') === 0) {
$share = mb_substr($name, 22, mb_strlen($name)-23);
$shares_options[$share]['delete_moves_to_trash'] = trim($value) === '1' || mb_strpos(strtolower(trim($value)), 'yes') !== FALSE || mb_strpos(strtolower(trim($value)), 'true') !== FALSE;
} else if (mb_strpos($name, 'drive_selection_groups') === 0) { // or dir_selection_groups
$share = mb_substr($name, 23, mb_strlen($name)-24);
if (preg_match("/(.+):(.+)/", $value, $regs)) {
$group_name = trim($regs[1]);
$shares_options[$share]['drive_selection_groups'][$group_name] = array_map('trim', explode(',', $regs[2]));
$parsing_drive_selection_groups = $share;
}
} else if (mb_strpos($name, 'drive_selection_algorithm') === 0) { // or dir_selection_algorithm
$share = mb_substr($name, 26, mb_strlen($name)-27);
if (!isset($shares_options[$share]['drive_selection_groups'])) {
$shares_options[$share]['drive_selection_groups'] = @$drive_selection_groups;
}
$shares_options[$share]['drive_selection_algorithm'] = DriveSelection::parse($value, $shares_options[$share]['drive_selection_groups']);
} else {
global ${$name};
if (is_numeric($value)) {
${$name} = (int) $value;
} else {
${$name} = $value;
}
}
}
} else if ($parsing_drive_selection_groups !== FALSE) {
$value = trim($line);
if (strlen($value) == 0 || $value[0] == '#') {
continue;
}
if (preg_match("/(.+):(.+)/", $value, $regs)) {
$group_name = trim($regs[1]);
$drives = array_map('trim', explode(',', $regs[2]));
if (is_string($parsing_drive_selection_groups)) {
$share = $parsing_drive_selection_groups;
$shares_options[$share]['drive_selection_groups'][$group_name] = $drives;
} else {
$drive_selection_groups[$group_name] = $drives;
}
}
}
}
if (is_array($storage_pool_drives) && count($storage_pool_drives) > 0) {
$df_command = "df -k";
foreach ($storage_pool_drives as $key => $sp_drive) {
$df_command .= " " . escapeshellarg($sp_drive);
$storage_pool_drives[$key] = '/' . trim($sp_drive, '/');
}
$df_command .= " 2>&1 | grep '%' | grep -v \"^df: .*: No such file or directory$\"";
} else {
gh_log(WARN, "You have no storage_pool_drive defined. Greyhole can't run.");
return FALSE;
}
exec('testparm -s ' . escapeshellarg($smb_config_file) . ' 2> /dev/null', $config_text);
foreach ($config_text as $line) {
$line = trim($line);
if (mb_strlen($line) == 0) { continue; }
if ($line[0] == '[' && preg_match('/\[([^\]]+)\]/', $line, $regs)) {
$share_name = $regs[1];
}
if (isset($share_name) && !isset($shares_options[$share_name]) && array_search($share_name, $trash_share_names) === FALSE) { continue; }
if (isset($share_name) && preg_match('/^\s*path[ \t]*=[ \t]*(.+)$/i', $line, $regs)) {
$shares_options[$share_name]['landing_zone'] = '/' . trim($regs[1], '/');
$shares_options[$share_name]['name'] = $share_name;
}
}
global $drive_selection_algorithm;
if (isset($drive_selection_algorithm)) {
foreach ($drive_selection_algorithm as $ds) {
$ds->update();
}
} else {
// Default drive_selection_algorithm
$drive_selection_algorithm = DriveSelection::parse('most_available_space', null);
}
foreach ($shares_options as $share_name => $share_options) {
if (array_search($share_name, $trash_share_names) !== FALSE) {
global $trash_share;
$trash_share = array('name' => $share_name, 'landing_zone' => $shares_options[$share_name]['landing_zone']);
unset($shares_options[$share_name]);
continue;
}
if ($share_options['num_copies'] > count($storage_pool_drives)) {
$share_options['num_copies'] = count($storage_pool_drives);
}
if (!isset($share_options['landing_zone'])) {
global $config_file, $smb_config_file;
gh_log(WARN, "Found a share ($share_name) defined in $config_file with no path in $smb_config_file. Either add this share in $smb_config_file, or remove it from $config_file, then restart Greyhole.");
return FALSE;
}
if (!isset($share_options['delete_moves_to_trash'])) {
$share_options['delete_moves_to_trash'] = $delete_moves_to_trash;
}
if (isset($share_options['drive_selection_algorithm'])) {
foreach ($share_options['drive_selection_algorithm'] as $ds) {
$ds->update();
}
} else {
$share_options['drive_selection_algorithm'] = $drive_selection_algorithm;
}
if (isset($share_options['drive_selection_groups'])) {
unset($share_options['drive_selection_groups']);
}
$shares_options[$share_name] = $share_options;
// Validate that the landing zone is NOT a subdirectory of a storage pool drive!
foreach ($storage_pool_drives as $key => $sp_drive) {
if (mb_strpos($share_options['landing_zone'], $sp_drive) === 0) {
gh_log(CRITICAL, "Found a share ($share_name), with path " . $share_options['landing_zone'] . ", which is INSIDE a storage pool drive ($sp_drive). Share directories should never be inside a directory that you have in your storage pool.\nFor your shares to use your storage pool, you just need them to have 'vfs objects = greyhole' in their (smb.conf) config; their location on your file system is irrelevant.");
}
}
}
if (!isset($db_engine)) {
$db_engine = 'mysql';
} else {
$db_engine = mb_strtolower($db_engine);
}
global ${"db_use_$db_engine"};
${"db_use_$db_engine"} = TRUE;
if (!isset($max_queued_tasks)) {
if ($db_engine == 'sqlite') {
$max_queued_tasks = 1000;
} else {
$max_queued_tasks = 10000000;
}
}
if (!isset($memory_limit)) {
ini_set('memory_limit', $memory_limit);
}
if (isset($memory_limit)) {
if(preg_match('/M$/',$memory_limit)) {
$memory_limit = preg_replace('/M$/','',$memory_limit);
$memory_limit = $memory_limit * 1048576;
}elseif(preg_match('/K$/',$memory_limit)) {
$memory_limit = preg_replace('/K$/','',$memory_limit);
$memory_limit = $memory_limit * 1024;
}
}
$db_options = (object) array(
'engine' => $db_engine,
'schema' => "/usr/share/greyhole/schema-$db_engine.sql"
);
if ($db_options->engine == 'sqlite') {
$db_options->db_path = $db_path;
$db_options->dbh = null; // internal handle to use with sqlite
} else {
$db_options->host = $db_host;
$db_options->user = $db_user;
$db_options->pass = $db_pass;
$db_options->name = $db_name;
}
include('includes/sql.php');
if (strtolower($greyhole_log_file) == 'syslog') {
openlog("Greyhole", LOG_PID, LOG_USER);
}
return TRUE;
}
function clean_dir($dir) {
if ($dir[0] == '.' && $dir[1] == '/') {
$dir = mb_substr($dir, 2);
}
while (mb_strpos($dir, '//') !== FALSE) {
$dir = str_replace("//", "/", $dir);
}
return $dir;
}
function explode_full_path($full_path) {
return array(dirname($full_path), basename($full_path));
}
function gh_log($local_log_level, $text) {
global $greyhole_log_file, $log_level, $log_memory_usage, $action, $log_to_stdout;
if ($local_log_level > $log_level) {
return;
}
$date = date("M d H:i:s");
if ($log_level >= PERF) {
$utimestamp = microtime(true);
$timestamp = floor($utimestamp);
$date .= '.' . round(($utimestamp - $timestamp) * 1000000);
}
$log_text = sprintf("%s%s%s\n",
"$date $local_log_level $action: ",
$text,
@$log_memory_usage ? " [" . memory_get_usage() . "]" : ''
);
if (isset($log_to_stdout)) {
echo $log_text;
} else {
if (strtolower($greyhole_log_file) == 'syslog') {
$worked = syslog($local_log_level, $log_text);
} else if (!empty($greyhole_log_file)) {
$worked = error_log($log_text, 3, $greyhole_log_file);
} else {
$worked = FALSE;
}
if (!$worked) {
error_log(trim($log_text));
}
}
if ($local_log_level === CRITICAL) {
exit(1);
}
}
function gh_shutdown() {
if ($err = error_get_last()) {
gh_log(ERROR, "PHP Fatal Error: " . $err['message'] . "; BT: " . basename($err['file']) . '[L' . $err['line'] . '] ');
}
}
function gh_error_handler($errno, $errstr, $errfile, $errline, $errcontext) {
if (error_reporting() === 0) {
// Ignored (@) warning
return TRUE;
}
switch ($errno) {
case E_ERROR:
case E_PARSE:
case E_CORE_ERROR:
case E_COMPILE_ERROR:
gh_log(CRITICAL, "PHP Error [$errno]: $errstr in $errfile on line $errline");
break;
case E_WARNING:
case E_COMPILE_WARNING:
case E_CORE_WARNING:
case E_NOTICE:
global $greyhole_log_file;
if ($errstr == "fopen($greyhole_log_file): failed to open stream: Permission denied") {
// We want to ignore this warning. Happens when regular users try to use greyhole, and greyhole tries to log something.
// What would have been logged will be echoed instead.
return TRUE;
}
gh_log(WARN, "PHP Warning [$errno]: $errstr in $errfile on line $errline; BT: " . get_debug_bt());
break;
default:
gh_log(WARN, "PHP Unknown Error [$errno]: $errstr in $errfile on line $errline");
break;
}
// Don't execute PHP internal error handler
return TRUE;
}
function get_debug_bt() {
$bt = '';
foreach (debug_backtrace() as $d) {
if ($d['function'] == 'gh_error_handler' || $d['function'] == 'get_debug_bt') { continue; }
if ($bt != '') {
$bt = " => $bt";
}
$prefix = '';
if (isset($d['file'])) {
$prefix = basename($d['file']) . '[L' . $d['line'] . '] ';
}
foreach ($d['args'] as $k => $v) {
if (is_object($v)) {
$d['args'][$k] = 'stdClass';
}
if (is_array($v)) {
$d['args'][$k] = str_replace("\n", "", var_export($v, TRUE));
}
}
$bt = $prefix . $d['function'] .'(' . implode(',', $d['args']) . ')' . $bt;
}
return $bt;
}
function bytes_to_human($bytes, $html=TRUE) {
$units = 'B';
if (abs($bytes) > 1024) {
$bytes /= 1024;
$units = 'KB';
}
if (abs($bytes) > 1024) {
$bytes /= 1024;
$units = 'MB';
}
if (abs($bytes) > 1024) {
$bytes /= 1024;
$units = 'GB';
}
if (abs($bytes) > 1024) {
$bytes /= 1024;
$units = 'TB';
}
$decimals = (abs($bytes) > 100 ? 0 : (abs($bytes) > 10 ? 1 : 2));
if ($html) {
return number_format($bytes, $decimals) . " <span class=\"i18n-$units\">$units</span>";
} else {
return number_format($bytes, $decimals) . $units;
}
}
function duration_to_human($seconds) {
$displayable_duration = '';
if ($seconds > 60*60) {
$hours = floor($seconds / (60*60));
$displayable_duration .= $hours . 'h ';
$seconds -= $hours * (60*60);
}
if ($seconds > 60) {
$minutes = floor($seconds / 60);
$displayable_duration .= $minutes . 'm ';
$seconds -= $minutes * 60;
}
$displayable_duration .= $seconds . 's';
return $displayable_duration;
}
function get_share_landing_zone($share) {
global $shares_options, $trash_share_names;
if (isset($shares_options[$share]['landing_zone'])) {
return $shares_options[$share]['landing_zone'];
} else if (array_search($share, $trash_share_names) !== FALSE) {
global $trash_share;
return $trash_share['landing_zone'];
} else {
global $config_file, $smb_config_file;
gh_log(WARN, " Found a share ($share) with no path in $smb_config_file, or missing it's num_copies[$share] config in $config_file. Skipping.");
return FALSE;
}
}
$arch = exec('uname -m');
if ($arch != 'x86_64') {
gh_log(DEBUG, "32-bit system detected: Greyhole will NOT use PHP built-in file functions.");
function gh_filesize($filename) {
$result = exec("stat -c %s ".escapeshellarg($filename)." 2>/dev/null");
if (empty($result)) {
return FALSE;
}
return (float) $result;
}
function gh_fileowner($filename) {
$result = exec("stat -c %u ".escapeshellarg($filename)." 2>/dev/null");
if (empty($result)) {
return FALSE;
}
return (int) $result;
}
function gh_filegroup($filename) {
$result = exec("stat -c %g ".escapeshellarg($filename)." 2>/dev/null");
if (empty($result)) {
return FALSE;
}
return (int) $result;
}
function gh_fileperms($filename) {
$result = exec("stat -c %a ".escapeshellarg($filename)." 2>/dev/null");
if (empty($result)) {
return FALSE;
}
return "0" . $result;
}
function gh_is_file($filename) {
exec('[ -f '.escapeshellarg($filename).' ]', $tmp, $result);
return $result === 0;
}
function gh_fileinode($filename) {
// This function returns deviceid_inode to make sure this value will be different for files on different devices.
$result = exec("stat -c '%d_%i' ".escapeshellarg($filename)." 2>/dev/null");
if (empty($result)) {
return FALSE;
}
return (string) $result;
}
function gh_file_deviceid($filename) {
$result = exec("stat -c '%d' ".escapeshellarg($filename)." 2>/dev/null");
if (empty($result)) {
return FALSE;
}
return (string) $result;
}
function gh_rename($filename, $target_filename) {
exec("mv ".escapeshellarg($filename)." ".escapeshellarg($target_filename)." 2>/dev/null", $output, $result);
return $result === 0;
}
} else {
gh_log(DEBUG, "64-bit system detected: Greyhole will use PHP built-in file functions.");
function gh_filesize($filename) {
return filesize($filename);
}
function gh_fileowner($filename) {
return fileowner($filename);
}
function gh_filegroup($filename) {
return filegroup($filename);
}
function gh_fileperms($filename) {
return mb_substr(decoct(fileperms($filename)), -4);
}
function gh_is_file($filename) {
return is_file($filename);
}
function gh_fileinode($filename) {
// This function returns deviceid_inode to make sure this value will be different for files on different devices.
$stat = @stat($filename);
if ($stat === FALSE) {
return FALSE;
}
return $stat['dev'] . '_' . $stat['ino'];
}
function gh_file_deviceid($filename) {
$stat = @stat($filename);
if ($stat === FALSE) {
return FALSE;
}
return $stat['dev'];
}
function gh_rename($filename, $target_filename) {
return @rename($filename, $target_filename);
}
}
function memory_check() {
global $memory_limit;
$usage = memory_get_usage();
$used = $usage/$memory_limit;
$used = $used * 100;
if ($used > 95) {
gh_log(CRITICAL, $used . '% memory usage, exiting. Please increase memory_limit in /etc/greyhole.conf');
}
}
class metafile_iterator implements Iterator {
private $path;
private $share;
private $load_nok_metafiles;
private $quiet;
private $check_symlink;
private $metafiles;
private $metastores;
private $dir_handle;
public function __construct($share, $path, $load_nok_metafiles=FALSE, $quiet=FALSE, $check_symlink=TRUE) {
$this->quiet = $quiet;
$this->share = $share;
$this->path = $path;
$this->check_symlink = $check_symlink;
$this->load_nok_metafiles = $load_nok_metafiles;
}
public function rewind() {
$this->metastores = get_metastores();
$this->directory_stack = array($this->path);
$this->dir_handle = NULL;
$this->metafiles = array();
$this->next();
}
public function current() {
return $this->metafiles;
}
public function key() {
return count($this->metafiles);
}
public function next() {
$this->metafiles = array();
while(count($this->directory_stack)>0 && $this->directory_stack !== NULL) {
$this->dir = array_pop($this->directory_stack);
if (!$this->quiet) {
gh_log(DEBUG, "Loading metadata files for (dir) " . clean_dir($this->share . (!empty($this->dir) ? "/" . $this->dir : "")) . " ...");
}
for( $i = 0; $i < count($this->metastores); $i++ ) {
$metastore = $this->metastores[$i];
$this->base = "$metastore/".$this->share."/";
if(!file_exists($this->base.$this->dir)) {
continue;
}
if($this->dir_handle = opendir($this->base.$this->dir)) {
while (false !== ($file = readdir($this->dir_handle))) {
memory_check();
if($file=='.' || $file=='..')
continue;
if(!empty($this->dir)) {
$full_filename = $this->dir . '/' . $file;
}else
$full_filename = $file;
if(is_dir($this->base.$full_filename))
$this->directory_stack[] = $full_filename;
else{
$full_filename = str_replace("$this->path/",'',$full_filename);
if(isset($this->metafiles[$full_filename])) {
continue;
}
$this->metafiles[$full_filename] = get_metafiles_for_file($this->share, "$this->dir", $file, $this->load_nok_metafiles, $this->quiet, $this->check_symlink);
}
}
closedir($this->dir_handle);
$this->directory_stack = array_unique($this->directory_stack);
}
}
if(count($this->metafiles) > 0) {
break;
}
}
if (!$this->quiet) {
gh_log(DEBUG, 'Found ' . count($this->metafiles) . ' metadata files.');
}
return $this->metafiles;
}
public function valid() {
return count($this->metafiles) > 0;
}
}
function _getopt ( ) {
/* _getopt(): Ver. 1.3 2009/05/30
My page: http://www.ntu.beautifulworldco.com/weblog/?p=526
Usage: _getopt ( [$flag,] $short_option [, $long_option] );
Note that another function split_para() is required, which can be found in the same
page.
_getopt() fully simulates getopt() which is described at
http://us.php.net/manual/en/function.getopt.php , including long options for PHP
version under 5.3.0. (Prior to 5.3.0, long options was only available on few systems)
Besides legacy usage of getopt(), I also added a new option to manipulate your own
argument lists instead of those from command lines. This new option can be a string
or an array such as
$flag = "-f value_f -ab --required 9 --optional=PK --option -v test -k";
or
$flag = array ( "-f", "value_f", "-ab", "--required", "9", "--optional=PK", "--option" );
So there are four ways to work with _getopt(),
1. _getopt ( $short_option );
it's a legacy usage, same as getopt ( $short_option ).
2. _getopt ( $short_option, $long_option );
it's a legacy usage, same as getopt ( $short_option, $long_option ).
3. _getopt ( $flag, $short_option );
use your own argument lists instead of command line arguments.
4. _getopt ( $flag, $short_option, $long_option );
use your own argument lists instead of command line arguments.
*/
if ( func_num_args() == 1 ) {
$flag = $flag_array = $GLOBALS['argv'];
$short_option = func_get_arg ( 0 );
$long_option = array ();
return getopt($short_option);
} else if ( func_num_args() == 2 ) {
if ( is_array ( func_get_arg ( 1 ) ) ) {
$flag = $GLOBALS['argv'];
$short_option = func_get_arg ( 0 );
$long_option = func_get_arg ( 1 );
if (PHP_VERSION_ID >= 50300) { return getopt($short_option, $long_option); }
} else {
$flag = func_get_arg ( 0 );
$short_option = func_get_arg ( 1 );
$long_option = array ();
return getopt($short_option);
}
} else if ( func_num_args() == 3 ) {
$flag = func_get_arg ( 0 );
$short_option = func_get_arg ( 1 );
$long_option = func_get_arg ( 2 );
if (PHP_VERSION_ID >= 50300) { return getopt($short_option, $long_option); }
} else {
exit ( "wrong options\n" );
}
$short_option = trim ( $short_option );
$short_no_value = array();
$short_required_value = array();
$short_optional_value = array();
$long_no_value = array();
$long_required_value = array();
$long_optional_value = array();
$options = array();
for ( $i = 0; $i < strlen ( $short_option ); ) {
if ( $short_option{$i} != ":" ) {
if ( $i == strlen ( $short_option ) - 1 ) {
$short_no_value[] = $short_option{$i};
break;
} else if ( $short_option{$i+1} != ":" ) {
$short_no_value[] = $short_option{$i};
$i++;
continue;
} else if ( $short_option{$i+1} == ":" && $short_option{$i+2} != ":" ) {
$short_required_value[] = $short_option{$i};
$i += 2;
continue;
} else if ( $short_option{$i+1} == ":" && $short_option{$i+2} == ":" ) {
$short_optional_value[] = $short_option{$i};
$i += 3;
continue;
}
} else {
continue;
}
}
foreach ( $long_option as $a ) {
if ( substr( $a, -2 ) == "::" ) {
$long_optional_value[] = substr( $a, 0, -2);
continue;
} else if ( substr( $a, -1 ) == ":" ) {
$long_required_value[] = substr( $a, 0, -1 );
continue;
} else {
$long_no_value[] = $a;
continue;
}
}
if ( is_array ( $flag ) )
$flag_array = $flag;
else {
$flag = "- $flag";
$flag_array = split_para( $flag );
}
for ( $i = 0; $i < count( $flag_array ); ) {
if ( $i >= count ( $flag_array ) )
break;
if ( ! $flag_array[$i] || $flag_array[$i] == "-" ) {
$i++;
continue;
}
if ( $flag_array[$i]{0} != "-" ) {
$i++;
continue;
}
if ( substr( $flag_array[$i], 0, 2 ) == "--" ) {
if (strpos($flag_array[$i], '=') != false) {
list($key, $value) = explode('=', substr($flag_array[$i], 2), 2);
if ( in_array ( $key, $long_required_value ) || in_array ( $key, $long_optional_value ) )
$options[$key][] = $value;
$i++;
continue;
}
if (strpos($flag_array[$i], '=') == false) {
$key = substr( $flag_array[$i], 2 );
if ( in_array( substr( $flag_array[$i], 2 ), $long_required_value ) ) {
$options[$key][] = $flag_array[$i+1];
$i += 2;
continue;
} else if ( in_array( substr( $flag_array[$i], 2 ), $long_optional_value ) ) {
if ( $flag_array[$i+1] != "" && $flag_array[$i+1]{0} != "-" ) {
$options[$key][] = $flag_array[$i+1];
$i += 2;
} else {
$options[$key][] = FALSE;
$i ++;
}
continue;
} else if ( in_array( substr( $flag_array[$i], 2 ), $long_no_value ) ) {
$options[$key][] = FALSE;
$i++;
continue;
} else {
$i++;
continue;
}
}
} else if ( $flag_array[$i]{0} == "-" && $flag_array[$i]{1} != "-" ) {
for ( $j=1; $j < strlen($flag_array[$i]); $j++ ) {
if ( in_array( $flag_array[$i]{$j}, $short_required_value ) || in_array( $flag_array[$i]{$j}, $short_optional_value )) {
if ( $j == strlen($flag_array[$i]) - 1 ) {
if ( in_array( $flag_array[$i]{$j}, $short_required_value ) ) {
$options[$flag_array[$i]{$j}][] = $flag_array[$i+1];
$i += 2;
} else if ( in_array( $flag_array[$i]{$j}, $short_optional_value ) && $flag_array[$i+1] != "" && $flag_array[$i+1]{0} != "-" ) {
$options[$flag_array[$i]{$j}][] = $flag_array[$i+1];
$i += 2;
} else {
$options[$flag_array[$i]{$j}][] = FALSE;
$i ++;
}
$plus_i = 0;
break;
} else {
$options[$flag_array[$i]{$j}][] = substr ( $flag_array[$i], $j + 1 );
$i ++;
$plus_i = 0;
break;
}
} else if ( in_array ( $flag_array[$i]{$j}, $short_no_value ) ) {
$options[$flag_array[$i]{$j}][] = FALSE;
$plus_i = 1;
continue;
} else {
$plus_i = 1;
break;
}
}
$i += $plus_i;
continue;
}
$i++;
continue;
}
foreach ( $options as $key => $value ) {
if ( count ( $value ) == 1 ) {
$options[ $key ] = $value[0];
}
}
return $options;
}
function split_para ( $pattern ) {
/* split_para() version 1.0 2008/08/19
My page: http://www.ntu.beautifulworldco.com/weblog/?p=526
This function is to parse parameters and split them into smaller pieces.
preg_split() does similar thing but in our function, besides "space", we
also take the three symbols " (double quote), '(single quote),
and \ (backslash) into consideration because things in a pair of " or '
should be grouped together.
As an example, this parameter list
-f "test 2" -ab --required "t\"est 1" --optional="te'st 3" --option -v 'test 4'
will be splited into
-f
t"est 2
-ab
--required
test 1
--optional=te'st 3
--option
-v
test 4
see the code below,
$pattern = "-f \"test 2\" -ab --required \"t\\\"est 1\" --optional=\"te'st 3\" --option -v 'test 4'";
$result = split_para( $pattern );
echo "ORIGINAL PATTERN: $pattern\n\n";
var_dump( $result );
*/
$begin=0;
$backslash = 0;
$quote = "";
$quote_mark = array();
$result = array();
$pattern = trim ( $pattern );
for ( $end = 0; $end < strlen ( $pattern ) ; ) {
if ( ! in_array ( $pattern{$end}, array ( " ", "\"", "'", "\\" ) ) ) {
$backslash = 0;
$end ++;
continue;
}
if ( $pattern{$end} == "\\" ) {
$backslash++;
$end ++;
continue;
} else if ( $pattern{$end} == "\"" ) {
if ( $backslash % 2 == 1 || $quote == "'" ) {
$backslash = 0;
$end ++;
continue;
}
if ( $quote == "" ) {
$quote_mark[] = $end - $begin;
$quote = "\"";
} else if ( $quote == "\"" ) {
$quote_mark[] = $end - $begin;
$quote = "";
}
$backslash = 0;
$end ++;
continue;
} else if ( $pattern{$end} == "'" ) {
if ( $backslash % 2 == 1 || $quote == "\"" ) {
$backslash = 0;
$end ++;
continue;
}
if ( $quote == "" ) {
$quote_mark[] = $end - $begin;
$quote = "'";
} else if ( $quote == "'" ) {
$quote_mark[] = $end - $begin;
$quote = "";
}
$backslash = 0;
$end ++;
continue;
} else if ( $pattern{$end} == " " ) {
if ( $quote != "" ) {
$backslash = 0;
$end ++;
continue;
} else {
$backslash = 0;
$cand = substr( $pattern, $begin, $end-$begin );
for ( $j = 0; $j < strlen ( $cand ); $j ++ ) {
if ( in_array ( $j, $quote_mark ) )
continue;
$cand1 .= $cand{$j};
}
if ( $cand1 ) {
eval( "\$cand1 = \"$cand1\";" );
$result[] = $cand1;
}
$quote_mark = array();
$cand1 = "";
$end ++;
$begin = $end;
continue;
}
}
}
$cand = substr( $pattern, $begin, $end-$begin );
for ( $j = 0; $j < strlen ( $cand ); $j ++ ) {
if ( in_array ( $j, $quote_mark ) )
continue;
$cand1 .= $cand{$j};
}
eval( "\$cand1 = \"$cand1\";" );
if ( $cand1 )
$result[] = $cand1;
return $result;
}
function kshift(&$arr) {
if (count($arr) == 0) {
return FALSE;
}
foreach ($arr as $k => $v) {
unset($arr[$k]);
break;
}
return array($k, $v);
}
function kshuffle(&$array) {
if (!is_array($array)) { return $array; }
$keys = array_keys($array);
shuffle($keys);
$random = array();
foreach ($keys as $key) {
$random[$key] = $array[$key];
}
$array = $random;
}
class DriveSelection {
var $num_drives_per_draft;
var $selection_algorithm;
var $drives;
var $is_custom;
var $sorted_target_drives;
var $last_resort_sorted_target_drives;
function __construct($num_drives_per_draft, $selection_algorithm, $drives, $is_custom) {
$this->num_drives_per_draft = $num_drives_per_draft;
$this->selection_algorithm = $selection_algorithm;
$this->drives = $drives;
$this->is_custom = $is_custom;
}
function init(&$sorted_target_drives, &$last_resort_sorted_target_drives) {
// Sort by used space (asc) for least_used_space, or by available space (desc) for most_available_space
if ($this->selection_algorithm == 'least_used_space') {
$sorted_target_drives = $sorted_target_drives['used_space'];
$last_resort_sorted_target_drives = $last_resort_sorted_target_drives['used_space'];
asort($sorted_target_drives);
asort($last_resort_sorted_target_drives);
} else if ($this->selection_algorithm == 'most_available_space') {
$sorted_target_drives = $sorted_target_drives['available_space'];
$last_resort_sorted_target_drives = $last_resort_sorted_target_drives['available_space'];
arsort($sorted_target_drives);
arsort($last_resort_sorted_target_drives);
} else {
gh_log(CRITICAL, "Unknown drive_selection_algorithm found: " . $this->selection_algorithm);
}
// Only keep drives that are in $this->drives
$this->sorted_target_drives = array();
foreach ($sorted_target_drives as $sp_drive => $space) {
if (array_search($sp_drive, $this->drives) !== FALSE) {
$this->sorted_target_drives[$sp_drive] = $space;
}
}
$this->last_resort_sorted_target_drives = array();
foreach ($last_resort_sorted_target_drives as $sp_drive => $space) {
if (array_search($sp_drive, $this->drives) !== FALSE) {
$this->last_resort_sorted_target_drives[$sp_drive] = $space;
}
}
}
function draft() {
$drives = array();
$drives_last_resort = array();
while (count($drives)<$this->num_drives_per_draft) {
$arr = kshift($this->sorted_target_drives);
if ($arr === FALSE) {
break;
}
list($sp_drive, $space) = $arr;
if (!is_greyhole_owned_drive($sp_drive)) { continue; }
$drives[$sp_drive] = $space;
}
while (count($drives)+count($drives_last_resort)<$this->num_drives_per_draft) {
$arr = kshift($this->last_resort_sorted_target_drives);
if ($arr === FALSE) {
break;
}
list($sp_drive, $space) = $arr;
if (!is_greyhole_owned_drive($sp_drive)) { continue; }
$drives_last_resort[$sp_drive] = $space;
}
return array($drives, $drives_last_resort);
}
static function parse($config_string, $drive_selection_groups) {
$ds = array();
if ($config_string == 'least_used_space' || $config_string == 'most_available_space') {
global $storage_pool_drives;
$ds[] = new DriveSelection(count($storage_pool_drives), $config_string, $storage_pool_drives, FALSE);
return $ds;
}
if (!preg_match('/forced ?\((.+)\) ?(least_used_space|most_available_space)/i', $config_string, $regs)) {
gh_log(CRITICAL, "Can't understand the drive_selection_algorithm value: $config_string");
}
$selection_algorithm = $regs[2];
$groups = array_map('trim', explode(',', $regs[1]));
foreach ($groups as $group) {
$group = explode(' ', preg_replace('/^([0-9]+)x/', '\\1 ', $group));
$num_drives = trim($group[0]);
$group_name = trim($group[1]);
if (!isset($drive_selection_groups[$group_name])) {
//gh_log(WARN, "Warning: drive selection group named '$group_name' is undefined.");
continue;
}
if ($num_drives == 'all' || $num_drives > count($drive_selection_groups[$group_name])) {
$num_drives = count($drive_selection_groups[$group_name]);
}
$ds[] = new DriveSelection($num_drives, $selection_algorithm, $drive_selection_groups[$group_name], TRUE);
}
return $ds;
}
function update() {
// Make sure num_drives_per_draft and drives have been set, in case storage_pool_drive lines appear after drive_selection_algorithm line(s) in the config file
if (!$this->is_custom && ($this->selection_algorithm == 'least_used_space' || $this->selection_algorithm == 'most_available_space')) {
global $storage_pool_drives;
$this->num_drives_per_draft = count($storage_pool_drives);
$this->drives = $storage_pool_drives;
}
}
}
$greyhole_owned_drives = array();
function is_greyhole_owned_drive($sp_drive) {
global $going_drive, $df_cache_time, $greyhole_owned_drives;
if (isset($going_drive) && $sp_drive == $going_drive) {
return FALSE;
}
$is_greyhole_owned_drive = isset($greyhole_owned_drives[$sp_drive]);
if ($is_greyhole_owned_drive && $greyhole_owned_drives[$sp_drive] < time() - $df_cache_time) {
unset($greyhole_owned_drives[$sp_drive]);
$is_greyhole_owned_drive = FALSE;
}
if (!$is_greyhole_owned_drive) {
$drives_definitions = Settings::get('sp_drives_definitions', TRUE);
if (!$drives_definitions) {
$drives_definitions = convert_sp_drives_tag_files();
}
$drive_uuid = gh_dir_uuid($sp_drive);
$is_greyhole_owned_drive = @$drives_definitions[$sp_drive] === $drive_uuid && $drive_uuid !== FALSE;
if (!$is_greyhole_owned_drive) {
// Maybe this is a remote mount? Those don't have UUIDs, so we use the .greyhole_uses_this technique.
$is_greyhole_owned_drive = file_exists("$sp_drive/.greyhole_uses_this");
if ($is_greyhole_owned_drive && isset($drives_definitions[$sp_drive])) {
// This remote drive was listed in MySQL; it shouldn't be. Let's remove it.
unset($drives_definitions[$sp_drive]);
Settings::set('sp_drives_definitions', $drives_definitions);
}
}
if ($is_greyhole_owned_drive) {
$greyhole_owned_drives[$sp_drive] = time();
}
}
return $is_greyhole_owned_drive;
}
// Is it OK for a drive to be gone?
function gone_ok($sp_drive, $refresh=FALSE) {
global $gone_ok_drives;
if ($refresh || !isset($gone_ok_drives)) {
$gone_ok_drives = get_gone_ok_drives();
}
if (isset($gone_ok_drives[$sp_drive])) {
return TRUE;
}
return FALSE;
}
function get_gone_ok_drives() {
global $gone_ok_drives;
$gone_ok_drives = Settings::get('Gone-OK-Drives', TRUE);
if (!$gone_ok_drives) {
$gone_ok_drives = array();
Settings::set('Gone-OK-Drives', $gone_ok_drives);
}
return $gone_ok_drives;
}
function mark_gone_ok($sp_drive, $action='add') {
global $storage_pool_drives;
if (array_search($sp_drive, $storage_pool_drives) === FALSE) {
$sp_drive = '/' . trim($sp_drive, '/');
}
if (array_search($sp_drive, $storage_pool_drives) === FALSE) {
return FALSE;
}
global $gone_ok_drives;
$gone_ok_drives = get_gone_ok_drives();
if ($action == 'add') {
$gone_ok_drives[$sp_drive] = TRUE;
} else {
unset($gone_ok_drives[$sp_drive]);
}
Settings::set('Gone-OK-Drives', $gone_ok_drives);
return TRUE;
}
function gone_fscked($sp_drive, $refresh=FALSE) {
global $fscked_gone_drives;
if ($refresh || !isset($fscked_gone_drives)) {
$fscked_gone_drives = get_fsck_gone_drives();
}
if (isset($fscked_gone_drives[$sp_drive])) {
return TRUE;
}
return FALSE;
}
function get_fsck_gone_drives() {
global $fscked_gone_drives;
$fscked_gone_drives = Settings::get('Gone-FSCKed-Drives', TRUE);
if (!$fscked_gone_drives) {
$fscked_gone_drives = array();
Settings::set('Gone-FSCKed-Drives', $fscked_gone_drives);
}
return $fscked_gone_drives;
}
function mark_gone_drive_fscked($sp_drive, $action='add') {
global $fscked_gone_drives;
$fscked_gone_drives = get_fsck_gone_drives();
if ($action == 'add') {
$fscked_gone_drives[$sp_drive] = TRUE;
} else {
unset($fscked_gone_drives[$sp_drive]);
}
Settings::set('Gone-FSCKed-Drives', $fscked_gone_drives);
}
function check_storage_pool_drives($skip_fsck=FALSE) {
global $storage_pool_drives, $email_to, $gone_ok_drives;
$needs_fsck = FALSE;
$returned_drives = array();
$missing_drives = array();
$i = 0; $j = 0;
foreach ($storage_pool_drives as $sp_drive) {
if (!is_greyhole_owned_drive($sp_drive) && !gone_fscked($sp_drive, $i++ == 0) && !file_exists("$sp_drive/.greyhole_used_this")) {
if($needs_fsck !== 2){
$needs_fsck = 1;
}
mark_gone_drive_fscked($sp_drive);
$missing_drives[] = $sp_drive;
gh_log(WARN, "Warning! It seems the partition UUID of $sp_drive changed. This probably means this mount is currently unmounted, or that you replaced this drive and didn't use 'greyhole --replace'. Because of that, Greyhole will NOT use this drive at this time.");
gh_log(DEBUG, "Email sent for gone drive: $sp_drive");
$gone_ok_drives[$sp_drive] = TRUE; // The upcoming fsck should not recreate missing copies just yet
} else if ((gone_ok($sp_drive, $j++ == 0) || gone_fscked($sp_drive, $i++ == 0)) && is_greyhole_owned_drive($sp_drive)) {
// $sp_drive is now back
$needs_fsck = 2;
$returned_drives[] = $sp_drive;
gh_log(DEBUG, "Email sent for revived drive: $sp_drive");
mark_gone_ok($sp_drive, 'remove');
mark_gone_drive_fscked($sp_drive, 'remove');
$i = 0; $j = 0;
}
}
if(count($returned_drives) > 0) {
$body = "This is an automated email from Greyhole.\n\nOne (or more) of your storage pool drives came back:\n";
foreach ($returned_drives as $sp_drive) {
$body .= "$sp_drive was missing; it's now available again.\n";
}
if (!$skip_fsck) {
$body .= "\nA fsck will now start, to fix the symlinks found in your shares, when possible.\nYou'll receive a report email once that fsck run completes.\n";
}
$drive_string = join(", ", $returned_drives);
$subject = "Storage pool drive now online on " . exec ('hostname') . ": ";
$subject = $subject . $drive_string;
if (strlen($subject) > 255) {
$subject = substr($subject, 0, 255);
}
mail($email_to, $subject, $body);
}
if(count($missing_drives) > 0) {
$body = "This is an automated email from Greyhole.\n\nOne (or more) of your storage pool drives has disappeared:\n";
$drives_definitions = Settings::get('sp_drives_definitions', TRUE);
foreach ($missing_drives as $sp_drive) {
if (!is_dir($sp_drive)) {
$body .= "$sp_drive: directory doesn't exists\n";
} else {
$current_uuid = gh_dir_uuid($sp_drive);
if (empty($current_uuid)) {
$current_uuid = 'N/A';
}
$body .= "$sp_drive: expected partition UUID: " . $drives_definitions[$sp_drive] . "; current partition UUID: $current_uuid\n";
}
}
$sp_drive = $missing_drives[0];
$body .= "\nThis either means this mount is currently unmounted, or you forgot to use 'greyhole --replace' when you changed this drive.\n\n";
$body .= "Here are your options:\n\n";
$body .= "- If you forgot to use 'greyhole --replace', you should do so now. Until you do, this drive will not be part of your storage pool.\n\n";
$body .= "- If the drive is gone, you should either re-mount it manually (if possible), or remove it from your storage pool. To do so, use the following command:\n greyhole --gone=" . escapeshellarg($sp_drive) . "\n Note that the above command is REQUIRED for Greyhole to re-create missing file copies before the next fsck runs. Until either happens, missing file copies WILL NOT be re-created on other drives.\n\n";
$body .= "- If you know this drive will come back soon, and do NOT want Greyhole to re-create missing file copies for this drive until it reappears, you should execute this command:\n greyhole --wait-for=" . escapeshellarg($sp_drive) . "\n\n";
if (!$skip_fsck) {
$body .= "A fsck will now start, to fix the symlinks found in your shares, when possible.\nYou'll receive a report email once that fsck run completes.\n";
}
$subject = "Missing storage pool drives on " . exec('hostname') . ": ";
$drive_string = join(",",$missing_drives);
$subject = $subject . $drive_string;
if (strlen($subject) > 255) {
$subject = substr($subject, 0, 255);
}
mail($email_to, $subject, $body);
}
if ($needs_fsck !== FALSE) {
set_metastore_backup();
get_metastores(FALSE); // FALSE => Resets the metastores cache
clearstatcache();
if (!$skip_fsck) {
global $shares_options;
initialize_fsck_report('All shares');
if($needs_fsck === 2) {
foreach ($returned_drives as $drive) {
$metastores = get_metastores_from_storage_volume($drive);
gh_log(INFO, "Starting fsck for metadata store on $drive which came back online.");
foreach($metastores as $metastore) {
foreach($shares_options as $share_name => $share_options) {
gh_fsck_metastore($metastore,"/$share_name", $share_name);
}
}
gh_log(INFO, "fsck for returning drive $drive's metadata store completed.");
}
gh_log(INFO, "Starting fsck for all shares - caused by missing drive that came back online.");
}else{
gh_log(INFO, "Starting fsck for all shares - caused by missing drive. Will just recreate symlinks to existing copies when possible; won't create new copies just yet.");
fix_all_symlinks();
}
schedule_fsck_all_shares(array('email'));
gh_log(INFO, " fsck for all shares scheduled.");
}
// Refresh $gone_ok_drives to it's real value (from the DB)
get_gone_ok_drives();
}
}
class FSCKLogFile {
const PATH = '/usr/share/greyhole';
private $path;
private $filename;
private $lastEmailSentTime = 0;
public function __construct($filename, $path=self::PATH) {
$this->filename = $filename;
$this->path = $path;
}
public function emailAsRequired() {
$logfile = "$this->path/$this->filename";
if (!file_exists($logfile)) { return; }
$last_mod_date = filemtime($logfile);
if ($last_mod_date > $this->getLastEmailSentTime()) {
global $email_to;
gh_log(WARN, "Sending $logfile by email to $email_to");
mail($email_to, $this->getSubject(), $this->getBody());
$this->lastEmailSentTime = $last_mod_date;
Settings::set("last_email_$this->filename", $this->lastEmailSentTime);
}
}
private function getBody() {
$logfile = "$this->path/$this->filename";
if ($this->filename == 'fsck_checksums.log') {
return file_get_contents($logfile) . "\nNote: You should manually delete the $logfile file once you're done with it.";
} else if ($this->filename == 'fsck_files.log') {
global $fsck_report;
$fsck_report = unserialize(file_get_contents($logfile));
unlink($logfile);
return get_fsck_report() . "\nNote: This report is a complement to the last report you've received. It details possible errors with files for which the fsck was postponed.";
} else {
return '[empty]';
}
}
private function getSubject() {
if ($this->filename == 'fsck_checksums.log') {
return 'Mismatched checksums in Greyhole file copies';
} else if ($this->filename == 'fsck_files.log') {
return 'fsck_files of Greyhole shares on ' . exec('hostname');
} else {
return 'Unknown FSCK report';
}
}
private function getLastEmailSentTime() {
if ($this->lastEmailSentTime == 0) {
$setting = Settings::get("last_email_$this->filename");
if ($setting) {
$this->lastEmailSentTime = (int) $setting;
}
}
return $this->lastEmailSentTime;
}
public static function loadFSCKReport($what) {
$logfile = self::PATH . '/fsck_files.log';
if (file_exists($logfile)) {
global $fsck_report;
$fsck_report = unserialize(file_get_contents($logfile));
} else {
initialize_fsck_report($what);
}
}
public static function saveFSCKReport() {
global $fsck_report;
$logfile = self::PATH . '/fsck_files.log';
file_put_contents($logfile, serialize($fsck_report));
}
}
class Settings {
public static function get($name, $unserialize=FALSE, $value=FALSE) {
$query = sprintf("SELECT * FROM settings WHERE name LIKE '%s'", $name);
if ($value !== FALSE) {
$query .= sprintf(" AND value LIKE '%s'", $value);
}
$result = db_query($query) or gh_log(CRITICAL, "Can't select setting '$name'/'$value' from settings table: " . db_error());
$setting = db_fetch_object($result);
if ($setting === FALSE) {
return FALSE;
}
return $unserialize ? unserialize($setting->value) : $setting->value;
}
public static function set($name, $value) {
if (is_array($value)) {
$value = serialize($value);
}
global $db_use_mysql;
if (@$db_use_mysql) {
$query = sprintf("INSERT INTO settings (name, value) VALUES ('%s', '%s') ON DUPLICATE KEY UPDATE value = VALUES(value)", $name, $value);
db_query($query) or gh_log(CRITICAL, "Can't insert/update '$name' setting: " . db_error());
} else {
$query = sprintf("DELETE FROM settings WHERE name = '%s'", $name);
db_query($query) or gh_log(CRITICAL, "Can't delete '$name' setting: " . db_error());
$query = sprintf("INSERT INTO settings (name, value) VALUES ('%s', '%s')", $name, $value);
db_query($query) or gh_log(CRITICAL, "Can't insert '$name' setting: " . db_error());
}
return (object) array('name' => $name, 'value' => $value);
}
public static function rename($from, $to) {
$query = sprintf("UPDATE settings SET name = '%s' WHERE name = '%s'", $to, $from);
db_query($query) or gh_log(CRITICAL, "Can't rename setting '$from' to '$to': " . db_error());
}
public static function backup() {
global $storage_pool_drives;
$result = db_query("SELECT * FROM settings") or gh_log(CRITICAL, "Can't select settings for backup: " . db_error());
$settings = array();
while ($setting = db_fetch_object($result)) {
$settings[] = $setting;
}
foreach ($storage_pool_drives as $sp_drive) {
if (is_greyhole_owned_drive($sp_drive)) {
$settings_backup_file = "$sp_drive/.gh_settings.bak";
file_put_contents($settings_backup_file, serialize($settings));
}
}
}
public static function restore() {
global $storage_pool_drives;
foreach ($storage_pool_drives as $sp_drive) {
$settings_backup_file = "$sp_drive/.gh_settings.bak";
$latest_backup_time = 0;
if (file_exists($settings_backup_file)) {
$last_mod_date = filemtime($settings_backup_file);
if ($last_mod_date > $latest_backup_time) {
$backup_file = $settings_backup_file;
$latest_backup_time = $last_mod_date;
}
}
}
if (isset($backup_file)) {
gh_log(INFO, "Restoring settings from last backup: $backup_file");
$settings = unserialize(file_get_contents($backup_file));
foreach ($settings as $setting) {
Settings::set($setting->name, $setting->value);
}
return TRUE;
}
return FALSE;
}
}
function gh_dir_uuid($dir) {
$dev = exec('df ' . escapeshellarg($dir) . ' 2> /dev/null | grep \'/dev\' | awk \'{print $1}\'');
if (empty($dev) || strpos($dev, '/dev/') !== 0) {
return 'remote';
}
return trim(exec('blkid '.$dev.' | awk -F\'UUID="\' \'{print $2}\' | awk -F\'"\' \'{print $1}\''));
}
function fix_all_symlinks() {
global $shares_options;
foreach ($shares_options as $share_name => $share_options) {
fix_symlinks_on_share($share_name);
}
}
function fix_symlinks_on_share($share_name) {
global $shares_options, $storage_pool_drives;
$share_options = $shares_options[$share_name];
echo "Looking for broken symbolic links in the share '$share_name'... Please be patient... ";
chdir($share_options['landing_zone']);
exec("find -L . -type l", $result);
foreach ($result as $file_to_relink) {
if (is_link($file_to_relink)) {
$file_to_relink = substr($file_to_relink, 2);
foreach ($storage_pool_drives as $sp_drive) {
if (!is_greyhole_owned_drive($sp_drive)) { continue; }
$new_link_target = clean_dir("$sp_drive/$share_name/$file_to_relink");
if (gh_is_file($new_link_target)) {
unlink($file_to_relink);
symlink($new_link_target, $file_to_relink);
echo ".";
break;
}
}
}
}
echo "Done.\n";
}
function schedule_fsck_all_shares($fsck_options=array()) {
global $shares_options;
foreach ($shares_options as $share_name => $share_options) {
$full_path = $share_options['landing_zone'];
$query = sprintf("INSERT INTO tasks (action, share, additional_info, complete) VALUES ('fsck', '%s', %s, 'yes')",
db_escape_string($full_path),
(!empty($fsck_options) ? "'" . implode('|', $fsck_options) . "'" : "NULL")
);
db_query($query) or gh_log(CRITICAL, "Can't insert fsck task: " . db_error());
}
}
?>
Jump to Line
Something went wrong with that request. Please try again.