nur-sery/nur_src/shell.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);
}
}