#!/usr/bin/php -q setTimestamp($now); // Take the last date/time, set the time to 59 seconds past midnight // then remove one minute to make it the previous evening $date_orig = new DateTime(); $date_orig->setTimestamp($last); $date_last = new DateTime(); $date_last->setTimestamp($last)->setTime(0,0,59)->modify('-1 minute'); // Make sure we clone the last date, or we end up modifying the same object! $date_next = clone $date_last; $date_next->modify('+'.$frequency.'day'); cacti_log('Cacti Log Rotation - TIMECHECK Ran: ' . $date_orig->format('Y-m-d H:i:s') . ', Now: ' . $date_now->format('Y-m-d H:i:s') . ', Next: ' . $date_next->format('Y-m-d H:i:s'), true, 'MAINT', POLLER_VERBOSITY_HIGH); if ($date_next < $date_now || $force) { logrotate_rotatenow(); } } exit(0); /** realtime_purge_cache() - This function will purge files in the realtime directory * that are older than 2 hours without changes */ function realtime_purge_cache() { /* remove all Realtime files over than 2 hours */ if (read_config_option('realtime_cache_path') != '') { $cache_path = read_config_option('realtime_cache_path'); if (is_dir($cache_path) && is_writeable($cache_path)) { foreach (new DirectoryIterator($cache_path) as $fileInfo) { if ($fileInfo->isDot()) { continue; } // only remove .png and .rrd files if ( (substr($fileInfo->getFilename(), -4, 4) == '.png') || (substr($fileInfo->getFilename(), -4, 4) == '.rrd') ) { if ((time() - $fileInfo->getMTime()) >= 7200) { unlink($fileInfo->getRealPath()); } } } } } db_execute("DELETE FROM poller_output_realtime WHERE timesetTimestamp($run_time)->modify('-1day'); $rotated = 0; $cleaned = 0; $days = read_config_option('logrotate_retain'); if ($days == '' || $days < 0) { $days = 7; } if ($days > 365) { $days = 365; } foreach ($logs as $name => $log) { $rotated += logrotate_file_rotate($name, $log, $date); $cleaned += logrotate_file_clean($name, $log, $date, $days); } $cleaned += logrotate_file_clean($name, $log, $date, $days); /* record the start time */ $poller_end = microtime(true); $string = sprintf('LOGMAINT STATS: Time:%4.4f, Rotated:%d, Removed:%d, Days Retained:%d', ($poller_end - $poller_start), $rotated, $cleaned, $days); cacti_log($string, true, 'SYSTEM'); } /* logrotate_file_rotate() * rotates the specified log file, appending date given */ function logrotate_file_rotate($name, $log, $date) { if (empty($log)) { return 0; } clearstatcache(); if (!file_exists($log)) { cacti_log('Cacti Log Rotation - Skipped missing ' . $name . ' Log : ' . $log, true, 'MAINT'); return 0; } if (is_writable(dirname($log) . '/') && is_writable($log)) { $perms = octdec(substr(decoct( fileperms($log) ), 2)); $owner = fileowner($log); $group = filegroup($log); if ($owner !== false) { $ext = $date->format('Ymd'); if (file_exists($log . '-' . $ext)) { $ext_inc = 1; while (file_exists($log . '-' . $ext . '-' . $ext_inc) && $ext_inc < 99) { $ext_inc++; } $ext = $ext . '-' . $ext_inc; } if (rename($log, $log . '-' . $ext)) { touch($log); chown($log, $owner); chgrp($log, $group); chmod($log, $perms); cacti_log('Cacti Log Rotation - Created ' . $name . ' Log : ' . basename($log) . '-' . $ext, true, 'MAINT'); return 1; } else { cacti_log('Cacti Log Rotation - ERROR: Could not rename ' . $name . ' Log "' . basename($log) . '" to "' . basename($log) . '-' . $ext . '"', true, 'MAINT'); } } else { cacti_log('Cacti Log Rotation - ERROR: Permissions issue. Please check your ' . $name . ' Log directory : ' . basename($log), true, 'MAINT'); } } else { cacti_log('Cacti Log Rotation - ERROR: Permissions issue. Please check your ' . $name . ' Log as directory or file are not writable : ' . $log, true, 'MAINT'); } return 0; } /* * logrotate_file_clean * Cleans up any old log files that should be removed */ function logrotate_file_clean($name, $log, $date, $rotation) { global $config; if (empty($log)) { return false; } if ($rotation <= 0) { return false; } $baselogdir = dirname($log) . '/'; $baselogname = basename($log); clearstatcache(); $dir = scandir($baselogdir); if (cacti_sizeof($dir)) { $date_log = clone $date; $date_log->modify('-'.$rotation.'day'); $e = $date_log->format('Ymd'); cacti_log('Cacti Log Rotation - Purging all ' . $name . ' logs before '. $e, true, 'MAINT'); foreach ($dir as $d) { $fileparts = explode('-', $d); $matches = false; if (strpos($d, $baselogname) !== false) { if ($fileparts > 1) { foreach($fileparts as $p) { // Is it in the form YYYYMMDD? if (is_numeric($p) && strlen($p) == 8) { $matches = true; if ($p < $e) { if (is_writable($baselogdir . $d)) { @unlink($baselogdir . $d); cacti_log('Cacti Log Rotation - Purging ' . $name . ' Log : ' . $d, true, 'MAINT'); } else { cacti_log('Cacti Log Rotation - ERROR: Can not purge ' . $name . ' Log : ' . $d, true, 'MAINT'); } } else { cacti_log('Cacti Log Rotation - NOTE: Not expired, keeping ' . $name . ' Log : ' . $d, true, 'MAINT', POLLER_VERBOSITY_HIGH); } } } } } if ($matches) { cacti_log('Cacti Log Rotation - NOTE: File not in expected naming format, ignoring ' . $name . ' Log : ' . $d, true, 'MAINT', POLLER_VERBOSITY_DEBUG); } } } clearstatcache(); } /* * secpass_check_expired * Checks user accounts to determine if the accounts and/or their passwords should be expired */ function secpass_check_expired () { maint_debug('Checking for Account / Password expiration'); // Expire Old Accounts $e = read_config_option('secpass_expireaccount'); if ($e > 0 && is_numeric($e)) { $t = time(); db_execute_prepared("UPDATE user_auth SET lastlogin = ? WHERE lastlogin = -1 AND realm = 0 AND enabled = 'on'", array($t)); $t = $t - (intval($e) * 86400); db_execute_prepared("UPDATE user_auth SET enabled = '' WHERE realm = 0 AND enabled = 'on' AND lastlogin < ? AND id > 1", array($t)); } $e = read_config_option('secpass_expirepass'); if ($e > 0 && is_numeric($e)) { $t = time(); db_execute_prepared("UPDATE user_auth SET lastchange = ? WHERE lastchange = -1 AND realm = 0 AND enabled = 'on'", array($t)); $t = $t - (intval($e) * 86400); db_execute_prepared("UPDATE user_auth SET must_change_password = 'on' WHERE realm = 0 AND enabled = 'on' AND lastchange < ?", array($t)); } } /* * remove_files * remove all unwanted files; the list is given by table data_source_purge_action */ function remove_files($file_array) { global $config, $debug, $archived, $purged; maint_debug('RRDClean is now running on ' . cacti_sizeof($file_array) . ' items'); /* determine the location of the RRA files */ if (isset ($config['rra_path'])) { $rra_path = $config['rra_path']; } else { $rra_path = $config['base_path'] . '/rra'; } /* let's prepare the archive directory */ $rrd_archive = read_config_option('rrd_archive', true); if ($rrd_archive == '') { $rrd_archive = $rra_path . '/archive'; } rrdclean_create_path($rrd_archive); /* now scan the files */ foreach ($file_array as $file) { $source_file = $rra_path . '/' . $file['name']; switch ($file['action']) { case '1' : if (unlink($source_file)) { maint_debug('Deleted: ' . $file['name']); } else { cacti_log($file['name'] . " ERROR: RRDfile Maintenance unable to delete from $rra_path!", true, 'MAINT'); } $purged++; break; case '3' : $target_file = $rrd_archive . '/' . $file['name']; $target_dir = dirname($target_file); if (!is_dir($target_dir)) { rrdclean_create_path($target_dir); } if (rename($source_file, $target_file)) { maint_debug('Moved: ' . $file['name'] . ' to: ' . $rrd_archive); } else { cacti_log($file['name'] . " ERROR: RRDfile Maintenance unable to move to $rrd_archive!", true, 'MAINT'); } $archived++; break; } /* drop from data_source_purge_action table */ db_execute_prepared('DELETE FROM `data_source_purge_action` WHERE name = ?', array($file['name'])); maint_debug('Delete from data_source_purge_action: ' . $file['name']); //fetch all local_graph_id's according to this data source $lgis = db_fetch_assoc_prepared('SELECT DISTINCT gl.id FROM graph_local AS gl INNER JOIN graph_templates_item AS gti ON gl.id = gti.local_graph_id INNER JOIN data_template_rrd AS dtr ON dtr.id=gti.task_item_id INNER JOIN data_local AS dl ON dtr.local_data_id=dl.id WHERE (local_data_id=?)', array($file['local_data_id'])); if (cacti_sizeof($lgis)) { /* anything found? */ maint_debug('Processing ' . cacti_sizeof($lgis) . ' Graphs for data source id: ' . $file['local_data_id']); /* get them all */ foreach ($lgis as $item) { $remove_lgis[] = $item['id']; maint_debug('remove local_graph_id=' . $item['id']); } /* and remove them in a single run */ if (!empty ($remove_lgis)) { api_graph_remove_multi($remove_lgis); } } /* remove related data source if any */ if ($file['local_data_id'] > 0) { maint_debug('Removing Data Source: ' . $file['local_data_id']); api_data_source_remove($file['local_data_id']); } } maint_debug('RRDClean has finished a purge pass of ' . cacti_sizeof($file_array) . ' items'); } function rrdclean_create_path($path) { global $config; if (!is_dir($path)) { if (mkdir($path, 0775)) { if ($config['cacti_server_os'] != 'win32') { $owner_id = fileowner($config['rra_path']); $group_id = filegroup($config['rra_path']); // NOTE: chown/chgrp fails for non-root users, checking their // result is therefore irrevelevant @chown($path, $owner_id); @chgrp($path, $group_id); } } else { cacti_log("ERROR: RRDfile Maintenance unable to create directory '" . $path . "'", false, 'MAINT'); } } // if path existed, we can return true return is_dir($path) && is_writable($path); } /* * cleanup_ds_and_graphs - courtesy John Rembo */ function cleanup_ds_and_graphs() { global $config; $remove_ldis = array (); $remove_lgis = array (); maint_debug('RRDClean now cleans up all data sources and graphs'); //fetch all local_data_id's which have appropriate data-sources $rrds = db_fetch_assoc("SELECT local_data_id, name_cache, data_source_path FROM data_template_data WHERE name_cache > ''"); //filter those whose rrd files doesn't exist foreach ($rrds as $item) { $ldi = $item['local_data_id']; $name = $item['name_cache']; $ds_pth = $item['data_source_path']; $real_pth = str_replace('', $config['rra_path'], $ds_pth); if (!file_exists($real_pth)) { if (!in_array($ldi, $remove_ldis)) { $remove_ldis[] = $ldi; maint_debug("RRD file is missing for data source name: $name (local_data_id=$ldi)"); } } } if (empty ($remove_ldis)) { maint_debug('No missing rrd files found'); return 0; } maint_debug('Processing Graphs'); //fetch all local_graph_id's according to filtered rrds $lgis = db_fetch_assoc('SELECT DISTINCT gl.id FROM graph_local AS gl INNER JOIN graph_templates_item AS gti ON gl.id=gti.local_graph_id INNER JOIN data_template_rrd AS dtr ON dtr.id=gti.task_item_id INNER JOIN data_local AS dl ON dtr.local_data_id=dl.id WHERE (' . array_to_sql_or($remove_ldis, 'local_data_id') . ')'); foreach ($lgis as $item) { $remove_lgis[] = $item['id']; maint_debug('RRD file missing for local_graph_id=' . $item['id']); } if (!empty ($remove_lgis)) { maint_debug('removing graphs'); api_graph_remove_multi($remove_lgis); } maint_debug('removing data sources'); api_data_source_remove_multi($remove_ldis); maint_debug('removed graphs:' . cacti_count($remove_lgis) . ' removed data-sources:' . cacti_count($remove_ldis)); } function maint_debug($message) { global $debug; if ($debug) { print trim($message) . "\n"; } } /* display_version - displays version information */ function display_version() { $version = get_cacti_version(); print "Cacti Maintenance Poller, Version $version, " . COPYRIGHT_YEARS . "\n"; } /* * display_help * displays the usage of the function */ function display_help() { display_version(); print "\nusage: poller_maintenance.php [--force] [--debug]\n\n"; print "Cacti's maintenance poller. This poller is repsonsible for executing periodic\n"; print "maintenance activities for Cacti including log rotation, deactivating accounts, etc.\n\n"; print "Optional:\n"; print " --force - Force immediate execution, e.g. for testing.\n"; print " --debug - Display verbose output during execution.\n\n"; }