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 final 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);
|
|
}
|
|
}
|