/*-*- mode: C; tab-width:4 -*-*/
/* execve */
#include <unistd.h>
#include <sys/types.h>
/* stat */
#include <sys/stat.h>
/* fcntl */
#include <fcntl.h>
/* strings */
#include <string.h>
/* setenv */
#include <stdlib.h>
/* signal */
#include <signal.h>
/* poll */
#include <sys/poll.h>
/* wait */
#include <sys/wait.h>
/* miscellaneous */
#include <stdio.h>
#include <errno.h>
#include <time.h>
/* path and dir separators */
#include "php_wrapper.h"
#include "zend.h"
#include "ext/session/php_session.h"
#include "php_java.h"
#include "java_bridge.h"
#ifndef EXTENSION_DIR
#error EXTENSION_DIR must point to the PHP extension directory
#endif
const static char inet_socket_prefix[]="INET_LOCAL:";
const static char local_socket_prefix[]="LOCAL:";
const static char ext_dir[] = "extension_dir";
EXT_EXTERN_MODULE_GLOBALS(EXT)
#if EXTENSION == JAVA
static const char* const wrapper = EXTENSION_DIR/**/"/RunJavaBridge";
static void EXT_GLOBAL(get_server_args)(char*env[N_SENV], char*args[N_SARGS], short for_display TSRMLS_DC) {
static const char separator[2] = {ZEND_PATHS_SEPARATOR, 0};
char *s, *p;
char*program=EXT_GLOBAL(cfg)->vm;
char*cp=EXT_GLOBAL(cfg)->classpath;
char*lib_path=EXT_GLOBAL(cfg)->ld_library_path;
char*sys_libpath=getenv("LD_LIBRARY_PATH");
char*home=EXT_GLOBAL(cfg)->vm_home;
char *ext = php_ini_string((char*)ext_dir, sizeof ext_dir, 0);
#ifdef CFG_JAVA_SOCKET_INET
const char* s_prefix = inet_socket_prefix;
#else
const char *s_prefix = local_socket_prefix;
#endif
char *sockname, *cfg_sockname=EXT_GLOBAL(get_sockname)(TSRMLS_C), *cfg_logFile=EXT_GLOBAL(cfg)->logFile;
/* if socketname is off, show the user how to start a TCP backend */
if(for_display && !(EXT_GLOBAL(option_set_by_user) (U_SOCKNAME, EXT_GLOBAL(ini_user)))) {
cfg_sockname="0";
s_prefix=inet_socket_prefix;
cfg_logFile="";
}
/* send a prefix so that the server does not select a different
protocol */
sockname = malloc(strlen(s_prefix)+strlen(cfg_sockname)+1);
strcpy(sockname, s_prefix);
strcat(sockname, cfg_sockname);
/* library path usually points to the
extension dir */
if(!(EXT_GLOBAL(option_set_by_user) (U_LIBRARY_PATH, EXT_GLOBAL(ini_user)))) { /* look into extension_dir then */
if(ext) lib_path = ext;
}
if(!sys_libpath) sys_libpath="";
args[0]=strdup(program);
s="-Djava.library.path=";
p=malloc(strlen(s)+strlen(lib_path)+1);
strcpy(p, s); strcat(p, lib_path);
args[1] = p; /* library path */
s="-Djava.class.path=";
/* library path usually points to the
extension dir */
if(ext && !(EXT_GLOBAL(option_set_by_user) (U_CLASSPATH, EXT_GLOBAL(ini_user)))) { /* look into extension_dir then */
static char bridge[]="/JavaBridge.jar";
char *slash;
p=malloc(strlen(s)+strlen(ext)+sizeof(bridge));
strcpy(p, s); strcat(p, ext);
slash=p+strlen(p)-1;
if(*p&&*slash==ZEND_PATHS_SEPARATOR) *slash=0;
strcat(p, bridge);
} else {
p=malloc(strlen(s)+strlen(cp)+1);
strcpy(p, s); strcat(p, cp);
}
args[2] = p; /* user classes */
args[3] = strdup("-Djava.awt.headless=true");
/* disabled due to problems with IBM java, it could not find
default mime table anymore */
//s="-Djava.home=";
//p=malloc(strlen(s)+strlen(home)+1);
//strcpy(p, s); strcat(p, home);
//args[4] = p; /* java home */
args[4] = strdup("php.java.bridge.JavaBridge");
args[5] = sockname;
args[6] = strdup(EXT_GLOBAL(cfg)->logLevel);
args[7] = strdup(cfg_logFile);
args[8] = NULL;
s="JAVA_HOME=";
p=malloc(strlen(s)+strlen(home)+1);
strcpy(p, s); strcat(p, home);
env[0] = p; /* java home */
s="LD_LIBRARY_PATH=";
p=malloc(strlen(s)+strlen(lib_path)+1+strlen(sys_libpath)+1);
strcpy(p, s); strcat(p, lib_path);
strcat(p, separator); strcat(p, sys_libpath);
env[1] = p; /* library path */
env[2] = NULL;
}
#elif EXTENSION == MONO
static const char* const wrapper = EXTENSION_DIR/**/"/RunMonoBridge";
static void EXT_GLOBAL(get_server_args)(char*env[N_SENV], char*args[N_SARGS], short for_display TSRMLS_DC) {
static const char executable[] = "/MonoBridge.exe";
char *p, *slash;
char*program=EXT_GLOBAL(cfg)->vm;
#ifdef CFG_JAVA_SOCKET_INET
const char* s_prefix = inet_socket_prefix;
#else
const char *s_prefix = local_socket_prefix;
#endif
char *sockname, *cfg_sockname=EXT_GLOBAL(get_sockname)(TSRMLS_C), *cfg_logFile=EXT_GLOBAL(cfg)->logFile;
char*home = EXT_GLOBAL(cfg)->vm_home;
if(!(EXT_GLOBAL(option_set_by_user) (U_JAVA_HOME, EXT_GLOBAL(ini_user)))) { /* look into extension_dir then */
char *ext = php_ini_string((char*)ext_dir, sizeof ext_dir, 0);
if(ext) home = ext;
}
args[0]=strdup(program); /* mono */
p=malloc(strlen(home)+sizeof executable);
strcpy(p, home);
slash=p+strlen(p)-1;
if(*p&&*slash==ZEND_PATHS_SEPARATOR) *slash=0;
strcat(p, executable);
args[1] = p;
/* if socketname is off, show the user how to start a TCP backend */
if(for_display && !(EXT_GLOBAL(option_set_by_user) (U_SOCKNAME, EXT_GLOBAL(ini_user)))) {
static const char zero[] = "0";
cfg_sockname="0";
s_prefix=inet_socket_prefix;
cfg_logFile="";
}
/* send a prefix so that the server does not select a different */
/* protocol */
sockname = malloc(strlen(s_prefix)+strlen(cfg_sockname)+1);
strcpy(sockname, s_prefix);
strcat(sockname, cfg_sockname);
args[2] = sockname;
args[3] = strdup(EXT_GLOBAL(cfg)->logLevel);
args[4] = strdup(cfg_logFile);
args[5] = NULL;
env[0] = NULL;
}
#endif
static short use_wrapper(void) {
struct stat buf;
short use_wrapper=0;
#ifndef __MINGW32__
if(!stat(wrapper, &buf) && (S_IFREG&buf.st_mode)) {
if(getuid()==buf.st_uid)
use_wrapper=(S_IXUSR&buf.st_mode);
else if(getgid()==buf.st_gid)
use_wrapper=(S_IXGRP&buf.st_mode);
else
use_wrapper=(S_IXOTH&buf.st_mode);
}
#endif
return use_wrapper;
}
/*
* Get a string of the server arguments. Useful for display only.
*/
static char*get_server_string(short for_display TSRMLS_DC) {
#ifndef __MINGW32__
static const char quote[] = "'";
#else
static const char quote[] = "\"";
#endif
short must_use_wrapper = use_wrapper();
int i;
char*s;
char*env[N_SENV];
char*args[N_SARGS];
unsigned int length = 0;
EXT_GLOBAL(get_server_args)(env, args, for_display TSRMLS_CC);
if(must_use_wrapper)
length+=strlen(wrapper)+1;
#ifndef __MINGW32__
for(i=0; i< (sizeof env)/(sizeof*env); i++) {
if(!env[i]) break;
length+=strlen(env[i])+1;
}
#endif
for(i=0; i< (sizeof args)/(sizeof*args); i++) {
size_t l;
if(!args[i]) break;
l=strlen(args[i]);
length+=(l?l:2)+1;
}
s=malloc(length+1);
assert(s); if(!s) exit(9);
*s=0;
#ifndef __MINGW32__
for(i=0; i< (sizeof env)/(sizeof*env); i++) {
if(!env[i]) break;
strcat(s, env[i]); strcat(s, " ");
free(env[i]);
}
#endif
if(must_use_wrapper) {
strcat(s, wrapper);
strcat(s, " ");
}
for(i=0; i< (sizeof args)/(sizeof*args); i++) {
if(!args[i]) break;
if(!strlen(args[i])) strcat(s,quote);
strcat(s, args[i]);
if(!strlen(args[i])) strcat(s,quote);
strcat(s, " ");
free(args[i]);
}
s[length]=0;
return s;
}
char*EXT_GLOBAL(get_server_string)(TSRMLS_D) {
return get_server_string(1 TSRMLS_CC);
}
static void exec_vm(TSRMLS_D) {
#ifdef CFG_JAVA_INPROCESS
extern int EXT_GLOBAL(bridge_main)(int argc, char**argv) ;
static char*env[N_SENV];
static char*args[N_SARGS];
EXT_GLOBAL(get_server_args)(env, args, 0);
if(N_SENV>2) {
putenv(env[0]);
putenv(env[1]);
}
EXT_GLOBAL(bridge_main)(N_SARGS, args);
#else
static char*env[N_SENV];
static char*_args[N_SARGS+1];
char **args=_args+1;
EXT_GLOBAL(get_server_args)(env, args, 0 TSRMLS_CC);
if(N_SENV>2) {
putenv(env[0]);
putenv(env[1]);
}
if(use_wrapper()) *--args = strdup(wrapper);
execv(args[0], args);
#endif
}
static int test_local_server(void) {
int sock, n;
puts("test local");
#ifndef CFG_JAVA_SOCKET_INET
sock = socket (PF_LOCAL, SOCK_STREAM, 0);
#else
sock = socket (PF_INET, SOCK_STREAM, 0);
#endif
if(sock==-1) return -1;
n = connect(sock,(struct sockaddr*)&EXT_GLOBAL(cfg)->saddr, sizeof EXT_GLOBAL(cfg)->saddr);
if(n==-1) { close(sock); return -1; }
return sock;
}
/*
return 0 if user has hard-coded the socketname
*/
static short can_fork(void) {
return EXT_GLOBAL(cfg)->can_fork;
}
/*
* Test for a running server. Return the server name and the socket
* if _socket!=NULL. If all ckecks fail a local backend is started.
*/
char* EXT_GLOBAL(test_server)(int *_socket, short *local, struct sockaddr*_saddr TSRMLS_DC) {
int sock;
short called_from_init = !(local || _socket);
/* java.servlet=On forces
java.socketname Off */
short socketname_set =
(EXT_GLOBAL(option_set_by_user) (U_SOCKNAME, JG(ini_user))) &&
!(EXT_GLOBAL(option_set_by_user) (U_SERVLET, JG(ini_user)));
if(local) *local=0;
/* check for local server if socketname set or (socketname not set
and hosts not set), in which case we may have started a local
backend ourselfs. Do not check if socketname not set and we are
called from init, in which case we know that a local backend is
not running. */
if (((socketname_set || can_fork()) && (socketname_set || !called_from_init))
&& -1!=(sock=test_local_server()) ) {
if(_socket) {
*_socket=sock;
} else {
close(sock);
}
if(local) *local=1;
return strdup(EXT_GLOBAL(get_sockname)(TSRMLS_C));
}
/* host list */
if(JG(hosts) && strlen(JG(hosts))) {
char *host, *hosts = strdup(JG(hosts));
assert(hosts); if(!hosts) return 0;
for(host=strtok(hosts, ";"); host; host=strtok(0, ";")) {
struct sockaddr_in saddr;
char *_port = strrchr(host, ':'), *ret;
int port = 0;
if(_port) {
*_port++=0;
if(strlen(_port)) port=atoi(_port);
}
if(!port) port=atoi(DEFAULT_PORT);
memset(&saddr, 0, sizeof saddr);
saddr.sin_family = AF_INET;
saddr.sin_port=htons(port);
#ifndef __MINGW32__
if(!isdigit(*host)) {
struct hostent *hostent = gethostbyname(host);
if(hostent) {
memcpy(&saddr.sin_addr,hostent->h_addr,sizeof(struct in_addr));
} else {
inet_aton(host, &saddr.sin_addr);
}
} else {
inet_aton(host, &saddr.sin_addr);
}
#else
saddr.sin_addr.s_addr = inet_addr(host);
#endif
sock = socket (PF_INET, SOCK_STREAM, 0);
if(-1==sock) continue;
if (-1==connect(sock,(struct sockaddr*)&saddr, sizeof (struct sockaddr))) {
close(sock);
continue;
}
if(_socket) *_socket=sock;
else close(sock);
if(_port) _port[-1]=':';
ret = strdup(host);
free(hosts);
if(_saddr) memcpy(_saddr, &saddr, sizeof (struct sockaddr));
return ret;
}
free(hosts);
}
return 0;
}
static int wait_server(void) {
int count=15, sock;
#ifndef __MINGW32__
struct pollfd pollfd[1] = {{EXT_GLOBAL(cfg)->err, POLLIN, 0}};
/* wait for the server that has just started */
while(EXT_GLOBAL(cfg)->cid && -1==(sock=test_local_server()) && --count) {
if(EXT_GLOBAL(cfg)->err && poll(pollfd, 1, 0))
return FAILURE; /* server terminated with error code */
php_error(E_NOTICE, "php_mod_"/**/EXT_NAME()/**/"(%d): waiting for server another %d seconds",57, count);
sleep(1);
}
#else
while(EXT_GLOBAL(cfg)->cid && -1==(sock=test_local_server()) && --count) {
php_error(E_NOTICE, "php_mod_"/**/EXT_NAME()/**/"(%d): waiting for server another %d seconds",57, count);
Sleep(1000);
}
#endif
close(sock);
return (EXT_GLOBAL(cfg)->cid && count)?SUCCESS:FAILURE;
}
/* handle keyboard interrupt */
#ifndef __MINGW32__
static int s_pid=0;
static void s_kill(int sig) {
if(s_pid) kill(s_pid, SIGTERM);
}
#else
static PROCESS_INFORMATION s_pid;
static void s_kill(int sig) {
if(s_pid.hProcess) TerminateProcess(s_pid.hProcess, 1);
}
#endif
void EXT_GLOBAL(start_server)(TSRMLS_D) {
int pid=0, err=0, p[2];
char *test_server = 0;
#ifndef __MINGW32__
if(can_fork() && !(test_server=EXT_GLOBAL(test_server)(0, 0, 0 TSRMLS_CC)) && pipe(p)!=-1) {
if(!(pid=fork())) { /* daemon */
close(p[0]);
if(!fork()) { /* guard */
if(!(pid=fork())) { /* java */
setsid();
close(p[1]);
exec_vm(TSRMLS_C);
exit(105);
}
/* protect guard */
signal(SIGHUP, SIG_IGN);
s_pid=pid; signal(SIGINT, s_kill);
signal(SIGTERM, SIG_IGN);
write(p[1], &pid, sizeof pid);
waitpid(pid, &err, 0);
write(p[1], &err, sizeof err);
exit(0);
}
exit(0);
}
close(p[1]);
wait(&err);
if((read(p[0], &pid, sizeof pid))!=(sizeof pid)) pid=0;
EXT_GLOBAL(cfg)->cid=pid;
EXT_GLOBAL(cfg)->err=p[0];
wait_server();
} else
#else
if(can_fork() && !(test_server=EXT_GLOBAL(test_server)(0, 0, 0 TSRMLS_CC))) {
char *cmd = get_server_string(0 TSRMLS_CC);
DWORD properties = CREATE_NEW_CONSOLE;
STARTUPINFO su_info;
PROCESS_INFORMATION p_info;
ZeroMemory(&su_info, sizeof(STARTUPINFO));
su_info.cb = sizeof(STARTUPINFO);
EXT_GLOBAL(cfg)->cid=0;
if(CreateProcess( NULL, cmd, NULL, NULL, 1, properties, NULL, NULL, &su_info, &s_pid)) {
EXT_GLOBAL(cfg)->cid=s_pid.dwProcessId;
}
wait_server();
} else
#endif /* MINGW32 */
{
EXT_GLOBAL(cfg)->cid=EXT_GLOBAL(cfg)->err=0;
}
if(test_server) free(test_server);
}
static void wait_for_daemon(void) {
#ifndef __MINGW32__
struct pollfd pollfd[1] = {{EXT_GLOBAL(cfg)->err, POLLIN, 0}};
int err, c;
assert(EXT_GLOBAL(cfg)->err);
assert(EXT_GLOBAL(cfg)->cid);
/* first kill is trapped, second kill is received with default
handler. If the server still exists, we send it a -9 */
for(c=3; c>0 && EXT_GLOBAL(cfg)->cid; c--) {
if (!(!EXT_GLOBAL(cfg)->err || (EXT_GLOBAL(cfg)->err && !(err=poll(pollfd, 1, 0))))) break;
if(c>1) {
kill(EXT_GLOBAL(cfg)->cid, SIGTERM);
sleep(1);
if (!(!EXT_GLOBAL(cfg)->err || (EXT_GLOBAL(cfg)->err && !(err=poll(pollfd, 1, 0))))) break;
sleep(4);
} else {
kill(EXT_GLOBAL(cfg)->cid, SIGKILL);
}
}
if(EXT_GLOBAL(cfg)->err) {
if((read(EXT_GLOBAL(cfg)->err, &err, sizeof err))!=sizeof err) err=0;
//printf("VM terminated with code: %ld\n", err);
close(EXT_GLOBAL(cfg)->err);
EXT_GLOBAL(cfg)->err=0;
}
#else
s_kill(0);
Sleep(1000);
#endif
}
void EXT_GLOBAL(shutdown_library)()
{
if(EXT_GLOBAL(cfg)->cid) wait_for_daemon();
}
#ifndef PHP_WRAPPER_H
#error must include php_wrapper.h
#endif