1<?php
2// must be run within Dokuwiki
3if (!defined('DOKU_INC')) die();
4if (!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
5
6class helper_plugin_maintenance extends DokuWiki_Plugin {
7
8    function __construct() {
9        global $conf;
10        $this->temp_dir = $conf['tmpdir'].'/maintenance';
11        $this->script_last_log_file = $this->temp_dir.'/last.log';
12        $this->script_last_pid_file = $this->temp_dir.'/last.pid';
13        $this->script_last_script_file = $this->temp_dir.'/last.script';
14        $this->manual_lock_file = $this->temp_dir.'/.lock';
15        // put environmental variables to make shell scripts work right
16        $user = posix_getpwuid(posix_geteuid());
17        putenv('HOME='.$user['dir']);
18        putenv('USER='.$user['name']);
19        putenv('LANG='."en_US.UTF-8");
20    }
21
22    /**
23     * Gets the configured script
24     */
25    function get_script() {
26        $script = $this->getConf('script');
27        $script = str_replace(
28            array('%dokuwiki%', '%bin%'),
29            array(substr(DOKU_INC, 0, -1), dirname(__FILE__).'/bin'),
30            $script );
31        return $script;
32    }
33
34    /**
35     * Checks whether the site is currently locked
36     * @return  integer  1: locked, 0: not locked
37     */
38    function is_locked() {
39        $locks = glob($this->temp_dir.'/*.lock');
40        if (!empty($locks)) return 1;
41        if (is_file($this->temp_dir.'/.lock')) return 1;
42        return 0;
43    }
44
45    /**
46     * Runs a script and locks the site during running
47     *
48     * @return  integer  2: already run, 1: success, 0: fail
49     */
50    function script_start($script) {
51        $script_hash = sha1($script);
52        $lockfile = $this->temp_dir.'/'.$script_hash.'.lock';
53        @io_mkdir_p(dirname($lockfile));
54        $fh = fopen($lockfile, 'wb');
55        if (flock($fh, LOCK_EX | LOCK_NB)) {
56            $cmd = 'nohup bash '.escapeshellarg($script).' > '.escapeshellarg($this->script_last_log_file). ' 2>&1 & echo $!';
57            exec($cmd, $output, $result);
58            if ($result != 0) return 0;
59            file_put_contents($this->script_last_pid_file, $output[0]);
60            file_put_contents($this->script_last_script_file, $script_hash);
61            return 1;
62        }
63        return 2;
64    }
65
66    /**
67     * Kills the last started script
68     *
69     * @return  integer  2: already not run, 1: success, 0: fail
70     */
71    function script_stop() {
72        $script_hash = trim(file_get_contents($this->script_last_script_file));
73        $lockfile = $this->temp_dir.'/'.$script_hash.'.lock';
74        $result = $this->script_updatelock($lockfile);
75        if ($result != 1) return 2;
76        $pid = trim(file_get_contents($this->script_last_pid_file));
77        $cmd = "ps p $pid >&-";
78        exec($cmd, $output, $result);
79        if ($result != 0) return 2;
80        $cmd = "kill -9 $pid";
81        exec($cmd, $output, $result);
82        if ($result != 0) return 0;
83        return 1;
84    }
85
86    /**
87     * Checks whether it's time to run
88     */
89    function script_autocheck() {
90        $file = $this->script_last_pid_file;
91        $last_run = (is_file($file)) ? filemtime($file) : 0;
92        $interval = $this->getConf('script_auto_interval');
93        $now = time();
94        if ($now > $last_run+$interval) return true;
95        return false;
96    }
97
98    /**
99     * Checks all script locks
100     */
101    function script_updatelockall() {
102        // glob doesn't match hidden (started with ".") files
103        $locks = glob($this->temp_dir.'/*.lock');
104        foreach ($locks as $lock) {
105            $this->script_updatelock($lock);
106        }
107    }
108
109    /**
110     * Checks a script lock and removes it if already terminated
111     *
112     * @return  integer  3: already no lock, 2: terminated and removed, 1: not terminated, 0: terminated and failed to remove
113     */
114    function script_updatelock($lockfile) {
115        if (!is_file($lockfile)) return 3;
116        $fh = fopen($lockfile, 'wb');
117        if (flock($fh, LOCK_EX | LOCK_NB)) {
118            @flock($fh, LOCK_UN);
119            @unlink($lockfile);
120            if (!is_file($lockfile)) return 2;
121            return 0;
122        }
123        return 1;
124    }
125
126    /**
127     * Locks the site manually, must unlock manually
128     *
129     * @return  integer  2: already locked, 1: success, 0: fail
130     */
131    function manual_lock() {
132        if (is_file($this->manual_lock_file)) return 2;
133        @io_mkdir_p(dirname($this->manual_lock_file));
134        @touch($this->manual_lock_file);
135        if (is_file($this->manual_lock_file)) return 1;
136        return 0;
137    }
138
139    /**
140     * Removes a manual lock
141     *
142     * @return  integer  2: already no lock, 1: success, 0: fail
143     */
144    function manual_unlock() {
145        if (!is_file($this->manual_lock_file)) return 2;
146        @unlink($this->manual_lock_file);
147        if (!is_file($this->manual_lock_file)) return 1;
148        return 0;
149    }
150
151}
152