I managed to make a set of functions to work with GPG, since my hosting provider refused to use GPG-ME.
Included below is an example of decryption using a higher descriptor to push a passphrase.
Comments and emails welcome. :)
<?php
function GPGDecrypt($InputData, $Identity, $PassPhrase, $HomeDir="~/.gnupg", $GPGPath="/usr/bin/gpg") {
if(!is_executable($GPGPath)) {
trigger_error($GPGPath . " is not executable",
E_USER_ERROR);
die();
} else {
// Set up the descriptors
$Descriptors = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "w"),
3 => array("pipe", "r") // This is the pipe we can feed the password into
);
// Build the command line and start the process
$CommandLine = $GPGPath . ' --homedir ' . $HomeDir . ' --quiet --batch --local-user "' . $Identity . '" --passphrase-fd 3 --decrypt -';
$ProcessHandle = proc_open( $CommandLine, $Descriptors, $Pipes);
if(is_resource($ProcessHandle)) {
// Push passphrase to custom pipe
fwrite($Pipes[3], $PassPhrase);
fclose($Pipes[3]);
// Push input into StdIn
fwrite($Pipes[0], $InputData);
fclose($Pipes[0]);
// Read StdOut
$StdOut = '';
while(!feof($Pipes[1])) {
$StdOut .= fgets($Pipes[1], 1024);
}
fclose($Pipes[1]);
// Read StdErr
$StdErr = '';
while(!feof($Pipes[2])) {
$StdErr .= fgets($Pipes[2], 1024);
}
fclose($Pipes[2]);
// Close the process
$ReturnCode = proc_close($ProcessHandle);
} else {
trigger_error("cannot create resource", E_USER_ERROR);
die();
}
}
if (strlen($StdOut) >= 1) {
if ($ReturnCode <= 0) {
$ReturnValue = $StdOut;
} else {
$ReturnValue = "Return Code: " . $ReturnCode . "\nOutput on StdErr:\n" . $StdErr . "\n\nStandard Output Follows:\n\n";
}
} else {
if ($ReturnCode <= 0) {
$ReturnValue = $StdErr;
} else {
$ReturnValue = "Return Code: " . $ReturnCode . "\nOutput on StdErr:\n" . $StdErr;
}
}
return $ReturnValue;
}
?>
proc_open
(PHP 4 >= 4.3.0, PHP 5)
proc_open — Exécute une commande et ouvre les pointeurs de fichiers pour les entrées / sorties
Description
proc_open() est similaire à popen() mais fournit un plus grand degré de contrôle sur l'exécution du programme.
Liste de paramètres
- cmd
-
La commande à exécuter
- descriptorspec
-
Un tableau indexé, dont les clés représentent le numéro de descripteur et la valeur la méthode avec laquelle PHP va passer ce descripteur au processus fils. 0 est stdin, 1 est stdout, et 2 est stderr.
Les types de descripteurs actuellement supportés sont file et pipe.
Les numéros de descripteurs de fichiers ne sont pas limités à 0, 1 et 2 - vous pouvez spécifier n'importe quel numéro de descripteur valide, et il sera passé au processus fils. Cela permettra à votre script d'inter opérer avec d'autres scripts, et d'être exécuté comme "co-processus". En particulier, c'est très pratique pour passer des mots de passes à des programmes comme PGP, GPG et openssl, avec une méthode très protégée. C'est aussi pratique pour lire des informations de statut fournies par ces programmes, sur des descripteurs auxiliaires.
- pipes
-
Doit être défini en un tableau indexé de pointeurs de fichiers qui correspondent à la fin de n'importe quel descripteur PHP qui sont créés.
- cwd
-
Le dossier initial de travail de la commande. Cela doit être un chemin absolu vers le dossier ou NULL si vous voulez utiliser la valeur par défaut (le dossier de travail du processus courant PHP)
- env
-
Un tableau contenant les variables d'environnement pour la commande qui doit être exécutée, ou NULL pour utiliser le même environnement que le processus PHP courant
- other_options
-
Vous permet de spécifier des options supplémentaires. Les options actuellement supportées sont :
- suppress_errors (windows uniquement): suppression des erreurs générées par cette fonction lorsque définit à TRUE
- bypass_shell (windows uniquement): bypass du shell cmd.exe lorsque définit à TRUE
- context: contexte du flux utilisé lors de l'ouverture des fichiers (créé avec la fonction stream_context_create())
- binary_pipes: ouverture des pipes en mode binaire, au lieu d'utiliser l'encodage habituel stream_encoding
Valeurs de retour
Retourne une ressource représentant le processus, qui pourra être utilisé par la fonction proc_close() lorsque vous n'en aurez plus besoin. En cas d'échec, FALSE sera retourné.
Historique
| Version | Description |
|---|---|
| 6.0.0 | Ajout des options context et binary_pipes au paramètre other_options . |
| 5.2.1 | Ajout de l'option bypass_shell au paramètre other_options . |
| 5.0.0. | Ajout des paramètres cwd , env et other_options . |
Exemples
Exemple #1 Exemple avec proc_open()
<?php
$descriptorspec = array(
0 => array("pipe", "r"), // // stdin est un pipe où le processus va lire
1 => array("pipe", "w"), // stdout est un pipe où le processus va écrire
2 => array("file", "/tmp/error-output.txt", "a") // stderr est un fichier
);
$cwd = '/tmp';
$env = array('quelques_options' => 'aeiou');
$process = proc_open('php', $descriptorspec, $pipes, $cwd, $env);
if (is_resource($process)) {
// $pipes ressemble à :
// 0 => fichier accessible en écriture, connecté à l'entrée standard du processus fils
// 1 => fichier accessible en lecture, connecté à la sortie standard du processus fils
// Toute erreur sera ajoutée au fichier /tmp/error-output.txt
fwrite($pipes[0], '<?php print_r($_ENV); ?>');
fclose($pipes[0]);
echo stream_get_contents($pipes[1]);
fclose($pipes[1]);
// Il est important que vous fermiez les pipes avant d'appeler
// proc_close afin d'éviter un verrouillage.
$return_value = proc_close($process);
echo "La commande a retourné $return_value\n";
}
?>
L'exemple ci-dessus va afficher quelque chose de similaire à :
Array ( [some_option] => aeiou [PWD] => /tmp [SHLVL] => 1 [_] => /usr/local/bin/php ) La commande a retourné 0
Notes
Note: Compatibilité Windows : les descripteurs au-delà de 2 (stderr) sont accessibles au processus fils, sous la forme de pointeurs hérités, mais comme l'architecture Windows n'associe pas de nombre aux descripteurs de bas niveau, le processus fils n'a, actuellement, aucun moyen d'y accéder. D'un autre coté, stdin, stdout et stderr fonctionnent comme d'habitude.
Note: Si vous n'avez besoin que d'un processus unidirectionnel, popen() sera plus pratique, car plus simple à utiliser.
proc_open
05-Jun-2008 04:46
03-May-2008 07:22
@joachimb: The descriptorspec describes the i/o from the perspective of the process you are opening. That is why stdin is read: you are writing, the process is reading. So you want to open descriptor 2 (stderr) in write mode so that the process can write to it and you can read it. In your case where you want all descriptors to be pipes you should always use:
<?php
$descriptorspec = array(
0 => array('pipe', 'r'), // stdin
1 => array('pipe', 'w'), // stdout
2 => array('pipe', 'w') // stderr
);
?>
The examples below where stderr is opened as 'r' is a mistake.
I would like to see examples of using higher descriptor numbers than 2. Specifically GPG as mentioned in the documentation.
30-Apr-2008 05:24
I'm confused by the direction of the pipes. Most of the examples in this documentation opens pipe #2 as "r", because they want to read from stderr. That sounds logical to me, and that's what I tried to do. That didn't work, though. When I changed it to w, as in
<?php
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
$process = proc_open(escapeshellarg($scriptFile), $descriptorspec, $pipes, $this->wd);
...
while (!feof($pipes[1])) {
foreach($pipes as $key =>$pipe) {
$line = fread($pipe, 128);
if($line) {
print($line);
$this->log($line);
}
}
sleep(0.5);
}
...
?>
everything works fine.
28-Mar-2008 11:15
Some functions stops working proc_open() to me.
This i made to work for me to communicate between two php scripts:
<?php
$abs_path = '/var/www/domain/filename.php';
$spec = array(array("pipe", "r"), array("pipe", "w"), array("pipe", "w"));
$process = proc_open('php '.$abs_path, $spec, $pipes, null, $_ENV);
if (is_resource($process)) {
# wait till something happens on other side
sleep(1);
# send command
fwrite($pipes[0], 'echo $test;');
fflush($pipes[0]);
# wait till something happens on other side
usleep(1000);
# read pipe for result
echo fread($pipes[1],1024).'<hr>';
# close pipes
fclose($pipes[0]);fclose($pipes[1]);fclose($pipes[2]);
$return_value = proc_close($process);
}
?>
filename.php then contains this:
<?php
$test = 'test data generated here<br>';
while(true) {
# read incoming command
if($fh = fopen('php://stdin','rb')) {
$val_in = fread($fh,1024);
fclose($fh);
}
# execute incoming command
if($val_in)
eval($val_in);
usleep(1000);
# prevent neverending cycle
if($tmp_counter++ > 100)
break;
}
?>
22-Feb-2008 11:57
It took me a long time (and three consecutive projects) to figure this out. Because popen() and proc_open() return valid processes even when the command failed it's awkward to determine when it really has failed if you're opening a non-interactive process like "sendmail -t".
I had previously guess that reading from STDERR immediately after starting the process would work, and it does... but when the command is successful PHP just hangs because STDERR is empty and it's waiting for data to be written to it.
The solution is a simple stream_set_blocking($pipes[2], 0) immediately after calling proc_open().
<?php
$this->_proc = proc_open($command, $descriptorSpec, $pipes);
stream_set_blocking($pipes[2], 0);
if ($err = stream_get_contents($pipes[2]))
{
throw new Swift_Transport_TransportException(
'Process could not be started [' . $err . ']'
);
}
?>
If the process is opened successfully $pipes[2] will be empty, but if it failed the bash/sh error will be in it.
Finally I can drop all my "workaround" error checking.
I realise this solution is obvious and I'm not sure how it took me 18 months to figure it out, but hopefully this will help someone else.
NOTE: Make sure your descriptorSpec has ( 2 => array('pipe', 'w')) for this to work.
27-Dec-2007 04:40
I needed to emulate a tty for a process (it wouldnt write to stdout or read from stdin), so I found this:
<?php
$descriptorspec = array(0 => array('pty'),
1 => array('pty'),
2 => array('pty'));
?>
pipes are bidirectional then
07-Dec-2007 07:52
STDIN STDOUT example
test.php
<?php
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("pipe", "r")
);
$process = proc_open('php test_gen.php', $descriptorspec, $pipes, null, null); //run test_gen.php
echo ("Start process:\n");
if (is_resource($process))
{
fwrite($pipes[0], "start\n"); // send start
echo ("\n\nStart ....".fgets($pipes[1],4096)); //get answer
fwrite($pipes[0], "get\n"); // send get
echo ("Get: ".fgets($pipes[1],4096)); //get answer
fwrite($pipes[0], "stop\n"); //send stop
echo ("\n\nStop ....".fgets($pipes[1],4096)); //get answer
fclose($pipes[0]);
fclose($pipes[1]);
fclose($pipes[2]);
$return_value = proc_close($process); //stop test_gen.php
echo ("Returned:".$return_value."\n");
}
?>
test_gen.php
<?php
$keys=0;
function play_stop()
{
global $keys;
$stdin_stat_arr=fstat(STDIN);
if($stdin_stat_arr[size]!=0)
{
$val_in=fread(STDIN,4096);
switch($val_in)
{
case "start\n": echo "Started\n";
return false;
break;
case "stop\n": echo "Stopped\n";
$keys=0;
return false;
break;
case "pause\n": echo "Paused\n";
return false;
break;
case "get\n": echo ($keys."\n");
return true;
break;
default: echo("Передан не верный параметр: ".$val_in."\n");
return true;
exit();
}
}else{return true;}
}
while(true)
{
while(play_stop()){usleep(1000);}
while(play_stop()){$keys++;usleep(10);}
}
?>
05-Oct-2007 11:23
It seems that if you configured --enable-sigchild when you compiled PHP (which from my reading is required for you to use Oracle stuff), then return codes from proc_close() cannot be trusted.
Using proc_open's Example 1998's code on versions I have of PHP4 (4.4.7) and PHP5 (5.2.4), the return code is always "-1". This is also the only return code I can cause by running other shell commands whether they succeed or fail.
I don't see this caveat mentioned anywhere except on this old bug report -- http://bugs.php.net/bug.php?id=29123
27-Jul-2007 09:48
missilesilo at gmail dot com had a great example. However error messages didn't work because of wrong stderr argument.
I changed last value 'r' of
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'r')
);
to 'w' so that error messages are actually written.
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("pipe", "w") // stderr is a file to write to
);
08-Mar-2007 09:30
If you just want to execute a command and get the output of the program, here is a simple object-oriented way to do it:
If there was an error detected from the STDERR output of the program, the open method of the Process class will throw an Exception. Otherwise, it will return the STDOUT output of the program.
<?php
class Process
{
public static function open($command)
{
$retval = '';
$error = '';
$descriptorspec = array(
0 => array('pipe', 'r'),
1 => array('pipe', 'w'),
2 => array('pipe', 'r')
);
$resource = proc_open($command, $descriptorspec, $pipes, null, $_ENV);
if (is_resource($resource))
{
$stdin = $pipes[0];
$stdout = $pipes[1];
$stderr = $pipes[2];
while (! feof($stdout))
{
$retval .= fgets($stdout);
}
while (! feof($stderr))
{
$error .= fgets($stderr);
}
fclose($stdin);
fclose($stdout);
fclose($stderr);
$exit_code = proc_close($resource);
}
if (! empty($error))
throw new Exception($error);
else
return $retval;
}
}
try
{
$output = Process::open('cat example.txt');
// do something with the output
}
catch (Exception $e)
{
echo $e->getMessage() . "\n";
// there was a problem executing the command
}
?>
09-Jan-2007 04:12
whenever the result of proc_open is forgotten (e.g. a function calling it is left), the process will be terminated immediately. This can be extremely confusing. So always globalize the variable that stores the return value of proc_open...
16-Oct-2006 03:28
The best way on windows to open a process then to let the php script continue is to call your process with the start command then to kill the "start" process and let your program run.
<?
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("pipe", "w") // stderr
);
$process = proc_open('start notepad.exe', $descriptorspec, $pipes);
sleep(1);
proc_close($process);
?>
The start command will be called then open notepad, after 1 second the "start" command will be killed but the notepad will still opened and your php script can continue!
23-Sep-2006 12:37
I wanted to proc_open bash and then send a command and read the output multiple times instead of opening bash each time. There were several "tricks"
Put a "\n" on the end of each command
Use a fflush($pipes[0]) after each fwrite($pipes[0])
Put a sleep(1) before you read the output of the command
Once I added all of that I was able to send an arbitrary amount of commands to bash, read the output and when I was finished I could close the pipes.
09-Sep-2006 12:02
Here's an extremely useful function to perform
data dumps (pg_dump) on a PostgreSQL database.
It could be easily modified to zip the data into a
zip or gz file and force a browser download window.
Or it could be used as in a nightly backup script.
<?php
/**
* postgresBackup: Creates a backup of a postgresql database
* @author Michael Honaker - Symmetry Technical Consultants, LLC
* @date 2006-06-15
* @param - string - DB name
* @param - string - User name
* @param - string - Password
* @param - string - Backup directory
* @return - bool - Success or failure
**/
function postgresBackup($dbName, $dbUser, $dbPwd, $backupPath)
{
$fSuccess = FALSE;
// get rid of try..catch for PHP 4 or less
try {
ignore_user_abort(TRUE);
$file = date('YmdHis') . "_pgDBBackup.sql";
$buffer = '';
$logFile = "/tmp/pgdump-error-output.txt";
$descriptorspec = array(
0 => array("pipe", "r"), // stdin
1 => array("pipe", "w"), // stdout
2 => array("file", "$logFile", "a") // stderr
);
// may need entire path to pg_dump
$cmd = "pg_dump -c -D -U {$dbUser} {$dbName}";
$process = proc_open("$cmd", $descriptorspec, $pipes);
if (is_resource($process)):
// $pipes now looks like this:
// 0 => writeable handle connected to child stdin
// 1 => readable handle connected to child stdout
// Any error output will be written to log file
// send the password and close the stdin
fwrite($pipes[0], "{$dbPwd}\n");
fclose($pipes[0]);
// read in the dump data and close stdout
while(!feof($pipes[1])):
$buffer .= fgets($pipes[1], 1024);
endwhile;
fclose($pipes[1]);
// close any pipes before calling
// proc_close to avoid a deadlock
$return_value = proc_close($process);
// write the file to disk and return true
if($return_value == 0):
// this could be modified to automatically
// force a file download if run from a web page
// may need to change this line for PHP 4 or less
file_put_contents($backupPath . $file, $buffer);
// successfully created backup
$fSuccess = TRUE;
endif;
endif;
} catch(Exception $ex) {
error_log($ex->getMessage(), 3, $logFile);
return FALSE; // return so log file is not deleted
}
// comment out the following for debug information if backup fails
@unlink($logFile);
return $fSuccess;
}
// example usage
postgresBackup('mydb','myuser','pwd',"/home/db/");
?>
25-Jul-2006 02:12
if your writing a function that processes a resource from
another function its a good idea not only to check whether
a resource has been passed to your function but also if its
of the good type like so:
<?php
function workingonit($resource){
if(is_resource($resource)){
if(get_resource_type($resource) == "resource_type"){
// resource is a resource and of the good type. continue
}else{
print("resource is of the wrong type.");
return false;
}
}else{
print("resource passed is not a resource at all.");
return false;
}
// do your stuff with the resource here and return
}
?>
this is extra true for working with files and process pipes.
so always check whats being passed to your functions.
here's a small snipppet of a few resource types:
files are of type 'file' in php4 and 'stream' in php5
'prossess' are resources opened by proc_open.
'pipe' are resource opened by popen.
btw the 'prossess' resource type was not mentioned in
the documentation. i make a bug-report for this.
03-Jun-2006 01:47
If you are going to allow data coming from user input to be passed to this function, then you should keep in mind the following warning that also applies to exec() and system():
http://www.php.net/manual/en/function.exec.php
http://www.php.net/manual/en/function.system.php
Warning:
If you are going to allow data coming from user input to be passed to this function, then you should be using escapeshellarg() or escapeshellcmd() to make sure that users cannot trick the system into executing arbitrary commands.
07-Apr-2006 09:14
[Again, please delete my previous comment, the code still contained bugs (sorry). This version now includes Frederick Leitner's fix from below, and also fixes another bug: If an empty file was piped into the process, the loop would hang indefinitely.]
The following code works for piping large amounts of data through a filtering program. I find it very weird that such a lot of code is needed for this task... On entry, $stdin contains the standard input for the program. Tested on Debian Linux with PHP 5.1.2.
$descriptorSpec = array(0 => array("pipe", "r"),
1 => array('pipe', 'w'),
2 => array('pipe', 'w'));
$process = proc_open($command, $descriptorSpec, $pipes);
$txOff = 0; $txLen = strlen($stdin);
$stdout = ''; $stdoutDone = FALSE;
$stderr = ''; $stderrDone = FALSE;
stream_set_blocking($pipes[0], 0); // Make stdin/stdout/stderr non-blocking
stream_set_blocking($pipes[1], 0);
stream_set_blocking($pipes[2], 0);
if ($txLen == 0) fclose($pipes[0]);
while (TRUE) {
$rx = array(); // The program's stdout/stderr
if (!$stdoutDone) $rx[] = $pipes[1];
if (!$stderrDone) $rx[] = $pipes[2];
$tx = array(); // The program's stdin
if ($txOff < $txLen) $tx[] = $pipes[0];
stream_select($rx, $tx, $ex = NULL, NULL, NULL); // Block til r/w possible
if (!empty($tx)) {
$txRet = fwrite($pipes[0], substr($stdin, $txOff, 8192));
if ($txRet !== FALSE) $txOff += $txRet;
if ($txOff >= $txLen) fclose($pipes[0]);
}
foreach ($rx as $r) {
if ($r == $pipes[1]) {
$stdout .= fread($pipes[1], 8192);
if (feof($pipes[1])) { fclose($pipes[1]); $stdoutDone = TRUE; }
} else if ($r == $pipes[2]) {
$stderr .= fread($pipes[2], 8192);
if (feof($pipes[2])) { fclose($pipes[2]); $stderrDone = TRUE; }
}
}
if (!is_resource($process)) break;
if ($txOff >= $txLen && $stdoutDone && $stderrDone) break;
}
$returnValue = proc_close($process);
06-Mar-2006 09:36
I found that with disabling stream blocking I was sometimes attempting to read a return line before the external application had responded. So, instead, I left blocking alone and used this simple function to add a timeout to the fgets function:
// fgetsPending( $in,$tv_sec ) - Get a pending line of data from stream $in, waiting a maximum of $tv_sec seconds
function fgetsPending(&$in,$tv_sec=10) {
if ( stream_select($read = array($in),$write=NULL,$except=NULL,$tv_sec) ) return fgets($in);
else return FALSE;
}
29-Dec-2005 06:55
The pty option is actually disabled in the source for some reason via a #if 0 && condition. I'm not sure why it's disabled. I removed the 0 && and recompiled, after which the pty option works perfectly. Just a note.
24-Nov-2005 05:48
If you want pass an array to $env, you MUST serialize this!
Bad Example:
$env = array('pippo' => 'Hello', 'request' =>$_REQUEST);
$process = proc_open('php', $descriptorspec, $pipes, $cwd, $env);
fwrite($pipes[0], '<?php print_r($_ENV[request]); ?>');
A result is an empty array;
Good Example:
$env = array('pippo' => 'Hello', 'request' =>serialize($_REQUEST));
$process = proc_open('php', $descriptorspec, $pipes, $cwd, $env);
fwrite($pipes[0], '<?php print_r(unserialize($_ENV[request])); ?>');
A result is good array!
Bye,
Enrico
22-Oct-2005 07:42
Since I don't have access to PAM via Apache, suexec on, nor access to /etc/shadow I coughed up this way of authenticating users based on the system users details. It's really hairy and ugly, but it works.
<?
function authenticate($user,$password) {
$descriptorspec = array(
0 => array("pipe", "r"), // stdin is a pipe that the child will read from
1 => array("pipe", "w"), // stdout is a pipe that the child will write to
2 => array("file","/dev/null", "w") // stderr is a file to write to
);
$process = proc_open("su ".escapeshellarg($user), $descriptorspec, $pipes);
if (is_resource($process)) {
// $pipes now looks like this:
// 0 => writeable handle connected to child stdin
// 1 => readable handle connected to child stdout
// Any error output will be appended to /tmp/error-output.txt
fwrite($pipes[0],$password);
fclose($pipes[0]);
fclose($pipes[1]);
// It is important that you close any pipes before calling
// proc_close in order to avoid a deadlock
$return_value = proc_close($process);
return !$return_value;
}
}
?>
17-Oct-2005 01:51
The above note on Windows compatibility is not entirely correct.
Windows will dutifully pass on additional handles above 2 onto the child process, starting with Windows 95 and Windows NT 3.5. It even supports this capability (starting with Windows 2000) from the command line using a special syntax (prefacing the redirection operator with the handle number).
These handles will be, when passed to the child, preopened for low-level IO (e.g. _read) by number. The child can reopen them for high-level (e.g. fgets) using the _fdopen or _wfdopen methods. The child can then read from or write to them the same way they would stdin or stdout.
However, child processes must be specially coded to use these handles, and if the end user is not intelligent enough to use them (e.g. "openssl < commands.txt 3< cacert.der") and the program not smart enough to check, it could cause errors or hangs.
04-Oct-2005 09:34
One can learn from the source code in ext/standard/exec.c that the right-hand side of a descriptor assignment does not have to be an array ('file', 'pipe', or 'pty') - it can also be an existing open stream.
<?php
$p = proc_open('myfilter', array( 0 => $infile, ...), $pipes);
?>
I was glad to learn that because it solves the race condition in a scenario like this: you get a file name, open the file, read a little to make sure it's OK to serve to this client, then rewind the file and pass it as input to the filter. Without this feature, you would be limited to <?php array('file', $fname) ?> or passing the name to the filter command. Those choices both involve a race (because the file will be reopened after you have checked it's OK), and the last one invites surprises if not carefully quoted, too.
05-Aug-2005 09:16
proc_open is hard coded to use "/bin/sh". So if you're working in a chrooted environment, you need to make sure that /bin/sh exists, for now.
18-Jul-2005 08:40
An example of using proc_open() can be found in my program PHP Shell: http://phpshell.sourceforge.net/
22-Jun-2005 03:21
I thought it was highly not recommended to fork from your web server?
Apart from that, one caveat is that the child process inherits anything that is preserved over fork from the parent (apart from the file descriptors which are explicitly closed).
Importantly, it inherits the signal handling setup, which at least with apache means that SIGPIPE is ignored. Child processes that expect SIGPIPE to kill them in order to get sensible pipe handling and not go into a tight write loop will have problems unless they reset SIGPIPE themselves.
Similar caveats probably apply to other signals like SIGHUP, SIGINT, etc.
Other things preserved over fork include shared memory segments, umask and rlimits.
21-Mar-2005 02:22
Using this function under windows with large amounts of data is apparently futile.
these functions are returning 0 but do not appear to be doing anything useful.
stream_set_write_buffer($pipes[0],0);
stream_set_write_buffer($pipes[1],0);
these functions are returning false and are also apparently useless under windows.
stream_set_blocking($pipes[0], FALSE);
stream_set_blocking($pipes[1], FALSE);
The magic max buffer size I found with winxp is 63488 bytes, (62k). Anything larger than this results in a system hang.
25-May-2004 12:13
About the comment by ch at westend dot com
of 28-Aug-2003 08:46
File streams are buffers. The data is not actualy written if you do not flush the buffer. In your case, fclose has the side effect of flushing the buffer you are closing.
The program "hangs" because it tries to read some data that was not written (since it is buffered).
You must do something like:
<?php
fwrite($fp);
fflush($fp);
fread($fp);
?>
Good luck,
Andre Caldas.
12-May-2004 12:21
if you push a little bit more data through the pipe, it will be hanging forever. One simple solution on RH linux was to do this:
stream_set_blocking($pipes[0], FALSE);
stream_set_blocking($pipes[1], FALSE);
This did not work on windows XP though.
09-Jan-2004 08:49
The behaviour described in the following may depend on the system php runs on. Our platform was "Intel with Debian 3.0 linux".
If you pass huge amounts of data (ca. >>10k) to the application you run and the application for example echos them directly to stdout (without buffering the input), you will get a deadlock. This is because there are size-limited buffers (so called pipes) between php and the application you run. The application will put data into the stdout buffer until it is filled, then it blocks waiting for php to read from the stdout buffer. In the meantime Php filled the stdin buffer and waits for the application to read from it. That is the deadlock.
A solution to this problem may be to set the stdout stream to non blocking (stream_set_blocking) and alternately write to stdin and read from stdout.
Just imagine the following example:
<?
/* assume that strlen($in) is about 30k
*/
$descriptorspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/error-output.txt", "a")
);
$process = proc_open("cat", $descriptorspec, $pipes);
if (is_resource($process)) {
fwrite($pipes[0], $in);
/* fwrite writes to stdin, 'cat' will immediately write the data from stdin
* to stdout and blocks, when the stdout buffer is full. Then it will not
* continue reading from stdin and php will block here.
*/
fclose($pipes[0]);
while (!feof($pipes[1])) {
$out .= fgets($pipes[1], 1024);
}
fclose($pipes[1]);
$return_value = proc_close($process);
}
?>
24-Dec-2003 01:20
Note that if you need to be "interactive" with the user *and* the opened application, you can use stream_select to see if something is waiting on the other side of the pipe.
Stream functions can be used on pipes like :
- pipes from popen, proc_open
- pipes from fopen('php://stdin') (or stdout)
- sockets (unix or tcp/udp)
- many other things probably but the most important is here
More informations about streams (you'll find many useful functions there) :
http://www.php.net/manual/en/ref.stream.php
28-Aug-2003 05:46
I had trouble with this function as my script always hung like in a deadlock until I figured out that I had to strictly keep the following
order. Trying to close all at the end did not work!
proc_open();
fwrite(pipes[0]); fclose(pipes[0]); # stdin
fread(pipes[1]); fclose(pipes[1]); # stdout
fread(pipes[2]); flcose(pipes[2]); # stderr
proc_close();
16-Apr-2003 11:01
Just a small note in case it isn't obvious, its possible to treat the filename as in fopen, thus you can pass through the standard input from php like
$descs = array (
0 => array ("file", "php://stdin", "r"),
1 => array ("pipe", "w"),
2 => array ("pipe", "w")
);
$proc = proc_open ("myprogram", $descs, $fp);
29-Dec-2002 02:54
I worked with proc_open for a while before realizing how it works with applications in real time.
This example loads up the eDonkey2000 client and reads data from it and passes in various commands and returns the results.
This is the base for an ncurses gui for edonkey I am writing in PHP.
<?
define ("DASHES", "-------------------------------------------------\n");
function readit($pipes, $len=2, $end="> "){
stream_set_blocking($pipes[1], FALSE);
while($ret = fread($pipes[1],$len)){
$retval .= $ret;
if(substr_count($ret, $end) > 0){ $pipes[1] = "" ; break;}
}
return $retval;
}//end fucntion
function sendto($pipes, $str){
fwrite($pipes[0], $str."\n");
}//end function
function viewopts($pipes, $opt){
sleep(1);
sendto($pipes, $opt);
return readit($pipes);
}//end function
function sendopts($pipes, $opt){
sendto($pipes, $opt);
usleep(50);
return readit($pipes);
}//end function
$dspec = array(
0 => array("pipe", "r"),
1 => array("pipe", "w"),
2 => array("file", "/tmp/eo.txt", "a"),);
$process = proc_open("donkey", $dspec, $pipes);
if (is_resource($process)) {
readit($pipes);
echo DASHES;
echo viewopts($pipes, "vo");
echo DASHES; echo SEP;echo DASHES;
echo sendopts($pipes, "name test".rand(5,5000));
echo DASHES; echo SEP; echo DASHES;
echo viewopts($pipes, "vo");
echo DASHES; echo SEP; echo DASHES;
echo sendopts($pipes, "temp /tmp");
echo DASHES; echo SEP; echo DASHES;
echo viewopts($pipes, "g");
echo DASHES;
sendto($pipes, "q");
sendto($pipes, "y");
readit($pipes);
fclose($pipes[0]);
fclose($pipes[1]);
$return_value = proc_close($process);
}
?>
returns what looks like the following
-----------------------------------------------------------------
Name: test2555
AdminName: admin
AdminPass: password
AdminPort: 79
Max Download Speed: 0.00
Max Upload Speed: 0.00
Line Speed Down: 0.00
Door Port: 4662
AutoConnect: 1
Verbose: 0
SaveCorrupted: 1
AutoServerRemove: 1
MaxConnections: 45
> ----------------------------------------------------------------
19-Apr-2002 04:50
Example of emulating the press of the special key "F3":
fwrite($pipes[0], chr(27)."[13~");
(for others special keys, use the program 'od -c' on linux)
(NEEDED: a timeout for stdout pipe, otherwise a fgets on $pipes[1] can lag forever...)
