

//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
//_/_/
//_/_/  This file is part of PSYCLONE version 2.1.1.260726 (26 July 2026)
//_/_/  
//_/_/  developed by
//_/_/  
//_/_/  Communicative Machines Ltd. 
//_/_/    http://cmlabs.com/
//_/_/  Copyright (c) 2026
//_/_/  
//_/_/  in collaboration with 
//_/_/  
//_/_/  Icelandic Institute for Intelligent Machines ses
//_/_/    Menntavegur 1
//_/_/    101 Reykjavik
//_/_/    ICELAND
//_/_/    http://www.iiim.is/
//_/_/
//_/_/  PSYCLONE 2.1 is released under the GNU Lesser General Public License 
//_/_/  (LGPL) version 3.0, and is subject to the following
//_/_/  
//_/_/  - CADIA Clause: The license granted in and to the software under this 
//_/_/    agreement is a limited-use license. The software may not be used in 
//_/_/    furtherance of: (i) intentionally causing bodily injury or severe 
//_/_/    emotional harm to any person; (ii) invading the personal privacy or 
//_/_/    violating the human rights of any person; or (iii) committing or 
//_/_/    preparing for any act of war. 
//_/_/    
//_/_/  A commercial license for PSYCLONE 2.1 is also available to 
//_/_/  any interested party from the copyright holder.
//_/_/  
//_/_/  You can redistribute this copy of PSYCLONE 2.1 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.
//_/_/  
//_/_/  You should have received a copy of the GNU General Public License
//_/_/  and the GNU Lesser General Public License along with PSYCLONE 2.1.  
//_/_/  If not, see <http://www.gnu.org/licenses/>.
//_/_/  
//_/_/  PSYCLONE 2.1 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.
//_/_/
//_/_/  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
//_/_/  "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
//_/_/  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 
//_/_/  PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER 
//_/_/  OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
//_/_/  EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 
//_/_/  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
//_/_/  PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 
//_/_/  LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
//_/_/  NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 
//_/_/  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//_/_/
//_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/_/
#if !defined(_EXTERNALEXEC_H_)
#define _EXTERNALEXEC_H_

#pragma once

// T1.4: Auto-start and lifecycle management of external-module executables.
// See Docs/T1.4-external-autostart-spec.md for the full specification.

#include "MemoryManager.h"
#include "Utils.h"
#include "PsyTime.h"

#include <string>
#include <map>
#include <list>
#include <deque>
#include <mutex>

namespace	cmlabs{

class Node;

// Output routing modes for the child's stdout/stderr
#define EXTEXEC_OUTPUT_IGNORE		0	/* redirect to /dev/null */
#define EXTEXEC_OUTPUT_CONSOLE		1	/* inherit node console (legacy consoleoutput="yes") */
#define EXTEXEC_OUTPUT_LOGFILE		2	/* append captured lines to a log file */
#define EXTEXEC_OUTPUT_PSYCLONELOG	3	/* forward captured lines via LogPrint */
#define EXTEXEC_OUTPUT_MESSAGES		4	/* post lines as <ModuleName>.Stdout/.Stderr messages */

// Lifecycle states
#define EXTEXEC_STATE_DISABLED		0	/* enabled="no": registered but never auto-launched */
#define EXTEXEC_STATE_LAUNCHING		1	/* process started, waiting for PsySpace to become ACTIVE */
#define EXTEXEC_STATE_RUNNING		2	/* process running, space was seen ACTIVE */
#define EXTEXEC_STATE_WAITRESTART	3	/* crashed/hung, waiting for restart delay */
#define EXTEXEC_STATE_FAILED		4	/* gave up (restartlimit exceeded or autorestart="no" after failure) */
#define EXTEXEC_STATE_STOPPED		5	/* deliberately stopped (shutdown/module delete) */

// Parsed <executable> options (node-local; the shared-memory ComponentSetup
// binary contract is intentionally left unchanged).
struct ExternalExecSpec {
	ExternalExecSpec() :
		spaceID(0), moduleID(0), outputMode(EXTEXEC_OUTPUT_IGNORE),
		enabled(true), autoRestart(true), shutdownOnDelete(true),
		restartDelay(3000), restartLimit(0), restartWindow(60000),
		maxRestartDelay(30000), startupTimeout(10000),
		stopSignal(15 /*SIGTERM*/), stopTimeout(3000) {}

	std::string spaceName;		//!< External space the executable connects to.
	uint16 spaceID;				//!< ProcessMap id of the space.
	uint32 moduleID;			//!< Owning module component id.
	std::string moduleName;		//!< Owning module name.

	std::string cmdline;		//!< Command line (element text), before substitution.
	std::string workdir;		//!< Child working directory ("" = inherit).
	std::string logfile;		//!< Log file for output="logfile".
	uint8 outputMode;			//!< EXTEXEC_OUTPUT_...
	bool enabled;				//!< enabled/autostart attribute (default yes).
	bool autoRestart;			//!< autorestart attribute (default yes).
	bool shutdownOnDelete;		//!< shutdownondelete attribute (default yes).
	uint32 restartDelay;		//!< Base restart delay ms (default 3000).
	uint32 restartLimit;		//!< Max restarts per restartWindow (0 = unlimited).
	uint32 restartWindow;		//!< Sliding window ms for restartLimit (default 60000).
	uint32 maxRestartDelay;		//!< Backoff cap ms (default 30000).
	uint32 startupTimeout;		//!< Max ms to wait for space ACTIVE after launch (default 10000).
	int stopSignal;				//!< POSIX signal for graceful stop (default SIGTERM).
	uint32 stopTimeout;			//!< ms to wait after stopSignal before SIGKILL (default 3000).
	std::map<std::string, std::string> env;	//!< Extra child environment variables.
};

THREAD_RET THREAD_FUNCTION_CALL ExternalExecRun(THREAD_ARG arg);

/*! Launches and supervises external-module executables declared with
	<executable> inside a <module> bound to an external space.
	Owned by Node; one monitor thread polls all managed children. */
class ExternalExecManager {
	friend THREAD_RET THREAD_FUNCTION_CALL ExternalExecRun(THREAD_ARG arg);
public:
	ExternalExecManager(Node* node);
	~ExternalExecManager();

	//! Register (and auto-launch if enabled). Duplicate declarations for an
	//! already-managed space are ignored with a warning. @return true if accepted.
	bool registerExecutable(const ExternalExecSpec& spec);

	//! Stop (if shutdownondelete) and unregister the executable owned by a module.
	bool removeByModule(uint32 moduleID);

	//! Node shutdown: stop all children with shutdownondelete="yes" and stop the monitor thread.
	bool stopAll();

	//! Number of managed entries (for tests/diagnostics).
	uint32 count();

	//! Current state of the entry for a space name; -1 if unknown (for tests/diagnostics).
	int getState(const char* spaceName);

	//! Parse an <output>/... mode string to EXTEXEC_OUTPUT_...; -1 if invalid.
	static int ParseOutputMode(const char* str);
	//! Parse a signal name ("SIGTERM", "TERM", "15") to a signal number; -1 if invalid.
	static int ParseSignal(const char* str);

private:
	struct Entry {
		Entry() : pid(0), state(EXTEXEC_STATE_DISABLED), launchTime(0),
			restartDue(0), consecutiveFailures(0), outFD(-1), errFD(-1) {}
		ExternalExecSpec spec;
		std::string expandedCmdline;	//!< cmdline after %..% substitution.
		std::string expandedWorkdir;
		std::string expandedLogfile;
		uint32 pid;						//!< OS process id (0 = not running).
		uint8 state;					//!< EXTEXEC_STATE_...
		uint64 launchTime;				//!< Time of last launch.
		uint64 restartDue;				//!< When to attempt the next restart.
		uint32 consecutiveFailures;		//!< For exponential backoff.
		std::deque<uint64> restartTimes;//!< Restart timestamps for restartlimit/window.
		int outFD;						//!< Child stdout pipe (POSIX; -1 = none).
		int errFD;						//!< Child stderr pipe (POSIX; -1 = none).
		std::string outBuf;				//!< Partial stdout line buffer.
		std::string errBuf;				//!< Partial stderr line buffer.
	};

	bool run();							//!< Monitor thread body.
	bool launch(Entry* entry);
	bool stopChild(Entry* entry, bool graceful);
	void handleFailure(Entry* entry, const char* reason);
	void pumpOutput(Entry* entry);
	void routeLine(Entry* entry, const char* line, bool isStderr);
	std::string substitute(Entry* entry, const std::string& text);
	void closePipes(Entry* entry);

	Node* node;
	std::list<Entry*> entries;
	std::mutex mutex;
	uint32 threadID;
	bool threadRunning;
	bool shouldContinue;
};

}
#endif // _EXTERNALEXEC_H_
