238 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			238 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| namespace nur;
 | |
| 
 | |
| use nur\b\IllegalAccessException;
 | |
| 
 | |
| /**
 | |
|  * Class shell: outils pour gérer l'interaction avec le shell unix
 | |
|  */
 | |
| class shell {
 | |
|   static final function _quote(string $value): string {
 | |
|     if (preg_match('/^[a-zA-Z0-9_.@:,\/+-]+$/', $value)) return $value;
 | |
|     return escapeshellarg($value);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * obtenir la valeur $value quotée pour le shell
 | |
|    *
 | |
|    * si c'est une valeur scalaire, elle sera quotée sous la forme 'value'
 | |
|    * si c'est un tableau séquentiel, elle sera quoté sous la forme ('value'...)
 | |
|    * sinon, elle sera quotée sour la forme ([key]=value...)
 | |
|    */
 | |
|   static final function quote($value): string {
 | |
|     if (is_array($value)) {
 | |
|       if (A::is_seq($value)) {
 | |
|         $parts = [];
 | |
|         foreach ($value as $part) {
 | |
|           $parts[] = self::_quote(strval($part));
 | |
|         }
 | |
|         return "(".implode(" ", $parts).")";
 | |
|       } else {
 | |
|         $parts = [];
 | |
|         foreach ($value as $key => $part) {
 | |
|           $key = self::_quote($key);
 | |
|           $val = self::_quote($part);
 | |
|           $parts[] = "[$key]=$val";
 | |
|         }
 | |
|         return "(".implode(" ", $parts).")";
 | |
|       }
 | |
|     } else {
 | |
|       return self::_quote(strval($value));
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * obtenir une commande shell à partir du tableau des arguments.
 | |
|    * à utiliser avec exec()
 | |
|    */
 | |
|   static final function join(array $parts): string {
 | |
|     $count = count($parts);
 | |
|     for($i = 0; $i < $count; $i++) {
 | |
|       $parts[$i] = self::_quote(strval($parts[$i]));
 | |
|     }
 | |
|     return implode(" ", $parts);
 | |
|   }
 | |
| 
 | |
|   private static function add_redir(string &$cmd, ?string $redir, ?string $input, ?string $output): void {
 | |
|     if ($redir !== null) {
 | |
|       switch ($redir) {
 | |
|       case "outonly":
 | |
|       case "noerr":
 | |
|         $redir = "2>/dev/null";
 | |
|         break;
 | |
|       case "erronly":
 | |
|       case "noout":
 | |
|         $redir = "2>&1 >/dev/null";
 | |
|         break;
 | |
|       case "both":
 | |
|       case "err2out":
 | |
|         $redir = "2>&1";
 | |
|         break;
 | |
|       case "none":
 | |
|       case "null":
 | |
|         $redir = ">/dev/null 2>&1";
 | |
|         break;
 | |
|       case "default":
 | |
|         $redir = null;
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
|     if ($input !== null) {
 | |
|       $redir = $redir !== null? "$redir ": "";
 | |
|       $redir .= "<".escapeshellarg($input); 
 | |
|     }
 | |
|     if ($output !== null) {
 | |
|       $redir = $redir !== null? "$redir ": "";
 | |
|       $redir .= ">".escapeshellarg($output);
 | |
|     }
 | |
|     if ($redir !== null) $cmd .= " $redir";
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Corriger la commande $cmd:
 | |
|    * - si c'est tableau, joindre les arguments avec {@link join()}
 | |
|    * - sinon, mettre les caractères en échappement avec {@link escapeshellarg()}
 | |
|    */
 | |
|   static final function fix_cmd(&$cmd, ?string $redir=null, ?string $input=null, ?string $output=null): void {
 | |
|     if (is_array($cmd)) $cmd = self::join($cmd);
 | |
|     else $cmd = escapeshellcmd(strval($cmd));
 | |
|     self::add_redir($cmd, $redir, $input, $output);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Lancer la commande spécifiée avec passthru() et retourner le code de retour
 | |
|    * dans la variable $retcode. $cmd doit déjà être formaté comme il convient
 | |
|    *
 | |
|    * voici la différence entre system(), passthru() et exec()
 | |
|    * +----------------+-----------------+----------------+----------------+
 | |
|    * |    Command     | Displays Output | Can Get Output | Gets Exit Code |
 | |
|    * +----------------+-----------------+----------------+----------------+
 | |
|    * | passthru()     | Yes (raw)       | No             | Yes            |
 | |
|    * | system()       | Yes (as text)   | Last line only | Yes            |
 | |
|    * | exec()         | No              | Yes (array)    | Yes            |
 | |
|    * +----------------+-----------------+----------------+----------------+
 | |
|    *
 | |
|    * @return bool true si la commande s'est lancée sans erreur, false sinon
 | |
|    */
 | |
|   static final function _passthru(string $cmd, int &$retcode=null): bool {
 | |
|     passthru($cmd, $retcode);
 | |
|     return $retcode == 0;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Comme {@link _passthru()} mais lancer la commande spécifiée avec system().
 | |
|    * cf la doc de {@link _passthru()} pour les autres détails
 | |
|    */
 | |
|   static final function _system(string $cmd, string &$output=null, int &$retcode=null): bool {
 | |
|     $last_line = system($cmd, $retcode);
 | |
|     if ($last_line !== false) $output = $last_line;
 | |
|     return $retcode == 0;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Comme {@link _passthru()} mais lancer la commande spécifiée avec exec().
 | |
|    * cf la doc de {@link _passthru()} pour les autres détails
 | |
|    */
 | |
|   static final function _exec(string $cmd, array &$output=null, int &$retcode=null): bool {
 | |
|     exec($cmd, $output, $retcode);
 | |
|     return $retcode == 0;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Lancer la commande $cmd dans un processus fils via un shell et attendre la
 | |
|    * fin de son exécution.
 | |
|    *
 | |
|    * $cmd doit déjà être formaté comme il convient
 | |
|    */
 | |
|   static final function _fork_exec(string $cmd, int &$retcode=null): bool {
 | |
|     $pid = pcntl_fork();
 | |
|     if ($pid == -1) {
 | |
|       // parent, impossible de forker
 | |
|       throw new IllegalAccessException("unable to fork");
 | |
|     } elseif ($pid) {
 | |
|       // parent, fork ok
 | |
|       pcntl_waitpid($pid, $status);
 | |
|       if (pcntl_wifexited($status)) {
 | |
|         $retcode = pcntl_wexitstatus($status);
 | |
|       } else {
 | |
|         $retcode = 127;
 | |
|       }
 | |
|       return $retcode == 0;
 | |
|     }
 | |
|     // child, fork ok
 | |
|     pcntl_exec("/bin/sh", ["-c", $cmd]);
 | |
|     return false;
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Corriger la commande spécifiée avec {@link fix_cmd()} puis la lancer
 | |
|    * avec passthru() et retourner le code de retour dans la variable $retcode
 | |
|    *
 | |
|    * $redir spécifie le type de redirection demandée:
 | |
|    * - "default" | null: $output reçoit STDOUT et STDERR n'est pas redirigé
 | |
|    * - "outonly" | "noerr": $output ne reçoit que STDOUT et STDERR est perdu
 | |
|    * - "erronly" | "noout": $output ne reçoit que STDERR et STDOUT est perdu
 | |
|    * - "both" | "err2out": $output reçoit STDOUT et STDERR
 | |
|    * - "none" | "null": STDOUT et STDERR sont perdus
 | |
|    * - sinon c'est une redirection spécifique, et la valeur est rajoutée telle
 | |
|    * quelle à la ligne de commande
 | |
|    *
 | |
|    * @return bool true si la commande s'est lancée sans erreur, false sinon
 | |
|    */
 | |
|   static final function passthru($cmd, int &$retcode=null, ?string $redir=null): bool {
 | |
|     self::fix_cmd($cmd, $redir);
 | |
|     return self::_passthru($cmd, $retcode);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Comme {@link passthru()} mais lancer la commande spécifiée avec system().
 | |
|    * Cf la doc de {@link passthru()} pour les autres détails
 | |
|    */
 | |
|   static final function system($cmd, string &$output=null, int &$retcode=null, ?string $redir=null): bool {
 | |
|     self::fix_cmd($cmd, $redir);
 | |
|     return self::_system($cmd, $output, $retcode);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Comme {@link passthru()} mais lancer la commande spécifiée avec exec().
 | |
|    * Cf la doc de {@link passthru()} pour les autres détails
 | |
|    */
 | |
|   static final function exec($cmd, array &$output=null, int &$retcode=null, ?string $redir=null): bool {
 | |
|     self::fix_cmd($cmd, $redir);
 | |
|     return self::_exec($cmd, $output, $retcode);
 | |
|   }
 | |
| 
 | |
|   /**
 | |
|    * Corriger la commande spécifiée avec {@link fix_cmd()}, la préfixer de
 | |
|    * "exec" puis la lancer avec {@link _fork_exec()}
 | |
|    */
 | |
|   static final function fork_exec($cmd, int &$retcode=null, ?string $redir=null): bool {
 | |
|     self::fix_cmd($cmd, $redir);
 | |
|     return self::_fork_exec("exec $cmd", $retcode);
 | |
|   }
 | |
| 
 | |
|   #############################################################################
 | |
| 
 | |
|   static final function is_diff_file(string $f, string $g): bool {
 | |
|     if (!is_file($f) || !is_file($g)) return true;
 | |
|     self::exec(array("diff", "-q", $f, $g), $output, $retcode);
 | |
|     return $retcode !== 0;
 | |
|   }
 | |
| 
 | |
|   static final function is_same_file(string $f, string $g): bool {
 | |
|     if (!is_file($f) || !is_file($g)) return false;
 | |
|     self::exec(array("diff", "-q", $f, $g), $output, $retcode);
 | |
|     return $retcode === 0;
 | |
|   }
 | |
| 
 | |
|   static final function is_diff_link(string $f, string $g): bool {
 | |
|     if (!is_link($f) || !is_link($g)) return true;
 | |
|     return @readlink($f) !== @readlink($g);
 | |
|   }
 | |
| 
 | |
|   static final function is_same_link(string $f, string $g): bool {
 | |
|     if (!is_link($f) || !is_link($g)) return false;
 | |
|     return @readlink($f) === @readlink($g);
 | |
|   }
 | |
| }
 |