PageRenderTime 52ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/common.php

http://greyhole.googlecode.com/
PHP | 940 lines | 883 code | 34 blank | 23 comment | 53 complexity | c0015b600233db3fe2526b2f30a93dd8 MD5 | raw file
Possible License(s): GPL-3.0, LGPL-2.0
  1. <?php
  2. /*
  3. Copyright 2009 Guillaume Boudreau
  4. This file is part of Greyhole.
  5. Greyhole is free software: you can redistribute it and/or modify
  6. it under the terms of the GNU General Public License as published by
  7. the Free Software Foundation, either version 3 of the License, or
  8. (at your option) any later version.
  9. Greyhole is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. GNU General Public License for more details.
  13. You should have received a copy of the GNU General Public License
  14. along with Greyhole. If not, see <http://www.gnu.org/licenses/>.
  15. */
  16. define('PERF', 9);
  17. define('TEST', 8);
  18. define('DEBUG', 7);
  19. define('INFO', 6);
  20. define('WARN', 4);
  21. define('ERROR', 3);
  22. define('CRITICAL', 2);
  23. $action = 'initialize';
  24. date_default_timezone_set(date_default_timezone_get());
  25. set_error_handler("gh_error_handler");
  26. umask(0);
  27. setlocale(LC_COLLATE, "en_US.UTF-8");
  28. setlocale(LC_CTYPE, "en_US.UTF-8");
  29. if (!defined('PHP_VERSION_ID')) {
  30. $version = explode('.', PHP_VERSION);
  31. define('PHP_VERSION_ID', ($version[0] * 10000 + $version[1] * 100 + $version[2]));
  32. }
  33. $constarray = get_defined_constants(true);
  34. foreach($constarray['user'] as $key => $val) {
  35. eval(sprintf('$_CONSTANTS[\'%s\'] = ' . (is_int($val) || is_float($val) ? '%s' : "'%s'") . ';', addslashes($key), addslashes($val)));
  36. }
  37. // Cached df results
  38. $last_df_time = 0;
  39. $last_dfs = array();
  40. $is_new_line = TRUE;
  41. $sleep_before_task = array();
  42. if (!isset($config_file)) {
  43. $config_file = '/etc/greyhole.conf';
  44. }
  45. if (!isset($smb_config_file)) {
  46. $smb_config_file = '/etc/samba/smb.conf';
  47. }
  48. $attic_share_names = array('Greyhole Attic', 'Greyhole Trash', 'Greyhole Recycle Bin');
  49. function parse_config() {
  50. global $_CONSTANTS, $storage_pool_directories, $shares_options, $minimum_free_space_pool_directories, $df_command, $config_file, $smb_config_file, $sticky_files, $db_options, $frozen_directories, $attic_share_names, $max_queued_tasks, $memory_limit;
  51. $shares_options = array();
  52. $storage_pool_directories = array();
  53. $frozen_directories = array();
  54. $config_text = file_get_contents($config_file);
  55. foreach (explode("\n", $config_text) as $line) {
  56. if (preg_match("/^[ \t]*([^=\t]+)[ \t]*=[ \t]*([^#]+)/", $line, $regs)) {
  57. $name = trim($regs[1]);
  58. $value = trim($regs[2]);
  59. if ($name[0] == '#') {
  60. continue;
  61. }
  62. switch($name) {
  63. case 'log_level':
  64. global ${$name};
  65. ${$name} = $_CONSTANTS[$value];
  66. break;
  67. case 'delete_moves_to_attic':
  68. case 'log_memory_usage':
  69. case 'balance_modified_files':
  70. case 'check_for_open_files':
  71. global ${$name};
  72. ${$name} = trim($value) === '1' || mb_strpos(strtolower(trim($value)), 'yes') !== FALSE || mb_strpos(strtolower(trim($value)), 'true') !== FALSE;
  73. break;
  74. case 'storage_pool_directory':
  75. if (preg_match("/(.*) ?, ?min_free ?: ?([0-9]+) ?gb?/i", $value, $regs)) {
  76. $storage_pool_directories[] = trim($regs[1]);
  77. $minimum_free_space_pool_directories[trim($regs[1])] = (float) trim($regs[2]);
  78. }
  79. break;
  80. case 'wait_for_exclusive_file_access':
  81. $shares = explode(',', str_replace(' ', '', $value));
  82. foreach ($shares as $share) {
  83. $shares_options[$share]['wait_for_exclusive_file_access'] = TRUE;
  84. }
  85. break;
  86. case 'sticky_files':
  87. $last_sticky_files_dir = trim($value, '/');
  88. $sticky_files[$last_sticky_files_dir] = array();
  89. break;
  90. case 'stick_into':
  91. $sticky_files[$last_sticky_files_dir][] = '/' . trim($value, '/');
  92. break;
  93. case 'frozen_directory':
  94. $frozen_directories[] = trim($value, '/');
  95. break;
  96. case 'memory_limit':
  97. ini_set('memory_limit',$value);
  98. $memory_limit = $value;
  99. default:
  100. if (mb_strpos($name, 'num_copies') === 0) {
  101. $share = mb_substr($name, 11, mb_strlen($name)-12);
  102. $shares_options[$share]['num_copies'] = (int) $value;
  103. } else if (mb_strpos($name, 'delete_moves_to_attic') === 0) {
  104. $share = mb_substr($name, 22, mb_strlen($name)-23);
  105. $shares_options[$share]['delete_moves_to_attic'] = trim($value) === '1' || mb_strpos(strtolower(trim($value)), 'yes') !== FALSE || mb_strpos(strtolower(trim($value)), 'true') !== FALSE;
  106. } else {
  107. global ${$name};
  108. if (is_numeric($value)) {
  109. ${$name} = (int) $value;
  110. } else {
  111. ${$name} = $value;
  112. }
  113. }
  114. }
  115. }
  116. }
  117. if (is_array($storage_pool_directories) && count($storage_pool_directories) > 0) {
  118. $df_command = "df -k";
  119. foreach ($storage_pool_directories as $key => $target_drive) {
  120. $df_command .= " " . escapeshellarg($target_drive);
  121. $storage_pool_directories[$key] = '/' . trim($target_drive, '/');
  122. }
  123. $df_command .= " 2>&1 | grep '%' | grep -v \"^df: .*: No such file or directory$\"";
  124. } else {
  125. gh_log(WARN, "You have no storage_pool_directory defined. Greyhole can't run.");
  126. return FALSE;
  127. }
  128. $config_text = file_get_contents($smb_config_file);
  129. foreach (explode("\n", $config_text) as $line) {
  130. $line = trim($line);
  131. if (mb_strlen($line) == 0) { continue; }
  132. if ($line[0] == '[' && preg_match('/\[([^\]]+)\]/', $line, $regs)) {
  133. $share_name = $regs[1];
  134. }
  135. if (isset($share_name) && !isset($shares_options[$share_name]) && array_search($share_name, $attic_share_names) === FALSE) { continue; }
  136. if (isset($share_name) && preg_match('/^\s*path[ \t]*=[ \t]*(.+)$/i', $line, $regs)) {
  137. $shares_options[$share_name]['landing_zone'] = '/' . trim($regs[1], '/');
  138. $shares_options[$share_name]['name'] = $share_name;
  139. }
  140. }
  141. foreach ($shares_options as $share_name => $share_options) {
  142. if (array_search($share_name, $attic_share_names) !== FALSE) {
  143. global $attic_share;
  144. $attic_share = array('name' => $share_name, 'landing_zone' => $shares_options[$share_name]['landing_zone']);
  145. unset($shares_options[$share_name]);
  146. continue;
  147. }
  148. if ($share_options['num_copies'] > count($storage_pool_directories)) {
  149. $share_options['num_copies'] = count($storage_pool_directories);
  150. $shares_options[$share_name] = $share_options;
  151. }
  152. if (!isset($share_options['landing_zone'])) {
  153. global $config_file, $smb_config_file;
  154. gh_log(WARN, "Found a share ($share_name) defined in $config_file with no path in $smb_config_file. Either add this share in $smb_config_file, or remove it from $config_file, then restart Greyhole.");
  155. return FALSE;
  156. }
  157. // Validate that the landing zone is NOT a subdirectory of a storage pool directory!
  158. foreach ($storage_pool_directories as $key => $target_drive) {
  159. if (mb_strpos($share_options['landing_zone'], $target_drive) === 0) {
  160. gh_log(CRITICAL, "Found a share ($share_name), with path " . $share_options['landing_zone'] . ", which is INSIDE a storage pooled directory ($target_drive). Share directories should never be inside a directory that you have in your storage pool.\nFor your shares to use your storage pool, you just need them to have 'vfs objects = greyhole' in their (smb.conf) config; their location on your file system is irrelevant.");
  161. }
  162. }
  163. }
  164. if (!isset($db_engine)) {
  165. $db_engine = 'mysql';
  166. } else {
  167. $db_engine = mb_strtolower($db_engine);
  168. }
  169. global ${"db_use_$db_engine"};
  170. ${"db_use_$db_engine"} = TRUE;
  171. if (!isset($max_queued_tasks)) {
  172. if ($db_engine == 'sqlite') {
  173. $max_queued_tasks = 1000;
  174. } else {
  175. $max_queued_tasks = 100000;
  176. }
  177. }
  178. if (!isset($memory_limit)) {
  179. $memory_limit = '128M';
  180. ini_set('memory_limit',$memory_limit);
  181. }
  182. if (isset($memory_limit)){
  183. if(preg_match('/M$/',$memory_limit)){
  184. $memory_limit = preg_replace('/M$/','',$memory_limit);
  185. $memory_limit = $memory_limit * 1048576;
  186. }elseif(preg_match('/K$/',$memory_limit)){
  187. $memory_limit = preg_replace('/K$/','',$memory_limit);
  188. $memory_limit = $memory_limit * 1024;
  189. }
  190. }
  191. $db_options = (object) array(
  192. 'engine' => $db_engine,
  193. 'schema' => "/usr/share/greyhole/schema-$db_engine.sql"
  194. );
  195. if ($db_options->engine == 'sqlite') {
  196. $db_options->db_path = $db_path;
  197. $db_options->dbh = null; // internal handle to use with sqlite
  198. } else {
  199. $db_options->host = $db_host;
  200. $db_options->user = $db_user;
  201. $db_options->pass = $db_pass;
  202. $db_options->name = $db_name;
  203. }
  204. if (!isset($balance_modified_files)) {
  205. global $balance_modified_files;
  206. $balance_modified_files = FALSE;
  207. }
  208. return TRUE;
  209. }
  210. function clean_dir($dir) {
  211. if ($dir[0] == '.' && $dir[1] == '/') {
  212. $dir = mb_substr($dir, 2);
  213. }
  214. if (mb_strpos($dir, '//') !== FALSE) {
  215. $dir = str_replace("//", "/", $dir);
  216. }
  217. return $dir;
  218. }
  219. function explode_full_path($full_path) {
  220. return array(dirname($full_path), basename($full_path));
  221. }
  222. function gh_log($local_log_level, $text, $add_line_feed=TRUE) {
  223. global $greyhole_log_file, $log_level, $is_new_line, $log_memory_usage, $action, $log_to_stdout;
  224. if ($local_log_level > $log_level) {
  225. return;
  226. }
  227. $date = date("M d H:i:s");
  228. if ($log_level >= PERF) {
  229. $utimestamp = microtime(true);
  230. $timestamp = floor($utimestamp);
  231. $date .= '.' . round(($utimestamp - $timestamp) * 1000000);
  232. }
  233. $log_text = sprintf('%s%s%s%s',
  234. $is_new_line ? "$date $local_log_level $action: " : '',
  235. $text,
  236. $add_line_feed && $log_memory_usage ? " [" . memory_get_usage() . "]" : '',
  237. $add_line_feed ? "\n": ''
  238. );
  239. $is_new_line = $add_line_feed;
  240. if (isset($log_to_stdout)) {
  241. echo $log_text;
  242. } else {
  243. @$fp_log = fopen($greyhole_log_file, 'a');
  244. if ($fp_log) {
  245. fwrite($fp_log, $log_text);
  246. fclose($fp_log);
  247. } else {
  248. error_log(trim($log_text));
  249. }
  250. }
  251. if ($local_log_level === CRITICAL) {
  252. exit(1);
  253. }
  254. }
  255. function gh_error_handler($errno, $errstr, $errfile, $errline) {
  256. if (error_reporting() === 0) {
  257. // Ignored (@) warning
  258. return TRUE;
  259. }
  260. switch ($errno) {
  261. case E_ERROR:
  262. case E_PARSE:
  263. case E_CORE_ERROR:
  264. case E_COMPILE_ERROR:
  265. gh_log(CRITICAL, "PHP Error [$errno]: $errstr in $errfile on line $errline");
  266. break;
  267. case E_WARNING:
  268. case E_COMPILE_WARNING:
  269. case E_CORE_WARNING:
  270. case E_NOTICE:
  271. global $greyhole_log_file;
  272. if ($errstr == "fopen($greyhole_log_file): failed to open stream: Permission denied") {
  273. // We want to ignore this warning. Happens when regular users try to use greyhole, and greyhole tries to log something.
  274. // What would have been logged will be echoed instead.
  275. return TRUE;
  276. }
  277. gh_log(WARN, "PHP Warning [$errno]: $errstr in $errfile on line $errline");
  278. break;
  279. default:
  280. gh_log(WARN, "PHP Unknown Error [$errno]: $errstr in $errfile on line $errline");
  281. break;
  282. }
  283. // Don't execute PHP internal error handler
  284. return TRUE;
  285. }
  286. function bytes_to_human($bytes, $html=TRUE) {
  287. $units = 'B';
  288. if (abs($bytes) > 1024) {
  289. $bytes /= 1024;
  290. $units = 'KB';
  291. }
  292. if (abs($bytes) > 1024) {
  293. $bytes /= 1024;
  294. $units = 'MB';
  295. }
  296. if (abs($bytes) > 1024) {
  297. $bytes /= 1024;
  298. $units = 'GB';
  299. }
  300. if (abs($bytes) > 1024) {
  301. $bytes /= 1024;
  302. $units = 'TB';
  303. }
  304. $decimals = (abs($bytes) > 100 ? 0 : (abs($bytes) > 10 ? 1 : 2));
  305. if ($html) {
  306. return number_format($bytes, $decimals) . " <span class=\"i18n-$units\">$units</span>";
  307. } else {
  308. return number_format($bytes, $decimals) . $units;
  309. }
  310. }
  311. function duration_to_human($seconds) {
  312. $displayable_duration = '';
  313. if ($seconds > 60*60) {
  314. $hours = floor($seconds / (60*60));
  315. $displayable_duration .= $hours . 'h ';
  316. $seconds -= $hours * (60*60);
  317. }
  318. if ($seconds > 60) {
  319. $minutes = floor($seconds / 60);
  320. $displayable_duration .= $minutes . 'm ';
  321. $seconds -= $minutes * 60;
  322. }
  323. $displayable_duration .= $seconds . 's';
  324. return $displayable_duration;
  325. }
  326. function get_share_landing_zone($share) {
  327. global $shares_options, $attic_share_names;
  328. if (isset($shares_options[$share]['landing_zone'])) {
  329. return $shares_options[$share]['landing_zone'];
  330. } else if (array_search($share, $attic_share_names) !== FALSE) {
  331. global $attic_share;
  332. return $attic_share['landing_zone'];
  333. } else {
  334. global $config_file, $smb_config_file;
  335. gh_log(WARN, " Found a share ($share) with no path in $smb_config_file, or missing it's num_copies[$share] config in $config_file. Skipping.");
  336. return FALSE;
  337. }
  338. }
  339. $arch = exec('uname -m');
  340. if ($arch != 'x86_64') {
  341. gh_log(DEBUG, "32-bit system detected: Greyhole will NOT use PHP built-in file functions.");
  342. function gh_filesize($filename) {
  343. $result = exec("stat -c %s ".escapeshellarg($filename)." 2>/dev/null");
  344. if (empty($result)) {
  345. return FALSE;
  346. }
  347. return (float) $result;
  348. }
  349. function gh_fileowner($filename) {
  350. $result = exec("stat -c %u ".escapeshellarg($filename)." 2>/dev/null");
  351. if (empty($result)) {
  352. return FALSE;
  353. }
  354. return (int) $result;
  355. }
  356. function gh_filegroup($filename) {
  357. $result = exec("stat -c %g ".escapeshellarg($filename)." 2>/dev/null");
  358. if (empty($result)) {
  359. return FALSE;
  360. }
  361. return (int) $result;
  362. }
  363. function gh_fileperms($filename) {
  364. $result = exec("stat -c %a ".escapeshellarg($filename)." 2>/dev/null");
  365. if (empty($result)) {
  366. return FALSE;
  367. }
  368. return "0" . $result;
  369. }
  370. function gh_is_file($filename) {
  371. exec('[ -f '.escapeshellarg($filename).' ]', $tmp, $result);
  372. return $result === 0;
  373. }
  374. function gh_fileinode($filename) {
  375. // This function returns deviceid_inode to make sure this value will be different for files on different devices.
  376. $result = exec("stat -c '%d_%i' ".escapeshellarg($filename)." 2>/dev/null");
  377. if (empty($result)) {
  378. return FALSE;
  379. }
  380. return (string) $result;
  381. }
  382. function gh_file_deviceid($filename) {
  383. $result = exec("stat -c '%d' ".escapeshellarg($filename)." 2>/dev/null");
  384. if (empty($result)) {
  385. return FALSE;
  386. }
  387. return (string) $result;
  388. }
  389. } else {
  390. gh_log(DEBUG, "64-bit system detected: Greyhole will use PHP built-in file functions.");
  391. function gh_filesize($filename) {
  392. return filesize($filename);
  393. }
  394. function gh_fileowner($filename) {
  395. return fileowner($filename);
  396. }
  397. function gh_filegroup($filename) {
  398. return filegroup($filename);
  399. }
  400. function gh_fileperms($filename) {
  401. return mb_substr(decoct(fileperms($filename)), -4);
  402. }
  403. function gh_is_file($filename) {
  404. return is_file($filename);
  405. }
  406. function gh_fileinode($filename) {
  407. // This function returns deviceid_inode to make sure this value will be different for files on different devices.
  408. $stat = @stat($filename);
  409. if ($stat === FALSE) {
  410. return FALSE;
  411. }
  412. return $stat['dev'] . '_' . $stat['ino'];
  413. }
  414. function gh_file_deviceid($filename) {
  415. $stat = @stat($filename);
  416. if ($stat === FALSE) {
  417. return FALSE;
  418. }
  419. return $stat['dev'];
  420. }
  421. }
  422. function memory_check(){
  423. global $memory_limit;
  424. $usage = memory_get_usage();
  425. $used = $usage/$memory_limit;
  426. $used = $used * 100;
  427. if($used > 95){
  428. gh_log(CRITICAL,$used.'% memory usage, exiting. Please increase memory_limit in greyhole.conf.');
  429. }
  430. }
  431. class tombstone_iterator implements Iterator {
  432. private $path;
  433. private $share;
  434. private $load_nok_tombstones;
  435. private $quiet;
  436. private $check_symlink;
  437. private $tombstones;
  438. private $graveyards;
  439. private $dir_handle;
  440. public function __construct($share, $path, $load_nok_tombstones=FALSE, $quiet=FALSE, $check_symlink=TRUE) {
  441. $this->quiet = $quiet;
  442. $this->share = $share;
  443. $this->path = $path;
  444. $this->check_symlink = $check_symlink;
  445. $this->load_nok_tombstones = $load_nok_tombstones;
  446. }
  447. public function rewind(){
  448. $this->graveyards = get_graveyards();
  449. $this->directory_stack = array($this->path);
  450. $this->dir_handle = NULL;
  451. $this->tombstones = array();
  452. $this->next();
  453. }
  454. public function current(){
  455. return $this->tombstones;
  456. }
  457. public function key() {
  458. return count($this->tombstones);
  459. }
  460. public function next() {
  461. $this->tombstones = array();
  462. while(count($this->directory_stack)>0 && $this->directory_stack !== NULL){
  463. $this->dir = array_pop($this->directory_stack);
  464. if($this->quiet == FALSE){
  465. gh_log(DEBUG,"Loading tombstones for (dir) " . $this->share . (!empty($this->dir) ? "/" . $this->dir : "") . "...");
  466. }
  467. for( $i = 0; $i < count($this->graveyards); $i++ ){
  468. $graveyard = $this->graveyards[$i];
  469. $this->base = "$graveyard/".$this->share."/";
  470. if(!file_exists($this->base.$this->dir)){
  471. continue;
  472. }
  473. if($this->dir_handle = opendir($this->base.$this->dir)){
  474. while (false !== ($file = readdir($this->dir_handle))){
  475. memory_check();
  476. if($file=='.' || $file=='..')
  477. continue;
  478. if(!empty($this->dir)){
  479. $full_filename = $this->dir . '/' . $file;
  480. }else
  481. $full_filename = $file;
  482. if(is_dir($this->base.$full_filename))
  483. $this->directory_stack[] = $full_filename;
  484. else{
  485. $full_filename = str_replace("$this->path/",'',$full_filename);
  486. if(isset($this->tombstones[$full_filename])) {
  487. continue;
  488. }
  489. $this->tombstones[$full_filename] = get_tombstones_for_file($this->share, "$this->dir", $file, $this->load_nok_tombstones, $this->quiet, $this->check_symlink);
  490. }
  491. }
  492. closedir($this->dir_handle);
  493. $this->directory_stack = array_unique($this->directory_stack);
  494. }
  495. }
  496. if(count($this->tombstones) > 0){
  497. break;
  498. }
  499. }
  500. gh_log(DEBUG,'Found ' . count($this->tombstones) . ' tombstones.');
  501. return $this->tombstones;
  502. }
  503. public function valid(){
  504. return count($this->tombstones) > 0;
  505. }
  506. }
  507. function _getopt ( ) {
  508. /* _getopt(): Ver. 1.3 2009/05/30
  509. My page: http://www.ntu.beautifulworldco.com/weblog/?p=526
  510. Usage: _getopt ( [$flag,] $short_option [, $long_option] );
  511. Note that another function split_para() is required, which can be found in the same
  512. page.
  513. _getopt() fully simulates getopt() which is described at
  514. http://us.php.net/manual/en/function.getopt.php , including long options for PHP
  515. version under 5.3.0. (Prior to 5.3.0, long options was only available on few systems)
  516. Besides legacy usage of getopt(), I also added a new option to manipulate your own
  517. argument lists instead of those from command lines. This new option can be a string
  518. or an array such as
  519. $flag = "-f value_f -ab --required 9 --optional=PK --option -v test -k";
  520. or
  521. $flag = array ( "-f", "value_f", "-ab", "--required", "9", "--optional=PK", "--option" );
  522. So there are four ways to work with _getopt(),
  523. 1. _getopt ( $short_option );
  524. it's a legacy usage, same as getopt ( $short_option ).
  525. 2. _getopt ( $short_option, $long_option );
  526. it's a legacy usage, same as getopt ( $short_option, $long_option ).
  527. 3. _getopt ( $flag, $short_option );
  528. use your own argument lists instead of command line arguments.
  529. 4. _getopt ( $flag, $short_option, $long_option );
  530. use your own argument lists instead of command line arguments.
  531. */
  532. if ( func_num_args() == 1 ) {
  533. $flag = $flag_array = $GLOBALS['argv'];
  534. $short_option = func_get_arg ( 0 );
  535. $long_option = array ();
  536. return getopt($short_option);
  537. } else if ( func_num_args() == 2 ) {
  538. if ( is_array ( func_get_arg ( 1 ) ) ) {
  539. $flag = $GLOBALS['argv'];
  540. $short_option = func_get_arg ( 0 );
  541. $long_option = func_get_arg ( 1 );
  542. if (PHP_VERSION_ID >= 50300) { return getopt($short_option, $long_option); }
  543. } else {
  544. $flag = func_get_arg ( 0 );
  545. $short_option = func_get_arg ( 1 );
  546. $long_option = array ();
  547. return getopt($short_option);
  548. }
  549. } else if ( func_num_args() == 3 ) {
  550. $flag = func_get_arg ( 0 );
  551. $short_option = func_get_arg ( 1 );
  552. $long_option = func_get_arg ( 2 );
  553. if (PHP_VERSION_ID >= 50300) { return getopt($short_option, $long_option); }
  554. } else {
  555. exit ( "wrong options\n" );
  556. }
  557. $short_option = trim ( $short_option );
  558. $short_no_value = array();
  559. $short_required_value = array();
  560. $short_optional_value = array();
  561. $long_no_value = array();
  562. $long_required_value = array();
  563. $long_optional_value = array();
  564. $options = array();
  565. for ( $i = 0; $i < strlen ( $short_option ); ) {
  566. if ( $short_option{$i} != ":" ) {
  567. if ( $i == strlen ( $short_option ) - 1 ) {
  568. $short_no_value[] = $short_option{$i};
  569. break;
  570. } else if ( $short_option{$i+1} != ":" ) {
  571. $short_no_value[] = $short_option{$i};
  572. $i++;
  573. continue;
  574. } else if ( $short_option{$i+1} == ":" && $short_option{$i+2} != ":" ) {
  575. $short_required_value[] = $short_option{$i};
  576. $i += 2;
  577. continue;
  578. } else if ( $short_option{$i+1} == ":" && $short_option{$i+2} == ":" ) {
  579. $short_optional_value[] = $short_option{$i};
  580. $i += 3;
  581. continue;
  582. }
  583. } else {
  584. continue;
  585. }
  586. }
  587. foreach ( $long_option as $a ) {
  588. if ( substr( $a, -2 ) == "::" ) {
  589. $long_optional_value[] = substr( $a, 0, -2);
  590. continue;
  591. } else if ( substr( $a, -1 ) == ":" ) {
  592. $long_required_value[] = substr( $a, 0, -1 );
  593. continue;
  594. } else {
  595. $long_no_value[] = $a;
  596. continue;
  597. }
  598. }
  599. if ( is_array ( $flag ) )
  600. $flag_array = $flag;
  601. else {
  602. $flag = "- $flag";
  603. $flag_array = split_para( $flag );
  604. }
  605. for ( $i = 0; $i < count( $flag_array ); ) {
  606. if ( $i >= count ( $flag_array ) )
  607. break;
  608. if ( ! $flag_array[$i] || $flag_array[$i] == "-" ) {
  609. $i++;
  610. continue;
  611. }
  612. if ( $flag_array[$i]{0} != "-" ) {
  613. $i++;
  614. continue;
  615. }
  616. if ( substr( $flag_array[$i], 0, 2 ) == "--" ) {
  617. if (strpos($flag_array[$i], '=') != false) {
  618. list($key, $value) = explode('=', substr($flag_array[$i], 2), 2);
  619. if ( in_array ( $key, $long_required_value ) || in_array ( $key, $long_optional_value ) )
  620. $options[$key][] = $value;
  621. $i++;
  622. continue;
  623. }
  624. if (strpos($flag_array[$i], '=') == false) {
  625. $key = substr( $flag_array[$i], 2 );
  626. if ( in_array( substr( $flag_array[$i], 2 ), $long_required_value ) ) {
  627. $options[$key][] = $flag_array[$i+1];
  628. $i += 2;
  629. continue;
  630. } else if ( in_array( substr( $flag_array[$i], 2 ), $long_optional_value ) ) {
  631. if ( $flag_array[$i+1] != "" && $flag_array[$i+1]{0} != "-" ) {
  632. $options[$key][] = $flag_array[$i+1];
  633. $i += 2;
  634. } else {
  635. $options[$key][] = FALSE;
  636. $i ++;
  637. }
  638. continue;
  639. } else if ( in_array( substr( $flag_array[$i], 2 ), $long_no_value ) ) {
  640. $options[$key][] = FALSE;
  641. $i++;
  642. continue;
  643. } else {
  644. $i++;
  645. continue;
  646. }
  647. }
  648. } else if ( $flag_array[$i]{0} == "-" && $flag_array[$i]{1} != "-" ) {
  649. for ( $j=1; $j < strlen($flag_array[$i]); $j++ ) {
  650. if ( in_array( $flag_array[$i]{$j}, $short_required_value ) || in_array( $flag_array[$i]{$j}, $short_optional_value )) {
  651. if ( $j == strlen($flag_array[$i]) - 1 ) {
  652. if ( in_array( $flag_array[$i]{$j}, $short_required_value ) ) {
  653. $options[$flag_array[$i]{$j}][] = $flag_array[$i+1];
  654. $i += 2;
  655. } else if ( in_array( $flag_array[$i]{$j}, $short_optional_value ) && $flag_array[$i+1] != "" && $flag_array[$i+1]{0} != "-" ) {
  656. $options[$flag_array[$i]{$j}][] = $flag_array[$i+1];
  657. $i += 2;
  658. } else {
  659. $options[$flag_array[$i]{$j}][] = FALSE;
  660. $i ++;
  661. }
  662. $plus_i = 0;
  663. break;
  664. } else {
  665. $options[$flag_array[$i]{$j}][] = substr ( $flag_array[$i], $j + 1 );
  666. $i ++;
  667. $plus_i = 0;
  668. break;
  669. }
  670. } else if ( in_array ( $flag_array[$i]{$j}, $short_no_value ) ) {
  671. $options[$flag_array[$i]{$j}][] = FALSE;
  672. $plus_i = 1;
  673. continue;
  674. } else {
  675. $plus_i = 1;
  676. break;
  677. }
  678. }
  679. $i += $plus_i;
  680. continue;
  681. }
  682. $i++;
  683. continue;
  684. }
  685. foreach ( $options as $key => $value ) {
  686. if ( count ( $value ) == 1 ) {
  687. $options[ $key ] = $value[0];
  688. }
  689. }
  690. return $options;
  691. }
  692. function split_para ( $pattern ) {
  693. /* split_para() version 1.0 2008/08/19
  694. My page: http://www.ntu.beautifulworldco.com/weblog/?p=526
  695. This function is to parse parameters and split them into smaller pieces.
  696. preg_split() does similar thing but in our function, besides "space", we
  697. also take the three symbols " (double quote), '(single quote),
  698. and \ (backslash) into consideration because things in a pair of " or '
  699. should be grouped together.
  700. As an example, this parameter list
  701. -f "test 2" -ab --required "t\"est 1" --optional="te'st 3" --option -v 'test 4'
  702. will be splited into
  703. -f
  704. t"est 2
  705. -ab
  706. --required
  707. test 1
  708. --optional=te'st 3
  709. --option
  710. -v
  711. test 4
  712. see the code below,
  713. $pattern = "-f \"test 2\" -ab --required \"t\\\"est 1\" --optional=\"te'st 3\" --option -v 'test 4'";
  714. $result = split_para( $pattern );
  715. echo "ORIGINAL PATTERN: $pattern\n\n";
  716. var_dump( $result );
  717. */
  718. $begin=0;
  719. $backslash = 0;
  720. $quote = "";
  721. $quote_mark = array();
  722. $result = array();
  723. $pattern = trim ( $pattern );
  724. for ( $end = 0; $end < strlen ( $pattern ) ; ) {
  725. if ( ! in_array ( $pattern{$end}, array ( " ", "\"", "'", "\\" ) ) ) {
  726. $backslash = 0;
  727. $end ++;
  728. continue;
  729. }
  730. if ( $pattern{$end} == "\\" ) {
  731. $backslash++;
  732. $end ++;
  733. continue;
  734. } else if ( $pattern{$end} == "\"" ) {
  735. if ( $backslash % 2 == 1 || $quote == "'" ) {
  736. $backslash = 0;
  737. $end ++;
  738. continue;
  739. }
  740. if ( $quote == "" ) {
  741. $quote_mark[] = $end - $begin;
  742. $quote = "\"";
  743. } else if ( $quote == "\"" ) {
  744. $quote_mark[] = $end - $begin;
  745. $quote = "";
  746. }
  747. $backslash = 0;
  748. $end ++;
  749. continue;
  750. } else if ( $pattern{$end} == "'" ) {
  751. if ( $backslash % 2 == 1 || $quote == "\"" ) {
  752. $backslash = 0;
  753. $end ++;
  754. continue;
  755. }
  756. if ( $quote == "" ) {
  757. $quote_mark[] = $end - $begin;
  758. $quote = "'";
  759. } else if ( $quote == "'" ) {
  760. $quote_mark[] = $end - $begin;
  761. $quote = "";
  762. }
  763. $backslash = 0;
  764. $end ++;
  765. continue;
  766. } else if ( $pattern{$end} == " " ) {
  767. if ( $quote != "" ) {
  768. $backslash = 0;
  769. $end ++;
  770. continue;
  771. } else {
  772. $backslash = 0;
  773. $cand = substr( $pattern, $begin, $end-$begin );
  774. for ( $j = 0; $j < strlen ( $cand ); $j ++ ) {
  775. if ( in_array ( $j, $quote_mark ) )
  776. continue;
  777. $cand1 .= $cand{$j};
  778. }
  779. if ( $cand1 ) {
  780. eval( "\$cand1 = \"$cand1\";" );
  781. $result[] = $cand1;
  782. }
  783. $quote_mark = array();
  784. $cand1 = "";
  785. $end ++;
  786. $begin = $end;
  787. continue;
  788. }
  789. }
  790. }
  791. $cand = substr( $pattern, $begin, $end-$begin );
  792. for ( $j = 0; $j < strlen ( $cand ); $j ++ ) {
  793. if ( in_array ( $j, $quote_mark ) )
  794. continue;
  795. $cand1 .= $cand{$j};
  796. }
  797. eval( "\$cand1 = \"$cand1\";" );
  798. if ( $cand1 )
  799. $result[] = $cand1;
  800. return $result;
  801. }
  802. ?>