I need to call a function within a PHP_FUNCTION() function in C to extend PHP, this is a multithread script and The function itself works perfectly using int main(). Here is what I try to achieve.
#define NUM_THREADS 3
char *messages[NUM_THREADS];
void *PrintHello(void *threadid)
{
zend_printf("gP");
int *id_ptr, taskid;
sleep(4);
id_ptr = (int *) threadid;
taskid = *id_ptr;
zend_printf("Thread %d: %s\n", taskid, messages[taskid]);
pthread_exit(NULL);
}
PHP_FUNCTION(hello_world)
{
pthread_t threads[NUM_THREADS];
int *taskids[NUM_THREADS];
int rc, t;
messages[0] = "English: Hello World!";
messages[1] = "French: Bonjour, le monde!";
messages[2] = "Spanish: Hola al mundo";
for(t=0; t < NUM_THREADS; t++)
{
taskids[t] = (int *) malloc(sizeof(int));
*taskids[t] = t;
zend_printf("Creating thread %d\n <br>", t);
rc = pthread_create(&threads[t], NULL, (void* (*) (void*)) pthreads_routine, (void *) taskids[t]);
if (rc) {
zend_printf("ERR; pthread_create() ret = %d\n", rc);
}
}
}
I need to call PrintHello() function from
rc = pthread_create(&threads[t], NULL, PrintHello, (void *) taskids[t]);
Do I also need to register void *PrintHello(void *threadid) in
const zend_function_entry my_functions[] = {
PHP_FE(hello_world, NULL)
PHP_FE_END
};
the var_dump() out put is
Creating thread 0
Creating thread 1
Creating thread 2
NULL
at the top of void *PrintHello(void *threadid) function I have included zend_printf("gP"); line to make sure that the function is invoked and from the looks of out put it is obvious that the function is not invoked.
My environment is Mac OSX , xCode 7.2, PHP 7.0.1
What am I doing wrong?
It appears you have two problems in your code, both of which explain why you're not getting any output.
1) At least in my tests, it appears that zend_printf -> php_printf -> vspprintf is not thread safe. Your code always crashes for me once one of the threads attempts to call zend_printf(). But, even if that weren't the case, there's also:
2) Assuming your php code looks like this:
<?php
hello_world();
What's happening is that when you call pthread_create(), it returns immediately having created the thread, though the thread has not necessarily started to run. Then, hello_world returns once all the threads have been created. Then, your main thread ends because there's nothing else to do.
Once the main thread ends, your spawned threads are immediately terminated. If you're seeing nothing at all, that's because the main thread ends before any of the pthreads are actually scheduled, before they even execute your zend_printf("gP"); line.
If you change your php code to:
<?php
hello_world();
sleep(10);
Then you give the child threads enough time to be scheduled and given CPU time (at which point they will probably crash calling the first zend_printf), and if not, give them enough time to make it past the sleep(4) to get to the zend_printf(Thread id).
If you replace your PrintHello with this:
void *PrintHello(void *threadid)
{
int *id_ptr, taskid;
id_ptr = (int *) threadid;
taskid = *id_ptr;
printf("Thread %d: start\n", taskid, messages[taskid]);
sleep(4);
printf("Thread %d: %s\n", taskid, messages[taskid]);
pthread_exit(NULL);
}
(replace the zend_printf with regular printf), then you'll get your desired output, at least on the cli.
Related
i am trying to warp a C library around a PHP class using PHP's Zend Engine. The constructor function of the class is supposed to take a string and a function pointer. I was able to fetch strings values from the __construct arguments and printing them. However when i fetch the function and try to make it run in the class's constructor. I get a segfault error.
Basically, the end result should like like so:
class TestClass
{
function __construct(callable: $callBack, string: $name);
}
(Naturally, there are other methods in the class, but i had no issue with them).
The C extension looks like so:
PHP_METHOD(TestClass, __construct)
{
zend_string* name;
zend_fcall_info fci;
zend_fcall_info_cache fci_cache;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "fS", &fci, &fci_cache, &name) == FAILURE)
return;
php_printf("name: %s\n", ZSTR_VAL(name));
if (zend_call_function(&fci, &fci_cache TSRMLS_CC) == SUCCESS) {
php_printf("Called");
}
zval* obj = getThis();
test_class_t* intern;
intern = Z_TSTOBJ_P(obj);
php_printf("Constructor\n");
zend_string_release(name);
if (fci.params)
efree(fci.params);
}
Segfault doesn't happen when zend_call_function is commented out.
I am using 7.2.19 and C99.
Is there a way to solve this issue?
Thanks
You have to specify a zval for the return value of the function before you can call it. You also have to specify the number of parameters you want to pass:
zval retval;
fci.retval = &retval;
fci.param_count = 0;
// Use zend_call_function()
zval_ptr_dtor(&retval);
To use parameters you have to allocate an array (stack or heap) of zvals to hold the values you want to pass. A simple example would be like this:
zval args[2];
ZVAL_LONG(&args[0], 1);
ZVAL_LONG(&args[1], 2);
fci.params = args;
fci.param_count = 2;
// Use zend_call_function() here.
zval_ptr_dtor(&args[0]);
zval_ptr_dtor(&args[1]);
In case of heap allocation:
zval *args = emalloc(2 * sizeof(zval));
// Assign, call and dtor as in previous example.
efree(args);
Ok, so lets start slow...
I have a pthreads script running and working for me, tested and working 100% of the time when I run it manually from the command line via ssh. The script is as follows with the main thread process code adjusted to simulate random process' run time.
class ProcessingPool extends Worker {
public function run(){}
}
class LongRunningProcess extends Threaded implements Collectable {
public function __construct($id,$data) {
$this->id = $id;
$this->data = $data;
}
public function run() {
$data = $this->data;
$this->garbage = true;
$this->result = 'START TIME:'.time().PHP_EOL;
// Here is our actual logic which will be handled within a single thread (obviously simulated here instead of the real functionality)
sleep(rand(1,100));
$this->result .= 'ID:'.$this->id.' RESULT: '.print_r($this->data,true).PHP_EOL;
$this->result .= 'END TIME:'.time().PHP_EOL;
$this->finished = time();
}
public function __destruct () {
$Finished = 'EXITED WITHOUT FINISHING';
if($this->finished > 0) {
$Finished = 'FINISHED';
}
if ($this->id === null) {
print_r("nullified thread $Finished!");
} else {
print_r("Thread w/ ID {$this->id} $Finished!");
}
}
public function isGarbage() : bool { return $this->garbage; }
public function getData() {
return $this->data;
}
public function getResult() {
return $this->result;
}
protected $id;
protected $data;
protected $result;
private $garbage = false;
private $finished = 0;
}
$LoopDelay = 500000; // microseconds
$MinimumRunTime = 300; // seconds (5 minutes)
// So we setup our pthreads pool which will hold our collection of threads
$pool = new Pool(4, ProcessingPool::class, []);
$Count = 0;
$StillCollecting = true;
$CountCollection = 0;
do {
// Grab all items from the conversion_queue which have not been processed
$result = $DB->prepare("SELECT * FROM `processing_queue` WHERE `processed` = 0 ORDER BY `queue_id` ASC");
$result->execute();
$rows = $result->fetchAll(PDO::FETCH_ASSOC);
if(!empty($rows)) {
// for each of the rows returned from the queue, and allow the workers to run and return
foreach($rows as $id => $row) {
$update = $DB->prepare("UPDATE `processing_queue` SET `processed` = 1 WHERE `queue_id` = ?");
$update->execute([$row['queue_id']]);
$pool->submit(new LongRunningProcess($row['fqueue_id'],$row));
$Count++;
}
} else {
// 0 Rows To Add To Pool From The Queue, Do Nothing...
}
// Before we allow the loop to move on to the next part, lets try and collect anything that finished
$pool->collect(function ($Processed) use(&$CountCollection) {
global $DB;
$data = $Processed->getData();
$result = $Processed->getResult();
$update = $DB->prepare("UPDATE `processing_queue` SET `processed` = 2 WHERE `queue_id` = ?");
$update->execute([$data['queue_id']]);
$CountCollection++;
return $Processed->isGarbage();
});
print_r('Collecting Loop...'.$CountCollection.'/'.$Count);
// If we have collected the same total amount as we have processed then we can consider ourselves done collecting everything that has been added to the database during the time this script started and was running
if($CountCollection == $Count) {
$StillCollecting = false;
print_r('Done Collecting Everything...');
}
// If we have not reached the full MinimumRunTime that this cron should run for, then lets continue to loop
$EndTime = microtime(true);
$TimeElapsed = ($EndTime - $StartTime);
if(($TimeElapsed/($LoopDelay/1000000)) < ($MinimumRunTime/($LoopDelay/1000000))) {
$StillCollecting = true;
print_r('Ended To Early, Lets Force Another Loop...');
}
usleep($LoopDelay);
} while($StillCollecting);
$pool->shutdown();
So while the above script will run via a command line (which has been adjusted to the basic example, and detailed processing code has been simulated in the above example), the below command gives a different result when run from a cron setup for every 5 minutes...
/opt/php7zts/bin/php -q /home/account/cron-entry.php file=every-5-minutes/processing-queue.php
The above script, when using the above command line call, will loop over and over during the run time of the script and collect any new items from the DB queue, and insert them into the pool, which allows 4 processes at a time to run and finish, which is then collected and the queue is updated before another loop happens, pulling any new items from the DB. This script will run until we have processed and collected all processes in the queue during the execution of the script. If the script has not run for the full 5 minute expected period of time, the loop is forced to continue checking the queue, if the script has run over the 5 minute mark it allows any current threads to finish & be collected before closing. Note that the above code also includes a code based "flock" functionality which makes future crons of this idle loop and exit or start once the lock has lifted, ensuring that the queue and threads are not bumping into each other. Again, ALL OF THIS WORKS FROM THE COMMAND LINE VIA SSH.
Once I take the above command, and put it into a cron to run for every 5 minutes, essentially giving me a never ending loop, while maintaining memory, I get a different result...
That result is described as follows... The script starts, checks the flock, and continues if the lock is not there, it creates the lock, and runs the above script. The items are taken from the queue in the DB, and inserted into the pool, the pool fires off the 4 threads at a time as expected.. But the unexpected result is that the run() command does not seem to be executed, and instead the __destruct function runs, and a "Thread w/ ID 2 FINISHED!" type of message is returned to the output. This in turn means that the collection side of things does not collect anything, and the initiating script (the cron script itself /home/account/cron-entry.php file=every-5-minutes/processing-queue.php) finishes after everything has been put into the pool, and destructed. Which prematurely "finishes" the cron job, since there is nothing else to do but loop and pull nothing new from the queue, since they are considered "being processed" when processed == 1 in the queue.
The question then finally becomes... How do I make the cron's script aware of the threads that where spawned and run() them without closing the pool out before they can do anything?
(note... if you copy / paste the provided script, note that I did not test it after removing the detailed logic, so it may need some simple fixes... please do not nit-pick said code, as the key here is that pthreads works if the script is executed FROM the Command Line, but fails to properly run when the script is executed FROM a CRON. If you plan on commenting with non-constructive criticism, please go use your fingers to do something else!)
Joe Watkins! I Need Your Brilliance! Thanks In Advance!
After all of that, it seems that the issue was with regards to user permissions. I was setting this specific cron up inside of cpanel, and when running the command manually I was logged in as root.
After setting this command up in roots crontab, I was able to get it to successfully run the threads from the pool. Only issue I have now is some threads never finish, and sometimes I am unable to close the pool. But this is a different issue, so I will open another question elsewhere.
For those running into this issue, make sure you know who the owner of the cron is as it matters with php's pthreads.
I need to have a mutex method in PHP so that it keeps exclusivity by a variable value. This is that threads with same value should enter that method one at a time while threads with different values may access that method arbitrarily.
For example, given that method:
/**
* #param integer $value
*/
function mutexMethod($value)
{
// Lock for value $value
echo 'processing';
sleep(2);
echo 'this is so heavy';
// Unlock for value $value
}
For example (I need this to be run through apache):
time |
0 | php > mutexMethod(1); | php > mutexMethod(2); | php > mutexMethod(1);
1 | processing | processing |
2 | | |
3 | this is so heavy | this is so heavy | processing
4 | | |
5 | | | this is so heavy
As a first solution, I've tried using semaphores but since $value may get any value, I've run out of semaphores space very quickly (I've tried removing the semaphores after using them but this breaks other threads waiting for it and since I cannot know if there are any threads waiting for them, I cannot remove them arbitrarily.
As a second solution, I've tried creating a file with the value $value as name and using flock to lock any other thread. Despite this worked in the CLI, I couldn't manage to make it work through apache. It certainly locked the file but it never released that lock so any other request got stuck until the first one timed out (after 30 seconds).
Finally I though about using MySQL locks but I would like to avoid them as much as we would like to not use the MySQL instance for such things. Ideally we would like a pure PHP solution.
Do you have an idea on how can I solve that problem? I would like to avoid single-semaphore solutions (like having a single semaphore to control access to a file where to keep track of the locks) as this would create a massive bottleneck (specially for those threads with different values).
Thank you very much.
https://github.com/arvenil/ninja-mutex
flock/mysql/redis/memcache adapter
You can try them all and pick the one that works for you
In your case example could look like this
<?php
require 'vendor/autoload.php';
use NinjaMutex\Lock\MemcacheLock;
use NinjaMutex\MutexFabric;
$memcache = new Memcache();
$memcache->connect('127.0.0.1', 11211);
$lock = new MemcacheLock($memcache);
$mutexFabric = new MutexFabric('memcache', $lock);
if ($mutexFabric->get($value)->acquireLock(1000)) {
// Do some very critical stuff
// and release lock after you finish
$mutexFabric->get($value)->releaseLock();
} else {
throw new Exception('Unable to gain lock for very critical stuff!');
}
From what I understand you want to make sure only a single process at a time is running a certain piece of code. I myself use lockfiles to have a solution that works on many platforms and doesn't rely on a specific library only available on Linux etc.
For that, I have written a small Lock class. Do note that it uses some non-standard functions from my library, for instance, to get where to store temporary files etc. But you could easily change that.
<?php
class Lock
{
private $_owned = false;
private $_name = null;
private $_lockFile = null;
private $_lockFilePointer = null;
public function __construct($name)
{
$this->_name = $name;
$this->_lockFile = PluginManager::getInstance()->getCorePlugin()->getTempDir('locks') . $name . '-' . sha1($name . PluginManager::getInstance()->getCorePlugin()->getPreference('EncryptionKey')->getValue()).'.lock';
}
public function __destruct()
{
$this->release();
}
/**
* Acquires a lock
*
* Returns true on success and false on failure.
* Could be told to wait (block) and if so for a max amount of seconds or return false right away.
*
* #param bool $wait
* #param null $maxWaitTime
* #return bool
* #throws \Exception
*/
public function acquire($wait = false, $maxWaitTime = null) {
$this->_lockFilePointer = fopen($this->_lockFile, 'c');
if(!$this->_lockFilePointer) {
throw new \RuntimeException(__('Unable to create lock file', 'dliCore'));
}
if($wait && $maxWaitTime === null) {
$flags = LOCK_EX;
}
else {
$flags = LOCK_EX | LOCK_NB;
}
$startTime = time();
while(1) {
if (flock($this->_lockFilePointer, $flags)) {
$this->_owned = true;
return true;
} else {
if($maxWaitTime === null || time() - $startTime > $maxWaitTime) {
fclose($this->_lockFilePointer);
return false;
}
sleep(1);
}
}
}
/**
* Releases the lock
*/
public function release()
{
if($this->_owned) {
#flock($this->_lockFilePointer, LOCK_UN);
#fclose($this->_lockFilePointer);
#unlink($this->_lockFile);
$this->_owned = false;
}
}
}
Usage
Now you can have two process that run at the same time and execute the same script
Process 1
$lock = new Lock('runExpensiveFunction');
if($lock->acquire()) {
// Some expensive function that should only run one at a time
runExpensiveFunction();
$lock->release();
}
Process 2
$lock = new Lock('runExpensiveFunction');
// Check will be false since the lock will already be held by someone else so the function is skipped
if($lock->acquire()) {
// Some expensive function that should only run one at a time
runExpensiveFunction();
$lock->release();
}
Another alternative would be to have the second process wait for the first one to finish instead of skipping the code.
$lock = new Lock('runExpensiveFunction');
// Process will now wait for the lock to become available. A max wait time can be set if needed.
if($lock->acquire(true)) {
// Some expensive function that should only run one at a time
runExpensiveFunction();
$lock->release();
}
RAM disk
To limit the number of writes to your HDD/SSD with the lockfiles you could crate a RAM disk to store them in.
On Linux you could add something like the following to /etc/fstab
tmpfs /mnt/ramdisk tmpfs nodev,nosuid,noexec,nodiratime,size=1024M 0 0
On Windows you can download something like ImDisk Toolkit and create a ramdisk with that.
If the number of different IDs you need to handle is just a handful you might want to consider using a queue system, eg. an AMQP compatible one like RabbitMQ and have one queue for each ID and then one consumer for each queue.
I'm trying to implement the following php code in visual basic. I wrote it in PHP because I knew I could do what I wanted to in that language, but I can't for the life of me figure out how to get it working in Visual Basic to loop through a MS SQL database.
$link = mysqli_connect('localhost', 'my_user', 'my_password', 'FakeDB');
$result = mysqli_query($con,"SELECT * FROM FakeDB");
$concatDupes = Array();
for ( $i = 0, $i< count($result), $i++) {
if ( $result[$i] == $result[$i + 1] {
array_push($concatDupes,$result[$i] + $result[$i + 1])
} else {
array_push( $concatDupes, $result[$i])
}
}
I've tried to use the code in this post but to no avail. (I put MsgBox("hello") inside the while loop and nothing happened) I've also attempeted the following, also with zero success.
Dim i = Me.PaymentTableAdapter.GetData().HazWasteAmtColumn.ToString()
MsgBox(i) 'no message box
Dim i = Me.PaymentTableAdapter.GetData().Count
MsgBox(i) ' also no message box
Dim i = Me.PaymentTableAdapter.GetData().Count
MsgBox(GetType(i)) 'type of i is not defined
I know my query is returning results because it works fine in a DataGridView.
Other useful info:
Watching the i (any of the i's referenced above) always shows Nothing, no matter what I've tried.
Here's what happens when I call Public Sub ReadData(ByVal connectionString As String, ByVal queryString As String) (full function in linked page) with:
Dim connect = WindowsApplication1.My.Settings.ShopMgtConnectionString
Dim querty As String = "SELECT * "
querty = querty + "FROM SM.PartItem "
ReadData(connect, querty)
immediate window output (the error list shows nothing):
A first chance exception of type 'System.ArgumentException' occurred in System.Data.dll
A first chance exception of type 'System.ArgumentException' occurred in System.Data.dll
A first chance exception of type 'System.ArgumentException' occurred in System.Data.dll
A first chance exception of type 'System.Data.ConstraintException' occurred in System.Data.dll
A first chance exception of type 'System.ArgumentException' occurred in System.Data.dll
EDIT: Here's the ReadData() method
Public Sub ReadData(ByVal connectionString As String, _
ByVal queryString As String)
Using connection As New OleDbConnection(connectionString)
Dim command As New OleDbCommand(queryString, connection)
connection.Open()
Dim reader As OleDbDataReader = command.ExecuteReader()
While reader.Read()
Console.WriteLine(reader(0).ToString())
End While
reader.Close()
End Using
End Sub
EDIT, Again...
I've taken a screen recording of the program running. It's just not doing anything useful.
http://youtu.be/i7oNVDXUw78
Make sure you are reading non null data:
While reader.Read()
If reader.IsDBNull(0) = False Then
Console.WriteLine(reader.GetValue(0).ToString())
End If
End While
I need to call an executable from an imposed context of a PHP script. Both performance and security wise it's better not to call a shell at all between web server process and executable.
Of course I searched the web, without success (in such a PHP context). Many other languages allow that and document it clearly.
Alas, backticks, exec(), shell_exec(), passthru(), system(), proc_open(), popen() call a shell.
And pcntl_fork() seems unavailable.
How to test if a function calls a shell or not.
This was tested on a Debian 6 64bit with PHP 5.3.3-7+squeeze15 .
Test code on http://pastebin.com/y4C7MeJz
To get a meaningful test I used a trick which is to ask to execute a shell command not also available as an executable. A good example is umask . Any function returning something like 0022 definitely called a shell. exec(), shell_exec(), passthru(), system(), proc_open() all did.
See detailed results on http://pastebin.com/RBcBz02F .
pcntl_fork fails
Now, back the the goal : how to execute arbitrary program without launching a shell ?
Php's exec takes as expected an array of string args instead of a unique string. But pcntl_fork just stops the requests without even a log.
Edit: pcntl_fork failure is because the server uses Apache's mod_php, see http://www.php.net/manual/en/function.pcntl-fork.php#49949 .
Edit: added popen() to the tests, following #hakre suggestion.
Any hint appreciated.
To answer your sentence :
Both performance and security wise it's better not to call a shell at
all between web server process and executable.
About performances, well, yes, php internals forks, and the shell itself forks too so that's a bit heavy. But you really need to execute a lot of processes to consider those performances issues.
About security, I do not see any issue here. PHP has the escapeshellarg function to sanitize arguments.
The only real problem I met with exec without pcntl is not a resource nor security issue : it is really difficult to create real deamons (without any attachment to its parent, particularily Apache). I solved this by using at, after double-escaping my command:
$arg1 = escapeshellarg($arg1);
$arg2 = escapeshellarg($arg2);
$command = escapeshellarg("/some/bin $arg1 $arg2 > /dev/null 2>&1 &");
exec("$command | at now -M");
To get back to your question, the only way I know to execute programs in a standard (fork+exec) way is to use the PCNTL extension (as already mentionned). Anyway, good luck!
To complete my answer, you can create an exec function yourself that does the same thing as pcntl_fork+pcntl_exec.
I made a my_exec extension that does a classic exec+fork, but actually, I do not think it will solve your issues if you're running this function under apache, because the same behaviour as pcntl_fork will apply (apache2 will be forked and there may be unexpected behaviours with signal catching and so on when execv does not succeed).
config.m4 the phpize configuration file
PHP_ARG_ENABLE(my_exec_extension, whether to enable my extension,
[ --enable-my-extension Enable my extension])
if test "$PHP_MY_EXEC_EXTENSION" = "yes"; then
AC_DEFINE(HAVE_MY_EXEC_EXTENSION, 1, [Whether you have my extension])
PHP_NEW_EXTENSION(my_exec_extension, my_exec_extension.c, $ext_shared)
fi
my_exec_extension.c the extension
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#define PHP_MY_EXEC_EXTENSION_VERSION "1.0"
#define PHP_MY_EXEC_EXTENSION_EXTNAME "my_exec_extension"
extern zend_module_entry my_exec_extension_module_entry;
#define phpext_my_exec_extension_ptr &my_exec_extension_module_entry
// declaration of a custom my_exec()
PHP_FUNCTION(my_exec);
// list of custom PHP functions provided by this extension
// set {NULL, NULL, NULL} as the last record to mark the end of list
static function_entry my_functions[] = {
PHP_FE(my_exec, NULL)
{NULL, NULL, NULL}
};
// the following code creates an entry for the module and registers it with Zend.
zend_module_entry my_exec_extension_module_entry = {
#if ZEND_MODULE_API_NO >= 20010901
STANDARD_MODULE_HEADER,
#endif
PHP_MY_EXEC_EXTENSION_EXTNAME,
my_functions,
NULL, // name of the MINIT function or NULL if not applicable
NULL, // name of the MSHUTDOWN function or NULL if not applicable
NULL, // name of the RINIT function or NULL if not applicable
NULL, // name of the RSHUTDOWN function or NULL if not applicable
NULL, // name of the MINFO function or NULL if not applicable
#if ZEND_MODULE_API_NO >= 20010901
PHP_MY_EXEC_EXTENSION_VERSION,
#endif
STANDARD_MODULE_PROPERTIES
};
ZEND_GET_MODULE(my_exec_extension)
char *concat(char *old, char *buf, int buf_len)
{
int str_size = strlen(old) + buf_len;
char *str = malloc((str_size + 1) * sizeof(char));
snprintf(str, str_size, "%s%s", old, buf);
str[str_size] = '\0';
free(old);
return str;
}
char *exec_and_return(char *command, char **argv)
{
int link[2], readlen;
pid_t pid;
char buffer[4096];
char *output;
output = strdup("");
if (pipe(link) < 0)
{
return strdup("Could not pipe!");
}
if ((pid = fork()) < 0)
{
return strdup("Could not fork!");
}
if (pid == 0)
{
dup2(link[1], STDOUT_FILENO);
close(link[0]);
if (execv(command, argv) < 0)
{
printf("Command not found or access denied: %s\n", command);
exit(1);
}
}
else
{
close(link[1]);
while ((readlen = read(link[0], buffer, sizeof(buffer))) > 0)
{
output = concat(output, buffer, readlen);
}
wait(NULL);
}
return output;
}
PHP_FUNCTION(my_exec)
{
char *command;
int command_len, argc, i;
zval *arguments, **data;
HashTable *arr_hash;
HashPosition pointer;
char **argv;
// recovers a string (s) and an array (a) from arguments
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "sa", &command, &command_len, &arguments) == FAILURE) {
RETURN_NULL();
}
arr_hash = Z_ARRVAL_P(arguments);
// creating argc and argv from our argument array
argc = zend_hash_num_elements(arr_hash);
argv = malloc((argc + 1) * sizeof(char *));
argv[argc] = NULL;
for (
i = 0, zend_hash_internal_pointer_reset_ex(arr_hash, &pointer);
zend_hash_get_current_data_ex(arr_hash, (void**) &data, &pointer) == SUCCESS;
zend_hash_move_forward_ex(arr_hash, &pointer)
)
{
if (Z_TYPE_PP(data) == IS_STRING) {
argv[i] = malloc((Z_STRLEN_PP(data) + 1) * sizeof(char));
argv[i][Z_STRLEN_PP(data)] = '\0';
strncpy(argv[i], Z_STRVAL_PP(data), Z_STRLEN_PP(data));
i++;
}
}
char *output = exec_and_return(command, argv);
// freeing allocated memory
for (i = 0; (i < argc); i++)
{
free(argv[i]);
}
free(argv);
// WARNING! I guess there is a memory leak here.
// Second arguemnt to 1 means to PHP: do not free memory
// But if I put 0, I get a segmentation fault
// So I think I do not malloc correctly for a PHP extension.
RETURN_STRING(output, 1);
}
test.php a usage sample
<?php
dl("my_exec.so");
$output = my_exec("/bin/ls", array("-l", "/"));
var_dump($output);
shell script run those commands, of course use your own module directory
phpize
./configure
make
sudo cp modules/my_exec_extension.so /opt/local/lib/php/extensions/no-debug-non-zts-20090626/my_exec.so
Result
KolyMac:my_fork ninsuo$ php test.php
string(329) ".DS_Store
.Spotlight-V100
.Trashes
.file
.fseventsd
.hidden
.hotfiles.btree
.vol
AppleScript
Applications
Developer
Installer Log File
Library
Microsoft Excel Documents
Microsoft Word Documents
Network
System
Users
Volumes
bin
cores
dev
etc
home
lost+found
mach_kernel
net
opt
private
sbin
tmp
usr
var
vc_command.txt
vidotask.txt"
I am not a C dev, so I think there are cleaner ways to achieve this. But you get the idea.
In PHP 7.4+, proc_open open processes directly if cmd is passed as array.
As of PHP 7.4.0, cmd may be passed as array of command parameters. In this case the process will be opened directly (without going through a shell) and PHP will take care of any necessary argument escaping.
So this example:
<?php
$file_descriptors = [
0=>['pipe','r'],
1=>['pipe','w'],
2=>['pipe','w']
];
$cmd_string = 'ps -o comm=';
$cmd_array = [
'ps',
'-o',
'comm='
];
// This is executed by shell:
$process = proc_open($cmd_string,$file_descriptors,$pipes);
$output = stream_get_contents($pipes[1]);
$return = proc_close($process);
printf("cmd_string:\n%s\n",$output);
// This is executed directly:
$process = proc_open($cmd_array,$file_descriptors,$pipes);
$output = stream_get_contents($pipes[1]);
$return = proc_close($process);
printf("cmd_array:\n%s\n",$output);
outputs:
cmd_string:
bash
php
sh
ps
cmd_array:
bash
php
ps
Id consider trying pcntl_exec()