maxima- How can I execute some maxima script from php? - php

I'm new to CAS and maxima. I'd like to know whether it's feasible to do the following:
1) I have a list of parameters e.g. a, b, c
2) In PHP, I have some maxima script stored as a string, involving a, b, c, e.g.:
do {
a=random(20);
b=random(20);
c=random(20);
}while (!(a > b) || !(b > c))
Such that a, b, c are randomized to desired values and satisfy requirements.
3) Retrieve the values of a, b, c in PHP.
The purpose is to create randomized questions with reasonable parameters for students. So how can I execute the maxima script and retrieve the values of parameters? Is it suitable for my purpose?

You can pass the command string in a file to Maxima, that is supported by command-line Maxima.
if your OS is Linux/Unix/MacOS:
In PHP:
exec('maxima -q -b file');
or
system('maxima -q -b file');
if your OS is Win:
$maximaDir = 'D:/Program Files/Maxima-5.30.0'; // If your maxima is installed in elsewhere, please modified this location
exec($maximaDir.'/bin/maxima.bat -q -b "result.txt"');
In Maxima, you can use stringout(); to get the result in a file, then in PHP read the file as a string, you can do any other manipulations to the string as you want.

I dont really know how your code works but if you save the maxima as a php extension it could work. Place this line of code at the start of the php file
<?php
require_once("extension/Maxima.php");
?>
For the echo example
echo $A ;

<?php
require_once($_SERVER['PM_BASE_CONFIG_PATH']);
class maxima_core {
private $executable_command;
protected $dbg_bool;
protected $dbg_info;
public function __construct($dbg=FALSE){
$this->executable_command=constant('PM_MAXIMA_EXEC_CMD');
$this->dbg_bool=$dbg;
$this->dbg_info="";
}
protected function exec($query){// to include package that is loaded by init_command
$descriptor = 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", "/tmp/error-log.txt", "a")//constant('PM_SERVER_LOG_DIR')."/maxima/error.log", "a") // stderr is a file to write to
);
$cwd=constant('PM_ACTIVITY_PLUGIN_URL')."/engine_solver";
$MAXIMA_DIR = constant('PM_ACTIVITY_PLUGIN_DIR');
$env=array();
$init_command="display2d:false$" . "PM_ACTIVITY_PLUGIN_URL: \"" . $MAXIMA_DIR . "\"$";
//'load("/home/gabriel/github/moodledata/stack/maximalocal.mac");';
$exec_cmd=$this->executable_command." --quiet";
// --userdir='".constant('PM_ACTIVITY_PLUGIN_DIR')."/engine_solver/maxima_userdir'";
// change
$result=NULL;
$process=proc_open($exec_cmd,$descriptor,$pipes,$cwd,$env);
if(is_resource($process)){
if (!fwrite($pipes[0], $init_command)) {
echo "<br />Could not write to the CAS process!<br />\n";
} else {
fwrite($pipes[0], $query);
fwrite($pipes[0], "quit();");
fclose($pipes[0]);
$result=stream_get_contents($pipes[1]);
fclose($pipes[1]);
proc_close($process);
}
}
return $result;
}
public function dbg_info(){
return $this->dbg_info;
}
}
?>

If the goal is to use Maxima to create homework problems which are presented by a web server, I think that there have been projects to do just that. I think one of them is named LON-CAPA -- a web search should find it. There might be some other projects mentioned on the related projects page at the Maxima web site. [1]
[1] http://maxima.sf.net/relatedprojects.html

Related

Best way to open a PTY with PHP

Problem description
I would like to open a Linux pseudoterminal with PHP but it seems there is no simple way of doing that. I have experimented with different solutions but none seem to be good enough.
The goal of the PTY is to emulate a terminal with the capability of flawlessly interacting with programs such as zsh and sudo. Other programming languages including Python and C have functions or libraries for that. Python has the PTY library which can simply do pty.spawn("/bin/zsh"), and C has the openpty() function.
My ideal end goal is to have a PHP function that allows me to read and write from/into the PTY terminal and does not require installing external libraries. (A lot of shared hosting providers do not allow that.)
What I have tried so far
Using proc_open()
My initial idea was just to use the proc_open() PHP function to create a Bash terminal with stdin, stdout and stderr pipes (Based on Example #1 in the PHP documentation) This, however, did soon prove to be problematic because it is actually not a real PTY. Running stty -a errored with stty: stdin isn't a terminal. Here are the instructions for reproducing this.
Run this with php pty_test.php
Read the output of the shell with cat /tmp/stdout.
Input commands with > /tmp/stdin.
Here is the PHP code which I used for this:
<?php
/* pty_test.php */
ini_set('display_errors', 1);
ini_set('display_startup_ūūerrors', 1);
error_reporting(E_ALL);
define("STD_IN", 0);
define("STD_OUT", 1);
define("STD_ERR", 2);
set_time_limit(0);
umask(0);
$chunk_size = 1400;
$write_a = null;
$error_a = null;
$shell = "/bin/sh -i ";
$stdin_fifo = "/tmp/stdin";
$stdout_fifo = "/tmp/stdout";
posix_mkfifo($stdin_fifo, 0644);
posix_mkfifo($stdout_fifo, 0644);
$resource_stdin = fopen($stdin_fifo, "rb+");
$resource_stdout = fopen($stdout_fifo, "wb+");
$descriptorspec = array(
STD_IN => array("pipe", "rb"),
STD_OUT => array("pipe", "wb"),
STD_ERR => array("pipe", "wb")
);
$process = proc_open($shell, $descriptorspec, $pipes, null, $env = null);
stream_set_blocking($pipes[STD_IN], 0);
stream_set_blocking($pipes[STD_OUT], 0);
stream_set_blocking($pipes[STD_ERR], 0);
stream_set_blocking($resource_stdin, 0);
stream_set_blocking($resource_stdout, 0);
while (1) {
$read_a = array($resource_stdin, $pipes[STD_OUT], $pipes[STD_ERR]);
$num_changed_streams = stream_select($read_a, $write_a, $error_a, null);
if (in_array($resource_stdin, $read_a)) {
$input = fread($resource_stdin, $chunk_size);
fwrite($pipes[STD_IN], $input);
}
if (in_array($pipes[STD_OUT], $read_a)) {
$input = fread($pipes[STD_OUT], $chunk_size);
fwrite($resource_stdout, $input);
}
if (in_array($pipes[STD_ERR], $read_a)) {
$input = fread($pipes[STD_ERR], $chunk_size);
fwrite($resource_stdout, $input);
}
}
fclose($resource_stdin);
fclose($resource_stdout);
fclose($pipes[STD_IN]);
fclose($pipes[STD_OUT]);
fclose($pipes[STD_ERR]);
proc_close($process);
unlink($stdin_fifo);
unlink($stdout_fifo);
?>
Python PTY
I noticed that running python3 -c "import pty;pty.spawn('/bin/bash');" in the non-pty shell (which I described above) will result in a fully interactive PTY shell as I desired. This resulted in a half-good solution: setting the $shell variable to be python3 -c "import pty;pty.spawn('/bin/bash')" will spawn the interactive shell using Python3. But relying on external software is not ideal since having Python3 is not always guaranteed. (And this solution also feels way too hacky...)
/dev/ptmx
I was reading the source code of the proc_open() function also found the source for openpty(). Unfortunately, PHP can't directly call this function but perhaps it is possible to replicate the behavior of it.
I could fopen("/dev/ptmx","r+") to create a new slave but openpty() also uses grantpt() and unlockpt(), which are not available in PHP.
Foreign Function Interface
FFI allows access to external libraries. Maybe it would be possible to import pty.h and to run openpty(). Unfortunately, FFI is very experimental and may not always be available.
TL;DR
What is the safest and most reliable way to spawn a PTY using PHP?
You do not have to use FFI to write PHP shared library.
I just tried to write an open source PHP library for this purpose. I named it TeaOpenPTY. I think this can be a good example how to write a simple PHP library in C.
GitHub repo: https://github.com/ammarfaizi2/TeaOpenPTY
Precompiled Shared Lib: https://github.com/ammarfaizi2/TeaOpenPTY/raw/master/compiled/tea_openpty.so
How to use the TeaOpenPTY library?
File test.php
<?php
use TeaOpenPTY\TeaOpenPTY;
$app = "/usr/bin/bash";
$argv = [$app, "-i"];
$teaOpenPTY = new TeaOpenPTY($app);
echo "Starting TeaOpenPTY...\n";
$ret = $teaOpenPTY->exec(...$argv);
if ($ret === -1) {
echo "Error: ", $teaOpenPTY->error(), "\n";
}
echo "TeaOpenPTY terminated!\n";
Run
ammarfaizi2#integral:/tmp$ wget https://github.com/ammarfaizi2/TeaOpenPTY/raw/master/compiled/tea_openpty.so
[...output abbreviated...]
2020-12-28 14:39:20 (612 KB/s) - ‘tea_openpty.so’ saved [19048/19048]
ammarfaizi2#integral:/tmp$ echo $$ # Show the current bash PID
19068
ammarfaizi2#integral:/tmp$ php -d extension=$(pwd)/tea_openpty.so test.php
Starting TeaOpenPTY...
ammarfaizi2#integral:/tmp$ echo $$ # Now we are in the terminal spawned by tea_openpty
329423
ammarfaizi2#integral:/tmp$ stty -a
speed 38400 baud; rows 46; columns 192; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>;
eol2 = <undef>; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R;
werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff
-iuclc -ixany -imaxbel iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt
echoctl echoke -flusho -extproc
ammarfaizi2#integral:/tmp$ exit # Terminate the terminal
exit
TeaOpenPTY terminated!
ammarfaizi2#integral:/tmp$ echo $$
19068
ammarfaizi2#integral:/tmp$

Php proc_open - save process handle to a file and retrieve it

I use the following code to open a process with proc_open, and to save the handle and the pipes to a file:
$command = "COMMAND_TO_EXECUTE";
$descriptors = 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", "error-output.txt", "a") // stderr is a file to write to
);
$pipes = array();
$processHandle = proc_open($command, $descriptors, $pipes);
if (is_resource($processHandle)) {
$processToSave = array(
"process" => $processHandle,
"pipes" => $pipes
);
file_put_contents("myfile.bin", serialize($processToSave) );
}
And in a second moment i need to retrieve this file handle from the file, i've used this code:
$processArray = unserialize(file_get_contents("myfile.bin"));
$processHandle = $processArray["process"];
$pipes = $processArray["pipes"];
But when I print a var_dump of $processHandle and $pipes after retrieving from file, I'll get integers instead of resource or process, but why??
var_dump($processHandle) -> int(0)
var_dump($pipes) - > array(2) { int(0), int(0) }
And at this point of course, if I try to close the pipes, i will get an error, resource expected, integer given.
How can I make this working? (NOTE: This is the solution I'm looking for)
But alternatively, I can get also the pid of process and then use this pid to stop or kill or do anything else with the process, but what about the pipes?
How can I read/write or save error from/to the process?
Thank you
Found the solution to myself, it's not to possible to serialize resource and when the script has done, those resource handler were free.
Solution was to create a daemon listening on a port, wich on request launch and stop process. Because the process is always running, it can maintain a list of handler process and stop when requested.

Trying to understand the difference between /dev/stdin and php://stdin

As a simple proof of concept, I tried to share a string between forked processes from node to node or from node to php.
Take this simple php code that should log the output of stdin according to the php docs:
echo 'test' | php -r 'echo trim(fgets(STDIN));'
Working fine, but when I'm spawning the process from nodejs:
spawner.js
var fs = require('fs'); var spawn = require('child_process').spawn;
//dummy stdin file
var stdin = fs.openSync('stdin_file', 'w+');
//write the string
fs.writeSync(stdin, 'test');
spawn('php', ['stdin_test.php'], {
cwd: __dirname,
detached: true,
//to fully detach the process nothing should be piped from or to the parent process
stdio: [stdin, fs.openSync('out.log', 'a'), fs.openSync('err.log', 'a')]
})
stdin_test.php
<?php
error_log('php://stdin');
//this should log 'test' but outputs a newline
error_log(trim(fgets(STDIN)));
$t = fopen('/dev/stdin', 'r');
error_log('/dev/stdin:');
//this is working as expected
error_log(trim(fgets($t)));
Why is php://stdin empty? Is it safe to use /dev/stdin? What is the difference between /dev/stdin and php://stdin anyway?
Note that I have this behavior between 2 node processes too: process.stdin is empty but /dev/stdin has the expected result.
Gist available here
stdin man reference
I tested with the following script ( stdin_test.php ) using:
> echo test | php stdin_test.php
stdin_test.php
<?
echo 'STDIN :' ;
echo trim(fgets(STDIN)) ;
echo PHP_EOL;
$stdin_stream = fopen('php://stdin', 'r');
echo 'php://stdin :';
echo trim(fgets($stdin_stream));
echo PHP_EOL;
fclose($stdin_stream);
$stdin_file = fopen('/dev/stdin', 'r');
echo '/dev/stdin :';
echo trim(fgets($stdin_file));
echo PHP_EOL;
fclose($stdin_file);
I get back :
STDIN :test
php://stdin :
/dev/stdin :
If I then comment out the line:
//echo trim(fgets(STDIN));
I get back:
STDIN :
php://stdin :test
/dev/stdin :
If I comment out both of the first stdin echoes (and the file handler pointers), I get:
STDIN :
php://stdin :
/dev/stdin : test
Looking at documentation on php://input and how it is one-time usable unless (after 5.6) "the request body is saved" which is typical for POST requests but not PUT requests (apparently). This has me thinking that they are called "streams" because you get to walk in them once.
Rewind your stdin stream in JS before spawning PHP, else the file pointer will sit at the end of what you just wrote.

Connect pipes of processes in php

I would like the output of one process created with proc_open to be piped to another one created with proc_open (in php). For example. In bash I can do:
[herbert#thdev1 ~]$ cat foo
2
3
1
[herbert#thdev1 ~]$ cat foo | sort
1
2
3
[herbert#thdev1 ~]$
I would like to simulate this in php using proc_open (instead of shell_exec) in order to have control over return-codes, pipes, etc. So I want something like this:
$catPipes=array();
$sortPipes=array();
$cwd = '/tmp';
$env = array();
$catProcess = proc_open("cat foo", array(
0 => array("pipe", "r"),
1 => array("pipe", "w")
), $catPipes, $cwd, $env);
$sortProcess = proc_open("sort", array(
0 => array("pipe", "r", $catPipes[1]),
1 => array("pipe", "w"),
), $sortPipes, $cwd, $env);
echo stream_get_contents($sortPipes[1]);
fclose($sortPipes[1]);
//proc_close(this) ... proc_close(that) ... etc
Would someone know how I can simulate the "|" of bash in php, i.e. connect the second descriptor of the cat-process to the first descriptor of the sort-process? Any help would be appreciated! But please do not redirect me to shell_exec, as I want to be able to check exit-codes and log errors :).
EDIT:
My needs-to-work-business-solution btw is:
while(!feof($searchPipes[1])) fwrite($lookupPipes[0], stream_get_line($searchPipes[1], 40000));
Which is basically what the OS would do, but I do not want to my own pipe-management, as I have a kernel/posix for that, and let's be honest, it's not 1976 :)
Yes, you can -- but i think you have to define this the way round. That you can use the STDIN of "sort" as STDOUT pipe for "cat". Have a look at the following, which works for me:
<?php
$txt = "a\nc\ne\nb\nd\n";
$fh = fopen('data://text/plain;base64,' . base64_encode($txt), 'r');
$sort_pipes = array();
$sort_proc = proc_open(
'sort',
array(
array('pipe', 'r'),
STDOUT
),
$sort_pipes
);
$cat_pipes = array();
$cat_proc = proc_open(
'cat',
array(
$fh,
$sort_pipes[0]
),
$cat_pipes
);
In the first two rows i defined a data stream from a text string that i do not have to rely on a file somewhere in the filesystem. Note, that i have a list of unsorted characters stored in the data stream (a, c, e, b, d). Running the script above should return a sorted list to STDOUT.
Note, that you can specify resources as descriptors, too. In this case you must omit the array notation, so:
STDOUT
instead of
array(STDOUT)
etc.
Btw.: you can even write directly to a file specified by a file name. You can find further information on the descriptor specification in the manual entry for proc_open at http://en.php.net/manual/de/function.proc-open.php
EDIT
The other way works too, of course: you can also write "cat" to an STDOUT pipe array('pipe', 'w') and use $cat_pipes[1] as STDIN for "sort". :)

Creating a PHP Online Grading System on Linux: exec Behavior, Process IDs, and grep

Background
I am writing a simple online judge (a code grading system) using PHP and MySQL. It takes submitted codes in C++ and Java, compiles them, and tests them.
This is Apache running PHP 5.2 on an old version of Ubuntu.
What I am currently doing
I have a php program that loops infinitely, calling another php program by
//for(infinity)
exec("php -f grade.php");
//...
every tenth of a second. Let's call the first one looper.php and the second one grade.php. (Checkpoint: grade.php should completely finish running before the "for" loop continues, correct?)
grade.php pulls the earliest submitted code that needs to be graded from the MySQL database, puts that code in a file (test.[cpp/java]), and calls 2 other php programs in succession, named compile.php and test.php, like so:
//...
exec("php -f compile.php");
//...
//for([all tests])
exec("php -f test.php");
//...
(Checkpoint: compile.php should completely finish running before the "for" loop calling test.php even starts, correct?)
compile.php then compiles the program in test.[cpp/java] as a background process. For now, let's assume that it's compiling a Java program and that test.java is located in a subdirectory. I now have
//...
//$dir = "./sub/" or some other subdirectory; this may be an absolute path
$start_time = microtime(true); //to get elapsed compilation time later
exec("javac ".$dir."test.java -d ".$dir." 2> ".$dir
."compileError.txt 1> ".$dir."compileText.txt & echo $!", $out);
//...
in compile.php. It's redirecting the output from javac, so javac should be running as a background process... and it seems like it works. The $out should be grabbing the process id of javac in $out[0].
The real problem
I want to stop compiling if for some reason compiling takes more than 10 seconds, and I want to end compile.php if the program stops compiling before 10 seconds. Since the exec("javac... I called above is a background process (or is it?), I have no way of knowing when it has completed without looking at the process id, which should have been stored in $out earlier. Right after, in compile.php, I do this with a 10 second loop calling exec("ps ax | grep [pid].*javac"); and seeing if the pid still exists:
//...
$pid = (int)$out[0];
$done_compile = false;
while((microtime(true) - $start_time < 10) && !$done_compile) {
usleep(20000); // only sleep 0.02 seconds between checks
unset($grep);
exec("ps ax | grep ".$pid.".*javac", $grep);
$found_process = false;
//loop through the results from grep
while(!$found_process && list(, $proc) = each($grep)) {
$boom = explode(" ", $proc);
$npid = (int)$boom[0];
if($npid == $pid)
$found_process = true;
}
$done_compile = !$found_process;
}
if(!done_compile)
exec("kill -9 ".$pid);
//...
... which doesn't seem to be working. At least some of the time. Often, what happens is test.php starts running before the javac even stops, resulting in test.php not being able to find the main class when it tries to run the java program. I think that the loop is bypassed for some reason, though this may not be the case. At other times, the entire grading system works as intended.
Meanwhile, test.php also uses the same strategy (with the X-second loop and the grep) in running a program in a certain time limit, and it has a similar bug.
I think the bug lies in the grep not finding javac's pid even when javac is still running, resulting in the 10 second loop breaking early. Can you spot an obvious bug? A more discreet bug? Is there a problem with my usage of exec? Is there a problem with $out? Or is something entirely different happening?
Thank you for reading my long question. All help is appreciated.
I just came up with this code that will run a process, and terminate it if it runs longer than $timeout seconds. If it terminates before the timeout, it will have the program output in $output and the exit status in $return_value.
I have tested it and it seems to work well. Hopefully you can adapt it to your needs.
<?php
$command = 'echo Hello; sleep 30'; // the command to execute
$timeout = 5; // terminate process if it goes longer than this time in seconds
$cwd = '/tmp'; // working directory of executing process
$env = null; // environment variables to set, null to use same as PHP
$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", "/tmp/error-output.txt", "a") // stderr is a file to write to
);
// start the process
$process = proc_open($command, $descriptorspec, $pipes, $cwd, $env);
$startTime = time();
$terminated = false;
$output = '';
if (is_resource($process)) {
// process was started
// $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
// loop infinitely until timeout, or process finishes
for(;;) {
usleep(100000); // dont consume too many resources
$stat = proc_get_status($process); // get info on process
if ($stat['running']) { // still running
if (time() - $startTime > $timeout) { // check for timeout
// close descriptors
fclose($pipes[1]);
fclose($pipes[0]);
proc_terminate($process); // terminate process
$return_value = proc_close($process); // get return value
$terminated = true;
break;
}
} else {
// process finished before timeout
$output = stream_get_contents($pipes[1]); // get output of command
// close descriptors
fclose($pipes[1]);
fclose($pipes[0]);
proc_close($process); // close process
$return_value = $stat['exitcode']; // set exit code
break;
}
}
if (!$terminated) {
echo $output;
}
echo "command returned $return_value\n";
if ($terminated) echo "Process was terminated due to long execution\n";
} else {
echo "Failed to start process!\n";
}
References: proc_open(), proc_close(), proc_get_status(), proc_terminate()

Categories