#!/usr/bin/php
<?php
/**
 * Small and fast system tasks trigger
 *
 * Copyright (C) 2014, 2015  Inria (Sylvain Beucler)
 *
 * This file is part of FusionForge. FusionForge 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 2 of the Licence, or (at your option)
 * any later version.
 *
 * FusionForge 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 FusionForge; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

//putenv('FUSIONFORGE_NO_PLUGINS=true');
//putenv('FUSIONFORGE_NO_DB=true');

require (dirname(__FILE__).'/../common/include/env.inc.php');
try {
        require_once $gfcommon.'include/pre.php';
} catch (DBException $e) {
        print "Cannot connect to DB: $e\nWaiting for DB...\n";
}
require_once $gfcommon.'include/cron_utils.php';

// Invalidate users/groups cache e.g. when a user is added to a group
// Special-case in 'publish-subscribe' mode
function usergroups_sync() {
	global $usergroups_lastsync;
	$res = db_query_params("SELECT MAX(last_modified_date) AS lastmodified FROM nss_usergroups");
	$row = db_fetch_array($res);
	if ($row['lastmodified'] >= $usergroups_lastsync) {
		$usergroups_lastsync = time();
		cron_reload_nscd();
		cron_regen_apache_auth();
		cron_reload_apache();
		$hook_params = array();
		plugin_hook("usergroups_sync", $hook_params);
	}
}

function systask_get_script($plugin_id, $systask_type) {
	global $cron_arr;
	if ($plugin_id == null) {
		if (isset($cron_arr[$systask_type]))
			return forge_get_config('source_path')
				.'/cronjobs/'.$cron_arr[$systask_type];
	} else {
		global $pm;
		$plugins = $pm->GetPlugins();  // reload in case a new plugin was installed
		if (isset($plugins[$plugin_id])) {
			$plugin = $pm->GetPluginObject($plugins[$plugin_id]);
			if ($plugin == null) {
				$pm->LoadPlugin($plugins[$plugin_id]);
				$plugin = $pm->GetPluginObject($plugins[$plugin_id]);
			}
			if (isset($plugin->systask_types[$systask_type]))
				return forge_get_config('plugins_path')."/".$plugin->GetName()
					."/cronjobs/".$plugin->systask_types[$systask_type];
		}
	}
	return null;
}

function is_group_active_nocache($group_id) {
	$res = db_query_params("SELECT group_id FROM nss_groups WHERE group_id=$1", array($group_id));
	return (db_numrows($res) > 0);
}


$shortopts = 'v';       // enable verbose mode
$longopts = array('verbose');
$options = getopt($shortopts, $longopts);
if (count($options) != (count($argv)-1)) {  // PHP just strips invalid options
	print "Usage: {$argv[0]} [-v|--verbose]\n";
	exit(1);
}
$verbose = false;
if (isset($options['v']) or isset($options['verbose'])) {
	print "verbose mode ON\n";
	$verbose = true;
}

// Proper daemon
posix_setsid();
chdir('/');
//umask(0); // programmers usually expect umask=0022, and this would default mkdir to 0777
umask(0022);
$log_path = forge_get_config('log_path');
if (!$verbose) {
	// Hack to reopen stdin/stdout/stderr, order is important
	// https://andytson.com/blog/2010/05/daemonising-a-php-cli-script-on-a-posix-system/
	// (prevents PHP from exiting when printing anything)
	fclose(STDIN);
	fclose(STDOUT);
	fclose(STDERR);
	$ff_stdin = fopen('/dev/null', 'r');
	//$ff_stdout = fopen('/dev/null', 'w');
	//$ff_stderr = fopen('php://stdout', 'w');
	$ff_stdout = fopen("$log_path/systasksd.stdout", 'w');
	$ff_stderr = fopen("$log_path/systasksd.stderr", 'w');
}
// We could fork & continue in the background too, but then we'd have
// to manage the PID file as well - best leave this to the init script


$pm = plugin_manager_get_object();

usergroups_sync();
while (true) {
	// Deal with pending requests
	$res = db_query_params("SELECT * FROM systasks WHERE status=$1"
						   . " ORDER BY systask_id", array('TODO'));
	if (!$res && !db_connection_status())
		db_reconnect();
	while ($arr = db_fetch_array($res)) {
		usergroups_sync();
		$script = systask_get_script($arr['plugin_id'], $arr['systask_type']);
		if (!file_exists($script))
			// Not installed on this node, skipping
			continue;
		if (!is_executable($script)) {
			db_query_params("UPDATE systasks SET status=$1, error_message=$2"
							. " WHERE systask_id=$3",
							array('ERROR',
								  "Cron job {$arr['plugin_id']}/{$arr['systask_type']}"
								  . " '$script' not executable.\n",
								  $arr['systask_id']));
			continue;
		}
		if (!empty($arr['group_id']) && !is_group_active_nocache($arr['group_id'])) {
			continue;  // wait until project is approved
		}
		db_query_params("UPDATE systasks SET status=$1, started=now() WHERE systask_id=$2",
						array('WIP', $arr['systask_id']));
		cron_acquire_lock($script);
		$ret = null;
		if ($verbose) print "Running: $script... ";
		system("$script\n", $ret);
		cron_release_lock($script);
		if ($ret == 0) {
			if ($verbose) print "DONE\n";
			db_query_params("UPDATE systasks SET status=$1, stopped=now() WHERE systask_id=$2",
							array('DONE', $arr['systask_id']));
		} else {
			if ($verbose) print "ERROR\n";
			db_query_params("UPDATE systasks SET status=$1, stopped=now() WHERE systask_id=$2",
							array('ERROR', $arr['systask_id']));
		}
	}

	usergroups_sync();

	sleep(1);
}

// Local Variables:
// mode: php
// c-file-style: "bsd"
// End:
