Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/* vim: set noai expandtab tabstop=4 softtabstop=4 shiftwidth=4: */
3
/**
4
 * System_Daemon turns PHP-CLI scripts into daemons.
5
 *
6
 * PHP version 5
7
 *
8
 * @category  System
9
 * @package   System_Daemon
10
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
11
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
12
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
13
 * @version   SVN: Release: $Id: OS.php 186 2010-05-10 13:10:04Z tiefland $
14
 * @link      http://trac.plutonia.nl/projects/system_daemon
15
 */
16
 
17
/**
18
 * Operating System focussed functionality.
19
 *
20
 * @category  System
21
 * @package   System_Daemon
22
 * @author    Kevin van Zonneveld <kevin@vanzonneveld.net>
23
 * @copyright 2008 Kevin van Zonneveld (http://kevin.vanzonneveld.net)
24
 * @license   http://www.opensource.org/licenses/bsd-license.php New BSD Licence
25
 * @version   SVN: Release: $Id: OS.php 186 2010-05-10 13:10:04Z tiefland $
26
 * @link      http://trac.plutonia.nl/projects/system_daemon
27
 *
28
 */
29
class System_Daemon_OS
30
{
31
    /**
32
     * Holds errors
33
     *
34
     * @var array
35
     */
36
    public $errors = array();
37
 
38
 
39
    /**
40
     * Template path
41
     *
42
     * @var string
43
     */
44
    protected $_autoRunTemplatePath = "";
45
 
46
    /**
47
     * Replace the following keys with values to convert a template into
48
     * a read autorun script
49
     *
50
     * @var array
51
     */
52
    protected $_autoRunTemplateReplace = array();
53
 
54
    /**
55
     * Path of init.d scripts
56
     *
57
     * @var string
58
     */
59
    protected $_autoRunDir;
60
 
61
    /**
62
     * Hold OS information
63
     *
64
     * @var array
65
     */
66
    protected $_osDetails = array();
67
 
68
 
69
 
70
    /**
71
     * Constructor
72
     * Only ran by instantiated OS Drivers
73
     */
74
    public function __construct()
75
    {
76
        // Up to date filesystem information
77
        clearstatcache();
78
 
79
        // Get ancestors
80
        $ancs = System_Daemon_OS::_getAncestors($this);
81
        foreach ($ancs as $i=>$anc) {
82
            $ancs[$i] = System_Daemon_OS::_getShortHand($anc);
83
        }
84
 
85
        // Set OS Details
86
        $this->_osDetails["shorthand"] = $this->_getShortHand(get_class($this));
87
        $this->_osDetails["ancestors"] = $ancs;
88
    }
89
 
90
    /**
91
     * Loads all the drivers and returns the one for the most specifc OS
92
     *
93
     * @param mixed   $force_os boolean or string when you want to enforce an OS
94
     * for testing purposes. CAN BE VERY DANGEROUS IF WRONG OS IS SPECIFIED!
95
     * Will otherwise autodetect OS.
96
     * @param boolean $retried  used internally to find out wether we are retrying
97
     *
98
     * @return object
99
     */
100
    public function &factory($force_os = false, $retried = false)
101
    {
102
        $drivers      = array();
103
        $driversValid = array();
104
        $class_prefix = "System_Daemon_OS_";
105
 
106
        // Load all drivers
107
        $driver_dir = realpath(dirname(__FILE__)."/OS");
108
        foreach (glob($driver_dir."/*.php") as $driver_path) {
109
            // Set names
110
            $driver = basename($driver_path, ".php");
111
            $class  = $class_prefix.$driver;
112
 
113
            // Only do this for real drivers
114
            if ($driver == "Exception" || !is_file($driver_path)) {
115
                continue;
116
            }
117
 
118
            // Let SPL include & load the driver or Report errors
119
            if (!class_exists($class, true)) {
120
                $this->errors[] = "Class ".$class." does not exist";
121
                return false;
122
            }
123
 
124
            // Save in drivers array
125
            $drivers[$class] = new $class;
126
        }
127
 
128
        // Determine which one to use
129
        if ($force_os !== false) {
130
            // Let's use the Forced OS. This could be dangerous
131
            $use_name = $class_prefix.$force_os;
132
        } else {
133
            // What OSes are valid for this system?
134
            // e.g. Debian makes Linux valid as well
135
            foreach ($drivers as $class=>$obj) {
136
                // Save in Installed container
137
                if (call_user_func(array($obj, "isInstalled"))) {
138
                    $driversValid[$class] = $obj;
139
                }
140
            }
141
 
142
            // What's the most specific OS?
143
            // e.g. Ubuntu > Debian > Linux
144
            $use_name = System_Daemon_OS::_mostSpecific($driversValid);
145
        }
146
 
147
        // If forced driver wasn't found, retry to autodetect it
148
        if (!isset($drivers[$use_name])) {
149
            // Make sure we don't build a loop
150
            if (!$retried) {
151
                $obj           = System_Daemon_OS::factory(false, true);
152
                $obj->errors[] = "Unable to use driver: ".$force_os." falling ".
153
                    "back to autodetection.";
154
            } else {
155
                $obj = false;
156
            }
157
        } else {
158
            $obj = $drivers[$use_name];
159
        }
160
 
161
        return $obj;
162
    }
163
 
164
 
165
 
166
    /**
167
     * Determines wether the system is compatible with this OS
168
     *
169
     * @return boolean
170
     */
171
    public function isInstalled()
172
    {
173
        $this->errors[] = "Not implemented for OS";
174
        return false;
175
    }
176
 
177
    /**
178
     * Returns array with all the specific details of the loaded OS
179
     *
180
     * @return array
181
     */
182
    public function getDetails()
183
    {
184
        return $this->_osDetails;
185
    }
186
 
187
    /**
188
     * Returns a template path to base the autuRun script on.
189
     * Uses $autoRunTemplatePath if possible.
190
     *
191
     * @param array $properties Additional properties
192
     *
193
     * @return unknown
194
     * @see autoRunTemplatePath
195
     */
196
    public function getAutoRunTemplatePath($properties = array())
197
    {
198
        $path = $this->_autoRunTemplatePath;
199
 
200
        if (!empty($properties['runTemplateLocation'])) {
201
            $path = $properties['runTemplateLocation'];
202
        }
203
 
204
        if (!$path) {
205
            $this->errors[] = "No autoRunTemplatePath found";
206
            return false;
207
        }
208
 
209
        // Replace variable: #datadir#
210
        // with actual package datadir
211
        // this enables predefined templates for e.g. redhat & bsd
212
        if (false !== strpos($path, '#datadir#')) {
213
            $dataDir = $this->getDataDir();
214
            if (false === $dataDir) {
215
                return false;
216
            }
217
            $path = str_replace('#datadir#', $dataDir, $path);
218
        }
219
 
220
        return $path;
221
    }
222
 
223
    /**
224
     * Returns the directory where data is stored (like init.d templates)
225
     * Could be PEAR's data directory but could also be a local one.
226
     *
227
     * @return string
228
     */
229
    public function getDataDir()
230
    {
231
        $tried_dirs = array();
232
 
233
        if (class_exists('PEAR_Config', true)) {
234
            $config = PEAR_Config::singleton();
235
            if (PEAR::isError($config)) {
236
                $this->errors[] = $config->getMessage();
237
                return false;
238
            }
239
 
240
            $try_dir = realpath(
241
                $config->get('data_dir').
242
                '/System_Daemon/data'
243
            );
244
            if (!is_dir($try_dir)) {
245
                $tried_dirs[] = $try_dir;
246
            } else {
247
                $dir = $try_dir;
248
            }
249
        }
250
 
251
        if (!$dir) {
252
            $try_dir = realpath(dirname(__FILE__).'/../../data');
253
            if (!is_dir($try_dir)) {
254
                $tried_dirs[] = $try_dir;
255
            } else {
256
                $dir = $try_dir;
257
            }
258
        }
259
 
260
        if (!$dir) {
261
            $this->errors[] = 'No data dir found in either: '.
262
            implode(' or ', $tried_dirs);
263
            return false;
264
        }
265
 
266
        return $dir;
267
    }
268
 
269
    /**
270
     * Returns OS specific path to autoRun file
271
     *
272
     * @param string $appName Unix-proof name of daemon
273
     *
274
     * @return string
275
     */
276
    public function getAutoRunPath($appName)
277
    {
278
        if (empty($this->_autoRunDir)) {
279
            $this->errors[] = "autoRunDir is not set";
280
            return false;
281
        }
282
 
283
        $path = $this->_autoRunDir."/".$appName;
284
 
285
        // Path exists
286
        if (!is_dir($dir = dirname($path))) {
287
            $this->errors[] = "Directory: '".$dir."' does not exist. ".
288
                "How can this be a correct path?";
289
            return false;
290
        }
291
 
292
        // Is writable?
293
        if (!self::isWritable($dir)) {
294
            $this->errors[] = "Directory: '".$dir."' is not writable. ".
295
                "Maybe run as root (now: " . getmyuid() . ")?";
296
            return false;
297
        }
298
 
299
        return $path;
300
    }
301
 
302
    /**
303
     * A 'better' is_writable. Taken from PHP.NET comments:
304
     * http://nl.php.net/manual/en/function.is-writable.php#73596
305
     * Will work in despite of Windows ACLs bug
306
     * NOTE: use a trailing slash for folders!!!
307
     * see http://bugs.php.net/bug.php?id=27609
308
     * see http://bugs.php.net/bug.php?id=30931
309
     *
310
     * @param string $path Path to test
311
     *
312
     * @return boolean
313
     */
314
    public static function isWritable($path)
315
    {
316
        if ($path{strlen($path)-1} === '/') {
317
            //// recursively return a temporary file path
318
            return self::isWritable($path.uniqid(mt_rand()).'.tmp');
319
        } else if (is_dir($path)) {
320
            return self::isWritable($path.'/'.uniqid(mt_rand()).'.tmp');
321
        }
322
        // check tmp file for read/write capabilities
323
        if (($rm = file_exists($path))) {
324
            $f = fopen($path, 'a');
325
        } else {
326
            $f = fopen($path, 'w');
327
        }
328
        if ($f === false) {
329
            print_r($path);
330
            return false;
331
        }
332
        @fclose($f);
333
        if (!$rm) {
334
            unlink($path);
335
        }
336
        return true;
337
    }
338
 
339
    /**
340
     * Returns a template to base the autuRun script on.
341
     * Uses $autoRunTemplatePath if possible.
342
     *
343
     * @param array $properties Contains the daemon properties
344
     *
345
     * @return unknown
346
     * @see autoRunTemplatePath
347
     */
348
    public function getAutoRunTemplate($properties)
349
    {
350
        if (($path = $this->getAutoRunTemplatePath($properties)) === false) {
351
            return false;
352
        }
353
 
354
        if (!file_exists($path)) {
355
            $this->errors[] = "autoRunTemplatePath: ".
356
            $path." does not exist";
357
            return false;
358
        }
359
 
360
        return file_get_contents($path);
361
    }
362
 
363
    /**
364
     * Uses properties to enrich the autuRun Template
365
     *
366
     * @param array $properties Contains the daemon properties
367
     *
368
     * @return mixed string or boolean on failure
369
     */
370
    public function getAutoRunScript($properties)
371
    {
372
 
373
        // All data in place?
374
        if (($template = $this->getAutoRunTemplate($properties)) === false) {
375
            return false;
376
        }
377
        if (!$this->_autoRunTemplateReplace
378
            || !is_array($this->_autoRunTemplateReplace)
379
            || !count($this->_autoRunTemplateReplace)
380
        ) {
381
            $this->errors[] = "No autoRunTemplateReplace found";
382
            return false;
383
        }
384
 
385
        // Replace System specific keywords with Universal placeholder keywords
386
        $script = str_replace(
387
            array_keys($this->_autoRunTemplateReplace),
388
            array_values($this->_autoRunTemplateReplace),
389
            $template
390
        );
391
 
392
        // Replace Universal placeholder keywords with Daemon specific properties
393
        if (!preg_match_all('/(\{PROPERTIES([^\}]+)\})/is', $script, $r)) {
394
            $this->errors[] = "No PROPERTIES found in autoRun template";
395
            return false;
396
        }
397
 
398
        $placeholders = $r[1];
399
        array_unique($placeholders);
400
        foreach ($placeholders as $placeholder) {
401
            // Get var
402
            $var = str_replace(array("{PROPERTIES.", "}"), "", $placeholder);
403
 
404
            // Replace placeholder with actual daemon property
405
            $script = str_replace($placeholder, $properties[$var], $script);
406
        }
407
 
408
        return $script;
409
    }
410
 
411
    /**
412
     * Writes an: 'init.d' script on the filesystem
413
     * combining
414
     *
415
     * @param array   $properties Contains the daemon properties
416
     * @param boolean $overwrite  Wether to overwrite when the file exists
417
     *
418
     * @return mixed string or boolean on failure
419
     * @see getAutoRunScript()
420
     * @see getAutoRunPath()
421
     */
422
    public function writeAutoRun($properties, $overwrite = false)
423
    {
424
        // Check properties
425
        if ($this->_testAutoRunProperties($properties) === false) {
426
            // Explaining errors should have been generated by
427
            // previous function already
428
            return false;
429
        }
430
 
431
        // Get script body
432
        if (($body = $this->getAutoRunScript($properties)) === false) {
433
            // Explaining errors should have been generated by
434
            // previous function already
435
            return false;
436
        }
437
 
438
        // Get script path
439
        if (($path = $this->getAutoRunPath($properties["appName"])) === false) {
440
            // Explaining errors should have been generated by
441
            // previous function already
442
            return false;
443
        }
444
 
445
        // Overwrite?
446
        if (file_exists($path) && !$overwrite) {
447
            return true;
448
        }
449
 
450
        // Write
451
        if (!file_put_contents($path, $body)) {
452
            $this->errors[] =  "startup file: '".
453
            $path."' cannot be ".
454
                "written to. Check the permissions";
455
            return false;
456
        }
457
 
458
        // Chmod
459
        if (!chmod($path, 0777)) {
460
            $this->errors[] =  "startup file: '".
461
            $path."' cannot be ".
462
                "chmodded. Check the permissions";
463
            return false;
464
        }
465
 
466
 
467
        return $path;
468
    }
469
 
470
 
471
 
472
    /**
473
     * Sets daemon specific properties
474
     *
475
     * @param array $properties Contains the daemon properties
476
     *
477
     * @return array
478
     */
479
    protected function _testAutoRunProperties($properties = false)
480
    {
481
        $required_props = array(
482
            "appName",
483
            "appExecutable",
484
            "appDescription",
485
            "appDir",
486
            "authorName",
487
            "authorEmail"
488
        );
489
 
490
        // Valid array?
491
        if (!is_array($properties) || !count($properties)) {
492
            $this->errors[] = "No properties to ".
493
                "forge init.d script";
494
            return false;
495
        }
496
 
497
        // Check if all required properties are available
498
        $success = true;
499
        foreach ($required_props as $required_prop) {
500
            if (!isset($properties[$required_prop])) {
501
                $this->errors[] = "Cannot forge an ".
502
                    "init.d script without a valid ".
503
                    "daemon property: ".$required_prop;
504
                $success        = false;
505
                continue;
506
            }
507
        }
508
 
509
        // Path to daemon
510
        $daemon_filepath = $properties["appDir"] . "/" .
511
            $properties["appExecutable"];
512
 
513
        // Path to daemon exists?
514
        if (!file_exists($daemon_filepath)) {
515
            $this->errors[] = "unable to forge startup script for non existing ".
516
                "daemon_filepath: ".$daemon_filepath.", try setting a valid ".
517
                "appDir or appExecutable";
518
            $success        = false;
519
        }
520
 
521
        // Path to daemon is executable?
522
        if (!is_executable($daemon_filepath)) {
523
            $this->errors[] = "unable to forge startup script. ".
524
                "daemon_filepath: ".$daemon_filepath.", needs to be executable ".
525
                "first";
526
            $success        = false;
527
        }
528
 
529
        return $success;
530
 
531
    }
532
 
533
    /**
534
     * Determines how specific an operating system is.
535
     * e.g. Ubuntu is more specific than Debian is more
536
     * specific than Linux is more specfic than Common.
537
     * Determined based on class hierarchy.
538
     *
539
     * @param array $classes Array with keys with classnames
540
     *
541
     * @return string
542
     */
543
    protected function _mostSpecific($classes)
544
    {
545
        $weights = array_map(
546
            array("System_Daemon_OS", "_getAncestorCount"),
547
            $classes
548
        );
549
        arsort($weights);
550
        $fattest = reset(array_keys($weights));
551
        return $fattest;
552
    }
553
 
554
    /**
555
     * Extracts last part of a classname. e.g. System_Daemon_OS_Ubuntu -> Ubuntu
556
     *
557
     * @param string $class Full classname
558
     *
559
     * @return string
560
     */
561
    protected function _getShortHand($class)
562
    {
563
        if (!is_string($class) || ! $class ) {
564
            return false;
565
        }
566
        $parts = explode("_", $class);
567
        return end($parts);
568
    }
569
 
570
    /**
571
     * Get the total parent count of a class
572
     *
573
     * @param string $class Full classname or instance
574
     *
575
     * @return integer
576
     */
577
    protected function _getAncestorCount($class)
578
    {
579
        return count(System_Daemon_OS::_getAncestors($class));
580
    }
581
 
582
    /**
583
     * Get an array of parent classes
584
     *
585
     * @param string $class Full classname or instance
586
     *
587
     * @return array
588
     */
589
    protected function _getAncestors($class)
590
    {
591
        $classes = array();
592
        while ($class = get_parent_class($class)) {
593
            $classes[] = $class;
594
        }
595
        return $classes;
596
    }
597
}