#!/usr/local/bin/php -q
<?

/*

PHPOSD, The PHP Persistent Object Store Daemon
==============================================

Copyright (C) 2003 Vincent Negrier aka. sIX <six@aegis-corp.org>

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

*/

// You may want to change the define statements below

define("PHPOSD_CONFIG_STORE", "conf/store.conf");
define("PHPOSD_CONFIG_CLASSES", "conf/classes.conf");
define("PHPOSD_STASIS_DIR", "stasis");

define("PHPOSD_DEFAULT_IFACE", "0.0.0.0");
define("PHPOSD_DEFAULT_PORT", 20100);

// But not these ones ...

define("PHPOSD_VERSION", "0.5.6-alpha");

define("PHPOS_OMP_VERSION", 1);
define("PHPOS_OMP_HDRLEN", 15);

define("PHPOS_MSG_NEWINST",	1);
define("PHPOS_MSG_BIND", 2);
define("PHPOS_MSG_PROPGET", 3);
define("PHPOS_MSG_PROPSET", 4);
define("PHPOS_MSG_MCALL", 5);
define("PHPOS_MSG_UNBIND", 6);

define("PHPOS_AC_ROOT", 0);
define("PHPOS_AC_OWNER", 1);
define("PHPOS_AC_AUTH", 2);
define("PHPOS_AC_WORLD", 3);

define("PHPOS_EOK", 0);
define("PHPOS_ENOTFOUND", 1);
define("PHPOS_ENOTALLOWED", 2);

define("PHPOS_EL_DEBUG", 1);
define("PHPOS_EL_OBJ_ACCESS", 2);
define("PHPOS_EL_OBJ_NEW", 4);
define("PHPOS_EL_CONNECT", 8);
define("PHPOS_EL_NOTICE", 16);
define("PHPOS_EL_WARNING", 32);
define("PHPOS_EL_ERROR", 64);
define("PHPOS_EL_DEFAULT", PHPOS_EL_NOTICE | PHPOS_EL_WARNING | PHPOS_EL_ERROR);
define("PHPOS_EL_ALL", 255);

require "lib/shadok.php";

class PHPOS_Store {

	var $containers;

	function PHPOS_Store() {

		$this->containers=array();
	
	}

	function &New_Class($oid, $class_name) {

		if (!$this->containers[$oid]) {
		
			$c =& new PHPOS_Class($oid, $class_name);
			$this->containers[$oid] =& $c;

			return ($c);

		} else {

			return false;

		}
	
	}
	
	function &New_Container($oid, $o=false) {

		if (!$this->containers[$oid]) {
		
			$c =& new PHPOS_Container($oid);
			if ($o) $c->Load_Object($o);
			$this->containers[$oid] =& $c;

			return ($c);
	
		} else {

			return false;

		}
	
	}

	function Write_Stasis_Data() {

		foreach ($this->containers as $fc) if ($GLOBALS["PHPOS_Config"]["store"][$fc->oid]["stasis"]) {
				
			$fc->Stasis_Freeze();
			SK_Toolkit::techo("frozen ".$fc->oid);

		}
	
	}

}

class PHPOS_S_Bind {

	var $container;
	var $login;
	var $password;
	var $access_mode;

	function PHPOS_S_Bind($oid, $login=false, $password=false) {

		if ($cont =& $GLOBALS["PHPOS_Store"]->containers[$oid]) {

			$this->container =& $cont;
			$this->login=$login;
			$this->password=$password;

			// FIXME
			$this->access_mode=0777;
		
		}
	
	}

}

class PHPOS_OID_Node {

	var $oid;
	var $access_mode;
	var $owner;

	var $last_act_time;

	function PHPOS_OID_Node($oid) {

		$this->oid=$oid;
	
	}
	
	function Set_Access_Mode($mode) {

		$this->access_mode=$mode;
	
	}

	function Set_Owner($owner) {

		$this->owner=$owner;
	
	}

}

class PHPOS_Class extends PHPOS_OID_Node {

	var $class_name;
	
	function PHPOS_Class($oid, $class_name) {

		parent::PHPOS_OID_Node($oid);

		$this->class_name=$class_name;
	
	}

}

class PHPOS_Container extends PHPOS_OID_Node {

	var $active_connections;

	var $active_binds;
	var $total_binds;

	var $content;

	var $create_time;
	var $sleep_time;
	var $wake_time;

	var $total_stasis;

	function PHPOS_Container($oid, $o=false) {
		
		parent::PHPOS_OID_Node($oid);
		
		$this->connections=array();
		$this->active_binds=0;
		$this->total_binds=0;

		$this->create_time=time();
		$this->sleep_time=0;
		$this->wake_time=0;
		
		if ($o) $this->content =& $o;
	
	}

	function Load_Object(&$o) {

		$this->content =& $o;
	
	}

	function On_Bind() {

		++$this->active_binds;
		++$this->total_binds;
	
	}

	function On_Unbind() {

		--$this->active_binds;
	
	}

	function Stasis_Freeze() {

		$this->sleep_time=time();
		++$this->total_stasis;
		$s=serialize($this);

		if ($f=fopen(PHPOSD_STASIS_DIR."/".$this->oid, "w")) {

			fputs($f, $s);
			fclose($f);

			return true;
		
		}
		
		return false;
	
	}

	function Stasis_Wakeup() {

		if ($s=@file_get_contents(PHPOSD_STASIS_DIR."/".$this->oid)) {

			$o=unserialize($s);

			$this->connections=array();
			$this->active_binds=0;
			$this->total_binds=$o->total_binds;

			$this->create_time=$o->create_time;
			$this->sleep_time=$o->sleep_time;
			$this->total_stasis=$o->total_stasis;
			$this->wake_time=time();
			
			$this->content =& $o->content;

			return true;

		}

		return false;
	
	}

}

class PHPOS_OMP {

	function Build_Packet($request_id, $message_type, $message_content) {

		$scontent=serialize($message_content);
		
		$pkt ="PHPOS";
		$pkt.=pack("CVCV", PHPOS_OMP_VERSION, $request_id, $message_type, strlen($scontent));
		$pkt.=$scontent;

		return ($pkt);
	
	}

	function Decode_Packet($pkt) {

		if (!($dcp=PHPOS_OMP::Decode_Packet_Header($pkt))) return false;
		$dcp["message"]=unserialize(substr($pkt, PHPOS_OMP_HDRLEN));

		return ($dcp);
	
	}
	
	function Decode_Packet_Header($pkt) {

		if (substr($pkt, 0, 5)!="PHPOS") return false;
		$hdr=unpack("Comp_version/Vrequest_id/Cmessage_type/Vmessage_length", substr($pkt, 5, (PHPOS_OMP_HDRLEN-5)));
		if ($hdr["omp_version"]!=PHPOS_OMP_VERSION) return false;

		return($hdr);
	
	}

}

class PHPOS_Server extends SK_Connection_Handler {

	var $peer;
	var $rcv_buf;

	var $hstatus;
	var $hbuffer;

	var $binds;

	function on_connect() {

		list($ip, $port)=$this->socket->getpeername();
		$this->peer=$ip.":".$port;
		$this->binds=array();

		SK_Toolkit::techo($this->peer." connected");

	}
	
	function on_read($data) {

		$this->rcv_buf.=$data;

		if (!$this->hstatus) {
		
			if (strlen($this->rcv_buf)>=PHPOS_OMP_HDRLEN) {

				if (!($hdr=PHPOS_OMP::Decode_Packet_Header($this->rcv_buf))) {

					SK_Toolkit::techo($this->peer." : invalid protocol data, closing connection");
					$this->Disconnect();
				
				} else {
			
					$this->hstatus=true;
					$this->hbuffer=$hdr;
					$this->rcv_buf=substr($this->rcv_buf, PHPOS_OMP_HDRLEN);
				
				}
			
			}

		} 

		if ($this->hstatus) {

			if (strlen($this->rcv_buf)>=$this->hbuffer["message_length"]) {

				$mcontent=substr($this->rcv_buf, 0, $this->hbuffer["message_length"]);
				$reply=$this->on_OMP_message($this->hbuffer["message_type"], unserialize($mcontent));
				$pkt=PHPOS_OMP::Build_Packet($this->hbuffer["request_id"], $this->hbuffer["message_type"], $reply);
				$this->socket->write($pkt);
			
				$this->rcv_buf=substr($this->rcv_buf, $this->hbuffer["message_length"]);
				$this->hstatus=false;
				$this->hbuffer=false;
			
			}

		}
	
	}

	function on_OMP_message($type, $content) {

		switch ($type) {

			case PHPOS_MSG_NEWINST:
			break;

			case PHPOS_MSG_BIND:

			if ($bind =& new PHPOS_S_Bind($content["oid"], $content["login"], $content["password"])) {

				$bind->container->On_Bind();
				$ret["bound"]=true;
				$ret["properties"]=get_object_vars($bind->container->content);
				$ret["methods"]=get_class_methods(get_class($bind->container->content));
				$this->binds[$content["oid"]] =& $bind;

				$bind->container->last_act_time=time();
			
			} else {

				$ret["bound"]=false;
				$ret["error"]=PHPOS_ENOTFOUND;
			
			}

			SK_Toolkit::techo($this->peer." BIND to ".$content["oid"]);

			break;

			case PHPOS_MSG_PROPGET:

			if ($bind =& $this->binds[$content["oid"]]) {

				$ret["value"]=$bind->container->content->$content["property"];
				$bind->container->last_act_time=time();

			} else {

				$ret["error"]=PHPOS_ENOTFOUND;

			}

			SK_Toolkit::techo($this->peer." GET ".$content["oid"]."::".$content["property"]);
			
			break;

			case PHPOS_MSG_PROPSET:

			if ($bind =& $this->binds[$content["oid"]]) {

				$bind->container->content->$content["property"]=$content["value"];
				$bind->container->last_act_time=time();

			} else {

				$ret["error"]=PHPOS_ENOTFOUND;
			
			}
			
			SK_Toolkit::techo($this->peer." SET ".$content["oid"]."::".$content["property"]);
			
			break;

			case PHPOS_MSG_MCALL:

			if ($bind =& $this->binds[$content["oid"]]) {

				if (method_exists($o=&$bind->container->content, $mname=$content["method"])) {
				
					$ret["return"]=call_user_func_array(array(&$o, $mname), $content["parameters"]);

				} else {

					$ret["error"]=PHPOS_ENOTFOUND;
				
				}

				$bind->container->last_act_time=time();

			} else {

				$ret["error"]=PHPOS_ENOTFOUND;

			}

			SK_Toolkit::techo($this->peer." CALL ".$content["oid"]."::".$content["method"]);
			
			break;

			case PHPOS_MSG_UNBIND:
			break;

		}

		return($ret);

	}

	function on_disconnect() {

		SK_Toolkit::techo($this->peer." disconnected");
	
	}

}

function phposd_shutdown() {

	$GLOBALS["PHPOS_Store"]->Write_Stasis_Data();
	SK_Toolkit::techo("daemon stopped (".$GLOBALS["PHPOS_Halt"].")");

	exit();

}

function phposd_term($sig_no=SIGTERM) {

	$GLOBALS["PHPOS_Halt"]="signal $sig_no received";

}

function phposd_stasis_write($sig_no=SIGHUP) {

	SK_Toolkit::techo("SIGHUP received, writing stasis data");
	$GLOBALS["PHPOS_Store"]->Write_Stasis_Data();

}

function srvlog($str, $level=PHPOS_EL_NOTICE) {

}

SK_Toolkit::techo("PHPOSd/".PHPOSD_VERSION." (c) 2003 by sIX <six@t0x.net>");

// Globals initialization

$PHPOS_Store =& new PHPOS_Store;

$PHPOS_Start_Time=time();
$PHPOS_Halt=false;
$PHPOS_Config=array();

register_shutdown_function("phposd_shutdown");

if (!$PHPOS_Config["classes"]=@parse_ini_file(PHPOSD_CONFIG_CLASSES, true)) {

	SK_Toolkit::techo("unable to load classes configuration file '".PHPOSD_CONFIG_CLASSES."'");
	exit(PHPOS_ENOTFOUND);

}

if (!$PHPOS_Config["store"]=@parse_ini_file(PHPOSD_CONFIG_STORE, true)) {

	SK_Toolkit::techo("unable to load store configuration file '".PHPOSD_CONFIG_STORE."'");
	exit(PHPOS_ENOTFOUND);

}

if (is_callable("pcntl_signal")) {

	// Catch signals

	pcntl_signal(SIGTERM, "phposd_term");
	pcntl_signal(SIGHUP, "phposd_stasis_write");

} else {

	// Or complain
	
	SK_Toolkit::techo("WARN: please --enable-pcntl in your php binary to allow catching signals");

}

// Load classes and start/wake running objects

foreach ($PHPOS_Config["classes"] as $class_name=>$class_init) {

	// Class

	if (!$i=@include_once($sf=$class_init["source_file"])) {

		SK_Toolkit::techo("unable to load source file '$sf' for class '$class_name'");
		exit(PHPOS_ENOTFOUND);

	}

	if (!$PHPOS_Store->New_Class($class_name, $cn=$class_name)) {

		SK_Toolkit::techo("unable to allocate '$class_name' for class '$cn'");
		exit(PHPOS_ENOTFOUND);
	
	}

}

foreach ($PHPOS_Config["store"] as $oid_name=>$oid_init) {

	// Running object

	if (!$PHPOS_Store->containers[$oid_init["instance_of"]]) {

		SK_Toolkit::techo("$oid_name : class '$io' not found");
		exit(PHPOS_ENOTFOUND);
	
	}

	if (!class_exists($cn=$PHPOS_Store->containers[$oid_init["instance_of"]]->class_name)) {

		SK_Toolkit::techo("$oid_name : class '$cn' undefined");
		exit(PHPOS_ENOTFOUND);
	
	}

	if (!$c=&$PHPOS_Store->New_Container($oid_name)) {

		SK_Toolkit::techo("unable to allocate OID '$oid_name'");
		exit(PHPOS_ENOTFOUND);
	
	}
	
	if ($oid_init["stasis"] && $c->Stasis_Wakeup()) {

		SK_Toolkit::techo("'$oid_name' revived and running");
	
	} else {

		$o =& new $cn;
		$c->Load_Object(&$o);

		SK_Toolkit::techo("'$oid_name' allocated and running");

	}

}

// Initialize the shadoks

$PHPOS_Server =& new SK_Server;
$PHPOS_Server->Set_Option("reuse_address", true);

if (!$PHPOS_Server->Register_Connection_Handler("tcp://".PHPOSD_DEFAULT_IFACE.":".PHPOSD_DEFAULT_PORT, "PHPOS_Server")) {

	SK_Toolkit::techo("unable to initialize network library");
	exit(PHPOS_ENOTALLOWED);

}

SK_Toolkit::techo("network library initialized, listening on port ".PHPOSD_DEFAULT_PORT);

// Main loop

while (!$PHPOS_Halt) {
	
	$PHPOS_Server->Run(1);

	declare (ticks = 1) { /* Do nothing, needed to catch signals */ }

}

?>