* @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 * @author Icinga Development Team * */ // {{{ICINGA_LICENSE_HEADER}}} namespace Icinga\Protocol\Statusdat; use Icinga\Exception\ConfigurationError; use Icinga\Exception\ProgrammingError; use Icinga\Protocol\Statusdat\Exception\ParsingException as ParsingException; /** * Status.dat and object.cache parser implementation */ class Parser { /** * An array of objects that couldn't be resolved yet due to missing dependencies * * @var array */ private $deferred = array(); /** * The resource pointing to the currently read file * * @var resource */ private $filehandle; /** * String representation of the currently parsed object type * * @var string */ private $currentObjectType; /** * The current state type (host, service) * * @var string */ private $currentStateType; /** * The internal representation of the icinga statue * * @var array */ private $icingaState; /** * The current line being read * * @var int */ private $lineCtr = 0; /** * Create a new parser using the given file * * @param resource $filehandle The file handle to usefor parsing * @param array $baseState The state using for the base * * @throws ConfigurationError When the file can't be used */ public function __construct($filehandle = null, $baseState = null) { if (!is_resource($filehandle)) { throw new ConfigurationError("Statusdat parser can't find $filehandle"); } $this->filehandle = $filehandle; $this->icingaState = $baseState; } /** * Parse the given file handle as an objects file and read object information */ public function parseObjectsFile() { $DEFINE = strlen("define "); $filehandle = $this->filehandle; $this->icingaState = array(); while (!feof($filehandle)) { $line = trim(fgets($filehandle)); $this->lineCtr++; if ($line === "" || $line[0] === "#") { continue; } $this->currentObjectType = trim(substr($line, $DEFINE, -1)); if (!isset($this->icingaState[$this->currentObjectType])) { $this->icingaState[$this->currentObjectType] = array(); } $this->readCurrentObject(); } $this->processDeferred(); } /** * Parse the given file handle as an status.dat file and read runtime information */ public function parseRuntimeState($filehandle = null) { if ($filehandle != null) { $this->filehandle = $filehandle; } else { $filehandle = $this->filehandle; } if (!$this->icingaState) { throw new ProgrammingError("Tried to read runtime state without existing objects data"); } $this->overwrites = array(); while (!feof($filehandle)) { $line = trim(fgets($filehandle)); $this->lineCtr++; if ($line === "" || $line[0] === "#") { continue; } $this->currentStateType = trim(substr($line, 0, -1)); $this->readCurrentState(); } } /** * Read the next object from the object.cache file handle * * @throws ParsingException */ private function readCurrentObject() { $filehandle = $this->filehandle; $monitoringObject = new PrintableObject(); while (!feof($filehandle)) { $line = explode("\t", trim(fgets($filehandle)), 2); $this->lineCtr++; if (!$line) { continue; } // End of object if ($line[0] === "}") { $this->registerObject($monitoringObject); return; } if (!isset($line[1])) { $line[1] = ""; } $monitoringObject->{$line[0]} = trim($line[1]); } throw new ParsingException("Unexpected EOF in objects.cache, line " . $this->lineCtr); } /** * Read the next state from the status.dat file handler * * @throws Exception\ParsingException */ private function readCurrentState() { $filehandle = $this->filehandle; $statusdatObject = new RuntimeStateContainer(); $objectType = $this->getObjectTypeForState(); if ($objectType != "host" && $objectType != "service") { $this->skipObject(); // ignore unknown objects return; } if (!isset($this->icingaState[$this->currentObjectType])) { throw new ParsingException("No $this->currentObjectType objects registered in objects.cache"); } $base = & $this->icingaState[$this->currentObjectType]; $state = $this->skipObject(true); $statusdatObject->runtimeState = & $state; $name = $this->getObjectIdentifier($statusdatObject); if (!isset($base[$name])) { throw new ParsingException( "Unknown object $name " . $this->currentObjectType . " - " . print_r( $statusdatObject, true ) . "\n" . print_r($base, true) ); } $type = substr($this->currentStateType, strlen($objectType)); if ($type == "status") { // directly set the status to the status field of the given object $base[$name]->status = & $statusdatObject; } else { if (!isset($base[$name]->$type) || !in_array($base[$name]->$type, $this->overwrites)) { $base[$name]->$type = array(); $this->overwrites[] = & $base[$name]->$type; } array_push($base[$name]->$type, $statusdatObject); $this->currentObjectType = $type; if (!isset($this->icingaState[$type])) { $this->icingaState[$type] = array(); } $this->icingaState[$type][] = &$statusdatObject; $id = $this->getObjectIdentifier($statusdatObject); if ($id !== false && isset($this->icingaState[$objectType][$id])) { $statusdatObject->$objectType = $this->icingaState[$objectType][$id]; } } return; } /** * Get the corresponding object type name for the given state * * @return string */ private function getObjectTypeForState() { $pos = strpos($this->currentStateType, "service"); if ($pos === false) { $pos = strpos($this->currentStateType, "host"); } else { $this->currentObjectType = "service"; return "service"; } if ($pos === false) { return $this->currentStateType; } else { $this->currentObjectType = "host"; return "host"; } return $this->currentObjectType; } /** * Skip the current object definition * * @param bool $returnString If true, the object string will be returned * @return string The skipped object if $returnString is true */ protected function skipObject($returnString = false) { if (!$returnString) { while (trim(fgets($this->filehandle)) !== "}") { } return null; } else { $str = ""; while (($val = trim(fgets($this->filehandle))) !== "}") { $str .= $val . "\n"; } return $str; } } /** * Register the given object in the icinga state * * @param object $object The monitoring object to register */ protected function registerObject(&$object) { $name = $this->getObjectIdentifier($object); if ($name !== false) { $this->icingaState[$this->currentObjectType][$name] = &$object; } $this->registerObjectAsProperty($object); } /** * Register the given object as a property in related objects * * This registers for example hosts underneath their hostgroup and vice cersa * * @param object $object The object to register as a property */ protected function registerObjectAsProperty(&$object) { if ($this->currentObjectType == 'service' || $this->currentObjectType == 'host' || $this->currentObjectType == 'contact') { return null; } $isService = strpos($this->currentObjectType, "service") !== false; $isHost = strpos($this->currentObjectType, "host") !== false; $isContact = strpos($this->currentObjectType, "contact") !== false; $name = $this->getObjectIdentifier($object); if ($isService === false && $isHost === false && $isContact === false) { // this would be error in the parser implementation return null; } $property = $this->currentObjectType; if ($isService) { $this->currentObjectType = "service"; $property = substr($property, strlen("service")); } elseif ($isHost) { $this->currentObjectType = "host"; $property = substr($property, strlen("host")); } elseif ($isContact) { $this->currentObjectType = "contact"; $property = substr($property, strlen("contact")); } if (!isset($this->icingaState[$this->currentObjectType])) { return $this->deferRegistration($object, $this->currentObjectType . $property); } // @TODO: Clean up, this differates between 1:n and 1:1 references if (strpos($property, "group") !== false) { $sourceIdentifier = $this->getMembers($object); foreach ($sourceIdentifier as $id) { $source = $this->icingaState[$this->currentObjectType][$id]; if (!isset($source->$property)) { $source->$property = array(); } $type = $this->currentObjectType; if (!isset($object->$type)) { $object->$type = array(); } // add the member to the group object array_push($object->$type, $source); // add the group to the member object array_push($source->$property, $name); } } else { $source = $this->icingaState[$this->currentObjectType][$this->getObjectIdentifier($object)]; if (!isset($source->$property)) { $source->$property = array(); } array_push($source->$property, $object); } return null; } /** * Defer registration of the given object * * @param object $object The object to defer * @param String $objType The name of the object type */ protected function deferRegistration($object, $objType) { $this->deferred[] = array($object, $objType); } /** * Process deferred objects */ protected function processDeferred() { foreach ($this->deferred as $obj) { $this->currentObjectType = $obj[1]; $this->registerObjectAsProperty($obj[0]); } } /** * Return the resolved members directive of an object * * @param object $object The object to get the members from * @return array An array of member names */ protected function getMembers(&$object) { if (!isset($object->members)) { return array(); } $members = explode(",", $object->members); if ($this->currentObjectType == "service") { $res = array(); for ($i = 0; $i < count($members); $i += 2) { $res[] = $members[$i] . ";" . $members[$i + 1]; } return $res; } else { return $members; } } /** * Return the unique name of the given object * * @param object $object The object to retrieve the name from * @return string The name of the object or null if no name can be retrieved */ protected function getObjectIdentifier(&$object) { if ($this->currentObjectType == 'contact') { return $object->contact_name; } if ($this->currentObjectType == "service") { return $object->host_name . ";" . $object->service_description; } $name = $this->currentObjectType . "_name"; if (isset($object->{$name})) { return $object->{$name}; } if (isset($object->service_description)) { return $object->host_name . ";" . $object->service_description; } elseif (isset($object->host_name)) { return $object->host_name; } return null; } /** * Return the internal state of the parser * * @return null */ public function getRuntimeState() { return $this->icingaState; } }