Příručka:BoxedCommand

From mediawiki.org
This page is a translated version of the page Manual:BoxedCommand and the translation is 100% complete.
Verze MediaWiki:
1.36
Gerrit change 626548

BoxedCommand je součástí knihovny Shellbox , která umožňuje transparentně spouštět příkazy příkazové řádky (shellu) a to nejem lokálně (jak se běžně dělá), ale i na serverech, pokud také používají Shellbox . Tahle stránka se zaobírá tím, jak lze pomocí této knihovny nahradit framework pro Shell, používaný od MediaWiki 1.30+.

A lightly edited and annotated example from Rozšíření:SyntaxHighlight :

// Nejprve získáme objekt BoxedCommand
$command = MediaWikiServices::getInstance()->getShellCommandFactory()
    // Který načteme do $wgShellboxUrls['syntaxhighlight']
	->createBoxed( 'syntaxhighlight' )
	// Vypneme přístup k síti
	->disableNetwork()
	// Pro firejail aplikujeme výchozí filtr seccomp
	->firejailDefaultSeccomp()
	->routeName( 'syntaxhighlight-pygments' );
// A sestavíme příkaz, který budeme spouštět
$result = $command
    // Parametry se spojí a předají jako textový řetězec
	->params(
		'/usr/bin/pygmentize',
		'-l', $lexer,
		'-f', 'html',
		'-O', implode( ',', $optionPairs ),
		'file'
	)
	// Pokud má příkaz použít soubor "file", jehož obsah je v $code, musíte ho načíst
	->inputFileFromString( 'file', $code )
	// A teď příkaz spustíme!
	->execute();
// Načteme si jeho výstup
$output = $result->getStdout();
// A zkontrolujeme návratový kód
if ( $result->getExitCode() != 0 ) {
	throw new PygmentsException( $output );
}
return $output;

Přehled

BoxedCommand používá oddělený sandbox, který se může spustit i na jiném serveru. Jde o dočasně vytvořený adresář, který neobsahuje žádné soubory, proto je potřeba data, s nimiž se bude pracovat, do příslušného souboru vložit pomocí funkce BoxedCommand. BoxedCommand se stará o to, aby vše fungovalo bez ohledu na to, jakou metodu pro vytvoření sandboxu administrátoři serverů nakonfigurovali.

Jak získáme BoxedCommand

Objekt ShellCommandFactory získáme, když zavoláme z MediaWikiServices funkci createBoxed(). Řetězcová hodnota, kterou předáte metodě createBoxed(), bude názvem klíče, pod který bude váš Shellbox zaregistrován do pole $wgShellboxUrls . Obvykle k tomu rozšíření používají svůj vlastní název, pokud nepotřebují spouštěné příkazy izolovat do samostatných Shellboxů.

There are various additional hardening measures that can be set at this point such as enabling firejail's default seccomp filter (firejailDefaultSeccomp()) or disabling network access (disableNetwork()). Ovšem ty budou dostupné jen v tom případě, že je bude vzdálený server také podporovat.

No a nakonec se nastaví název trasy (ovšem pozor, ve skutečnosti tohle přiřazení nic nedělá, protože je teprve ve fázi TODO – tedy jednou někdo možná k tomu nějakou funkcionalitu dodělá).

Obvykle si tvůrci rozšíření pomáhají statickou funkcí, s názvem boxedCommand(), která vrací objekt BoxedCommand již nakonfigurovaný:

private static function boxedCommand(): BoxedCommand {
	return MediaWikiServices::getInstance()->getShellCommandFactory()
		->createBoxed( 'syntaxhighlight' )
		->disableNetwork()
		->firejailDefaultSeccomp()
		->routeName( 'syntaxhighlight-pygments' );
}

Sestavení příkazu

Parametry se předávají zavoláním metody params(). Ta automaticky celý řetězec upraví tak, aby se mohl předat přes URL. Pokud se tomu chcete vyhnout a předat řetězec pro vykonání příkazu tak jak je, použijte unsafeParams(). Pomocná funkce Shellbox::escape(), která ošetří problematické znaky předávaného řetězce tak, aby nedošlo během předání k nežádoucí dezinterpretaci, se totiž volá dřív než unsafeParams(). A proměnné prostředí můžete předat jako pole, prostřednictvím metody environment().

Pokud chcete, aby sloučit standardní chybový výstup (stderr) se standardním výstupem (stdout), zavolejte metodu includeStderr().

Většina příkazů pracuje se soubory, proto má pro práci s nimi Shellbox několik pomocných funkcí:

  • inputFileFromString( $boxedName, $contents ) - Vytvoří ještě před spuštěním příkazu soubor pro jehož název použije obsah proměnné $boxedName, do kterého pak nalije obsah proměnné $contents
  • inputFileFromFile( $boxedName, $path ) - Udělá totéž co inputFileFromString, ovšem s tím rozdílem obsah souboru přelije ze souboru, identifikovaného řetězcovou hodnotou proměnné $path (cesta k souboru)
  • outputFileToString( $boxedName ) - Pokud má mít příkaz nějaký výstup, můžete ho po vykonání příkazu uložit do souboru, který je ovšem pouze v RAM
  • outputFileToFile( $boxedName, $path ) - Udělá totéž co outputFileToString, až na to že obsah přeleje rovnou do souboru identifikovaného cestou $path
  • outputGlobToString( $prefix, $extension ) - Funguje podobně jako outputFileToString, ovšem s tím rozdílem, že to udělá pro všechny soubory, jejichž název vyhoví následujícímu filtru ${prefix}*.${extension}
  • outputGlobToFile( $prefix, $extension, $dir ) - I v tomto případě to funguje jako u outputGlobToString, až na to, že cílem souborů vyhovujících nastaveným parametrům bude adresář $dir

A standardní vstup (stdin) můžete předat přes metodu stdin( $input )

Použití skriptu

Je zcela běžné, že se výstup předává ke zpracování dalším příkazům přes rouru. Například Score nejprve použije abc2ly, který převede ABC značky do syntaxe LilyPondu, a teprve z tohoto výstupu vygeneruje pomocí lilypond, který akceptuje pouze syntaxi LilyPondu finální obrázek. Tradičně by se řešilo přes dvě volání metody Shell::command(). Jedno pro abc2ly a druhé pro lilypond, s tím, že by se pak použil výstup z abc2ly. Ovšem to by vedlo ke zbytečné režii při použitíShellboxu na jiném serveru, protože obsah by se zbytečně posílat tam a zpět přes HTTP. Obejít se to dá tím, že se použije shellový skript, kterým si už sám, podle potřeby, bude volat další příkazy a případně průběžně zpracovávat jejich výstup.

Pokud chcete vidět nějaké ukázkové příklady, jak se to dělá, hoďte oko na adresář scripts/ v kódu rozšíření Score.

Zpracování výsledků

Zavoláním metody $command->execute() získáte objekt BoxedResult. U něj můžete využít následující metody:

  • getExitCode()
  • getStderr() - pokud použijete metodu includeStderr(), bude vracet null
  • getStdout()
  • getFileContents( $name ) - pokud bude přes outputFileToString do pamětu uložen nějaký výstup, můžete jeho obsah pomocí této metody uložit do souboru
  • wasReceived( $name ) - ověřuje, zda byl tento soubor přijat

Příklady