/*-*- mode: C; tab-width:4 -*-*/
/* execve,select */
#ifndef __MINGW32__
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#endif
/* opendir */
#ifndef __MINGW32__
#include <dirent.h>
#endif
/* stat, mkdir */
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.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 separator */
#include "php_wrapper.h"
#include "zend.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)
static short use_wrapper(char*wrapper) {
struct stat buf;
short use_wrapper=(EXT_GLOBAL(option_set_by_user) (U_WRAPPER, EXT_GLOBAL(ini_user)));
if(use_wrapper) return use_wrapper;
#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;
}
/**
* Check if the file ext_dir/javabridge.policy exists
* and return an allocated string containing s+ext_dir/javabridge.policy
* or NULL.
* @param s The prefix, for example "java.security.policy="
*/
static char *check_policy(char *s) {
struct stat buf;
short use_policy = 0;
char *p = 0;
#ifndef __MINGW32__
const static char bridge[]="/javabridge.policy";
char *slash, *ext= php_ini_string((char*)ext_dir, sizeof ext_dir, 0);
size_t slen = strlen(s);
p=malloc(slen+strlen(ext)+sizeof(bridge)); if(!p) return 0;
strcpy(p, s); strcat(p, ext);
slash=p+strlen(p)-1; if(*p&&(*slash=='/'||*slash=='\\')) *slash=0;
strcat(p, bridge);
/* check if it exists */
if(!stat(p+slen, &buf) && (S_IFREG&buf.st_mode)) {
if(getuid()==buf.st_uid)
use_policy=(S_IRUSR&buf.st_mode);
else if(getgid()==buf.st_gid)
use_policy=(S_IRGRP&buf.st_mode);
else
use_policy=(S_IROTH&buf.st_mode);
}
if(!use_policy) { free(p); p=0; }
#endif
return p;
}
/* Windows can handle slashes as well as backslashes so use / everywhere */
static const char path_separator[2] = {ZEND_PATHS_SEPARATOR, 0};
static const char bridge_base[] = "-Dphp.java.bridge.base=";
#if EXTENSION == JAVA
static void EXT_GLOBAL(get_server_args)(char*env[N_SENV], char*args[N_SARGS], short for_display TSRMLS_DC) {
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;
short any_port = 1; /* let back-end select the port# */
#else
const char *s_prefix = local_socket_prefix;
short any_port = for_display;
#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(any_port && !(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(!*program) { /* look into extension_dir then */
static const char java[] = "/java";
if(ext) {
program = malloc(strlen(ext)+sizeof(java)); assert(program); if(!program) exit(6);
strcpy(program, ext); strcat(program, java);
} else {
program = strdup(program);
}
} else {
program = strdup(program);
}
if(!sys_libpath) sys_libpath="";
args[0]=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=='/'||*slash=='\\')) *slash=0;
strcat(p, bridge);
} else {
p=malloc(strlen(s)+strlen(cp)+1);
strcpy(p, s); strcat(p, cp);
}
args[2] = p; /* user classes */
/* policy */
s="-Djava.security.policy="; p=0;
if(!p && EXT_GLOBAL(option_set_by_user) (U_POLICY, EXT_GLOBAL(ini_user))) {
char *cp = EXT_GLOBAL(cfg)->policy;
if(*cp==0||cp[1]==0) { /* policy=On (stored as '\0' or '1\0') */
p = check_policy(s);
} else {
p=malloc(strlen(s)+strlen(cp)+1);
strcpy(p, s); strcat(p, cp);
}
}
if(!p) { /* no policy at all */
p = strdup("-Djava.awt.headless=true");
}
args[3] = p;
/* base */
p = malloc(strlen(ext)+sizeof(bridge_base));
strcpy(p, bridge_base); strcat(p, ext);
args[4] = p;
args[5] = strdup("php.java.bridge.Standalone");
args[6] = sockname;
args[7] = strdup(EXT_GLOBAL(cfg)->logLevel);
args[8] = strdup(cfg_logFile);
args[9] = NULL;
if(*home) { /* set VM home */
s="JAVA_HOME=";
p=malloc(strlen(s)+strlen(home)+1);
strcpy(p, s); strcat(p, home);
env[0] = p;
} else { /* VM in PATH; don't set java home */
env[0] = strdup("");
}
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, path_separator); strcat(p, sys_libpath);
env[1] = p; /* library path */
env[2] = NULL;
}
#elif EXTENSION == MONO
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;
short any_port = 1; /* let back-end select the port# */
#else
const char *s_prefix = local_socket_prefix;
short any_port = for_display;
#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=='/'||*slash=='\\')) *slash=0;
strcat(p, executable);
args[1] = p;
/* if socketname is off, show the user how to start a TCP backend */
if(any_port && !(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 */
/* channel */
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
/*
* Get a string of the server arguments. Useful for display only.
*/
static char*get_server_string(short for_display TSRMLS_DC) {
short must_use_wrapper = use_wrapper(EXT_GLOBAL(cfg)->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(EXT_GLOBAL(cfg)->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, EXT_GLOBAL(cfg)->wrapper);
strcat(s, " ");
}
for(i=0; i< (sizeof args)/(sizeof*args); i++) {
if(!args[i]) break;
strcat(s, args[i]);
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) {
static char*env[N_SENV];
static char*_args[N_SARGS+1];
char **args=_args+1, *cmd;
EXT_GLOBAL(get_server_args)(env, args, 0 TSRMLS_CC);
if(N_SENV>2) {
if(*env[0]) putenv(env[0]);
if(*env[1]) putenv(env[1]);
}
if(use_wrapper(EXT_GLOBAL(cfg)->wrapper)) {
*--args = strdup(EXT_GLOBAL(cfg)->wrapper);
execv(args[0], args);
}
if(*args[0]=='/') execv(args[0], args); else execvp(args[0], args);
/* exec failed */
cmd = get_server_string(0 TSRMLS_CC);
php_error(E_WARNING, "php_mod_"/**/EXT_NAME()/**/"(%d) system error: Could not execute backend: %s: %s", 105, cmd, strerror(errno));
free(cmd);
}
static const int is_true = 1;
static int test_local_server(void) {
int sock, n;
#ifndef CFG_JAVA_SOCKET_INET
sock = socket (PF_LOCAL, SOCK_STREAM, 0);
#else
sock = socket (PF_INET, SOCK_STREAM, 0);
if(sock!=-1) setsockopt(sock, 0x6, TCP_NODELAY, (void*)&is_true, sizeof is_true);
#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(cfg)->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) && *(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;
setsockopt(sock, 0x6, TCP_NODELAY, (void*)&is_true, sizeof is_true);
}
else close(sock);
if(_port) _port[-1]=':';
ret = strdup(host);
free(hosts);
if(_saddr) memcpy(_saddr, &saddr, sizeof (struct sockaddr));
if(EXT_GLOBAL(cfg)->socketname_set)
EXT_GLOBAL(cfg)->socketname_set = 0;
return ret;
}
free(hosts);
}
socketname_set = EXT_GLOBAL(option_set_by_user) (U_SOCKNAME, JG(ini_user)) ;
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;
if(!EXT_GLOBAL(cfg)->socketname_set)
EXT_GLOBAL(cfg)->socketname_set = 1;
return strdup(EXT_GLOBAL(get_sockname)(TSRMLS_C));
}
return 0;
}
static const long timeout = 50000l; /* ys */
static void sleep_ms() {
struct timeval timeval = {0l, timeout};
select(0, 0, 0, 0, &timeval);
}
static int wait_server(void) {
#ifndef __MINGW32__
static const int wait_count = 30;
int count=wait_count, sock;
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 */
sleep_ms();
}
count=30;
while(EXT_GLOBAL(cfg)->cid && -1==sock && -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
static const int wait_count = 5;
int count=wait_count, sock;
while(EXT_GLOBAL(cfg)->cid && -1==(sock=test_local_server()) && --count) {
Sleep(500);
}
count=15;
while(EXT_GLOBAL(cfg)->cid && -1==sock && -1==(sock=test_local_server()) && --count) {
php_error(E_NOTICE, "php_mod_"/**/EXT_NAME()/**/"(%d): waiting for server another %d interval",57, count);
Sleep(500);
}
#endif
if(EXT_GLOBAL(cfg)->cid && count) {
close(sock);
return SUCCESS;
} else {
return 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
#ifndef _WIN32_WINNT
# define _WIN32_WINNT 0x500
#endif
#include <windows.h>
#include <tchar.h>
#include <stdarg.h>
#include <tlhelp32.h>
static struct s_pid {
short use_wrapper;
PROCESS_INFORMATION p;
} s_pid;
/**
* Unix kill emulation for windows.
* From http://www.rsdn.ru/?qna/?baseserv/killproc.xml
*/
static BOOL WINAPI KillProcess(IN DWORD dwProcessId)
{
HANDLE hProcess;
DWORD dwError;
// first try to obtain handle to the process without the use of any
// additional privileges
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
if (hProcess == NULL)
{
if (GetLastError() != ERROR_ACCESS_DENIED)
return FALSE;
OSVERSIONINFO osvi;
// determine operating system version
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
// we cannot do anything else if this is not Windows NT
if (osvi.dwPlatformId != VER_PLATFORM_WIN32_NT)
return FALSE;
// enable SE_DEBUG_NAME privilege and try again
TOKEN_PRIVILEGES Priv, PrivOld;
DWORD cbPriv = sizeof(PrivOld);
HANDLE hToken;
// obtain the token of the current thread
if (!OpenThreadToken(GetCurrentThread(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
FALSE, &hToken))
{
if (GetLastError() != ERROR_NO_TOKEN)
return FALSE;
// revert to the process token
if (!OpenProcessToken(GetCurrentProcess(),
TOKEN_QUERY|TOKEN_ADJUST_PRIVILEGES,
&hToken))
return FALSE;
}
assert(ANYSIZE_ARRAY > 0);
Priv.PrivilegeCount = 1;
Priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
LookupPrivilegeValue(NULL, SE_DEBUG_NAME, &Priv.Privileges[0].Luid);
// try to enable the privilege
if (!AdjustTokenPrivileges(hToken, FALSE, &Priv, sizeof(Priv),
&PrivOld, &cbPriv))
{
dwError = GetLastError();
CloseHandle(hToken);
return FALSE;
}
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
// the SE_DEBUG_NAME privilege is not present in the caller's
// token
CloseHandle(hToken);
return FALSE;
}
// try to open process handle again
hProcess = OpenProcess(PROCESS_TERMINATE, FALSE, dwProcessId);
dwError = GetLastError();
// restore the original state of the privilege
AdjustTokenPrivileges(hToken, FALSE, &PrivOld, sizeof(PrivOld),
NULL, NULL);
CloseHandle(hToken);
if (hProcess == NULL)
return FALSE;
}
// terminate the process
if (!TerminateProcess(hProcess, (UINT)-1))
{
dwError = GetLastError();
CloseHandle(hProcess);
return FALSE;
}
CloseHandle(hProcess);
// completed successfully
return TRUE;
}
typedef LONG NTSTATUS;
typedef LONG KPRIORITY;
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define SystemProcessesAndThreadsInformation 5
typedef struct _CLIENT_ID {
DWORD UniqueProcess;
DWORD UniqueThread;
} CLIENT_ID;
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING;
typedef struct _VM_COUNTERS {
SIZE_T PeakVirtualSize;
SIZE_T VirtualSize;
ULONG PageFaultCount;
SIZE_T PeakWorkingSetSize;
SIZE_T WorkingSetSize;
SIZE_T QuotaPeakPagedPoolUsage;
SIZE_T QuotaPagedPoolUsage;
SIZE_T QuotaPeakNonPagedPoolUsage;
SIZE_T QuotaNonPagedPoolUsage;
SIZE_T PagefileUsage;
SIZE_T PeakPagefileUsage;
} VM_COUNTERS;
typedef struct _SYSTEM_THREADS {
LARGE_INTEGER KernelTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER CreateTime;
ULONG WaitTime;
PVOID StartAddress;
CLIENT_ID ClientId;
KPRIORITY Priority;
KPRIORITY BasePriority;
ULONG ContextSwitchCount;
LONG State;
LONG WaitReason;
} SYSTEM_THREADS, * PSYSTEM_THREADS;
// Note that the size of the SYSTEM_PROCESSES structure is different on
// NT 4 and Win2K, but we don't care about it, since we don't access neither
// IoCounters member nor Threads array
typedef struct _SYSTEM_PROCESSES {
ULONG NextEntryDelta;
ULONG ThreadCount;
ULONG Reserved1[6];
LARGE_INTEGER CreateTime;
LARGE_INTEGER UserTime;
LARGE_INTEGER KernelTime;
UNICODE_STRING ProcessName;
KPRIORITY BasePriority;
ULONG ProcessId;
ULONG InheritedFromProcessId;
ULONG HandleCount;
ULONG Reserved2[2];
VM_COUNTERS VmCounters;
#if _WIN32_WINNT >= 0x500
IO_COUNTERS IoCounters;
#endif
SYSTEM_THREADS Threads[1];
} SYSTEM_PROCESSES, * PSYSTEM_PROCESSES;
static BOOL WINAPI KillProcessTreeNtHelper(IN PSYSTEM_PROCESSES pInfo, IN DWORD dwProcessId)
{
assert(pInfo != NULL);
PSYSTEM_PROCESSES p = pInfo;
// kill all children first
for (;;)
{
if (p->InheritedFromProcessId == dwProcessId)
KillProcessTreeNtHelper(pInfo, p->ProcessId);
if (p->NextEntryDelta == 0)
break;
// find the address of the next process structure
p = (PSYSTEM_PROCESSES)(((LPBYTE)p) + p->NextEntryDelta);
}
// kill the process itself
if (!KillProcess(dwProcessId))
return GetLastError();
return ERROR_SUCCESS;
}
static BOOL WINAPI KillProcessTreeWinHelper(IN DWORD dwProcessId)
{
HINSTANCE hKernel;
HANDLE (WINAPI * _CreateToolhelp32Snapshot)(DWORD, DWORD);
BOOL (WINAPI * _Process32First)(HANDLE, PROCESSENTRY32 *);
BOOL (WINAPI * _Process32Next)(HANDLE, PROCESSENTRY32 *);
// get handle to KERNEL32.DLL
hKernel = GetModuleHandle(_T("kernel32.dll"));
assert(hKernel != NULL);
// locate necessary functions in KERNEL32.DLL
*(FARPROC *)&_CreateToolhelp32Snapshot =
GetProcAddress(hKernel, "CreateToolhelp32Snapshot");
*(FARPROC *)&_Process32First =
GetProcAddress(hKernel, "Process32First");
*(FARPROC *)&_Process32Next =
GetProcAddress(hKernel, "Process32Next");
if (_CreateToolhelp32Snapshot == NULL ||
_Process32First == NULL ||
_Process32Next == NULL)
return ERROR_PROC_NOT_FOUND;
HANDLE hSnapshot;
PROCESSENTRY32 Entry;
// create a snapshot
hSnapshot = _CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapshot == INVALID_HANDLE_VALUE)
return GetLastError();
Entry.dwSize = sizeof(Entry);
if (!_Process32First(hSnapshot, &Entry))
{
DWORD dwError = GetLastError();
CloseHandle(hSnapshot);
return dwError;
}
// kill all children first
do
{
if (Entry.th32ParentProcessID == dwProcessId)
KillProcessTreeWinHelper(Entry.th32ProcessID);
Entry.dwSize = sizeof(Entry);
}
while (_Process32Next(hSnapshot, &Entry));
CloseHandle(hSnapshot);
// kill the process itself
if (!KillProcess(dwProcessId))
return GetLastError();
return ERROR_SUCCESS;
}
static BOOL WINAPI KillProcessEx(IN DWORD dwProcessId, IN BOOL bTree)
{
if (!bTree)
return KillProcess(dwProcessId);
OSVERSIONINFO osvi;
DWORD dwError;
// determine operating system version
osvi.dwOSVersionInfoSize = sizeof(osvi);
GetVersionEx(&osvi);
if (osvi.dwPlatformId == VER_PLATFORM_WIN32_NT &&
osvi.dwMajorVersion < 5)
{
HINSTANCE hNtDll;
NTSTATUS (WINAPI * _ZwQuerySystemInformation)(UINT, PVOID, ULONG, PULONG);
// get handle to NTDLL.DLL
hNtDll = GetModuleHandle(_T("ntdll.dll"));
assert(hNtDll != NULL);
// find the address of ZwQuerySystemInformation
*(FARPROC *)&_ZwQuerySystemInformation =
GetProcAddress(hNtDll, "ZwQuerySystemInformation");
if (_ZwQuerySystemInformation == NULL)
return FALSE;
// obtain a handle to the default process heap
HANDLE hHeap = GetProcessHeap();
NTSTATUS Status;
ULONG cbBuffer = 0x8000;
PVOID pBuffer = NULL;
// it is difficult to say a priory which size of the buffer
// will be enough to retrieve all information, so we start
// with 32K buffer and increase its size until we get the
// information successfully
do
{
pBuffer = HeapAlloc(hHeap, 0, cbBuffer);
if (pBuffer == NULL)
return FALSE;
Status = _ZwQuerySystemInformation(
SystemProcessesAndThreadsInformation,
pBuffer, cbBuffer, NULL);
if (Status == STATUS_INFO_LENGTH_MISMATCH)
{
HeapFree(hHeap, 0, pBuffer);
cbBuffer *= 2;
}
else if (!NT_SUCCESS(Status))
{
HeapFree(hHeap, 0, pBuffer);
return FALSE;
}
}
while (Status == STATUS_INFO_LENGTH_MISMATCH);
// call the helper function
dwError = KillProcessTreeNtHelper((PSYSTEM_PROCESSES)pBuffer,
dwProcessId);
HeapFree(hHeap, 0, pBuffer);
}
else
{
// call the helper function
dwError = KillProcessTreeWinHelper(dwProcessId);
}
return dwError == ERROR_SUCCESS;
}
static void s_kill(int sig) {
if(!s_pid.use_wrapper) {
/* we can kill our child directly */
if(s_pid.p.hProcess) TerminateProcess(s_pid.p.hProcess, 1);
}
else {
/* emulate unix kill behaviour */
if(s_pid.p.dwProcessId) KillProcessEx(s_pid.p.dwProcessId, 1);
}
}
#endif
static void make_local_socket_info(TSRMLS_D) {
memset(&EXT_GLOBAL(cfg)->saddr, 0, sizeof EXT_GLOBAL(cfg)->saddr);
#ifndef CFG_JAVA_SOCKET_INET
EXT_GLOBAL(cfg)->saddr.sun_family = AF_LOCAL;
memset(EXT_GLOBAL(cfg)->saddr.sun_path, 0, sizeof EXT_GLOBAL(cfg)->saddr.sun_path);
strcpy(EXT_GLOBAL(cfg)->saddr.sun_path, EXT_GLOBAL(get_sockname)(TSRMLS_C));
# ifdef HAVE_ABSTRACT_NAMESPACE
*EXT_GLOBAL(cfg)->saddr.sun_path=0;
# endif
#else
EXT_GLOBAL(cfg)->saddr.sin_family = AF_INET;
EXT_GLOBAL(cfg)->saddr.sin_port=htons(atoi(EXT_GLOBAL(get_sockname)(TSRMLS_C)));
EXT_GLOBAL(cfg)->saddr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
#endif
}
void EXT_GLOBAL(start_server)(TSRMLS_D) {
int pid=0, err=-1, p[2], st[2], stx;
char buf[127], count, *test_server = 0, *name;
#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]);
stx = pipe(st);
if(!fork()) { /* guard */
setsid();
if(!(pid=fork())) { /* java */
if(close(p[1])!=-1&& stx!=-1&& close(st[0])!=-1&& dup2(st[1], 1)!=-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);
if(stx!=-1 && close(st[1])!=-1) err = read(st[0], buf, sizeof buf);
count = (err==-1) ? 0 : (0xff & err);
write(p[1], &count, 1); if(count) write(p[1], buf, count);
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;
if((read(p[0], &count, 1))!=1) count=0;
EXT_GLOBAL(cfg)->cid=pid;
EXT_GLOBAL(cfg)->err=p[0];
if(count&&((read(p[0], buf, sizeof buf))==count)) {
/* received channel # */
size_t n = count;
name = malloc(n+1); if(!name) exit(9);
memcpy(name, buf, n); name[n]=0;
//php_printf("got server channel: %ld, %s", n, name);
free(EXT_GLOBAL(cfg)->default_sockname);
EXT_GLOBAL(cfg)->default_sockname=name;
make_local_socket_info(TSRMLS_C);
} else {
make_local_socket_info(TSRMLS_C);
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 | CREATE_NEW_PROCESS_GROUP;
STARTUPINFO su_info;
HANDLE read_pipe, write_pipe, read_pipe_dup;
SECURITY_ATTRIBUTES pipe_sattr = {sizeof(SECURITY_ATTRIBUTES),NULL,TRUE};
HANDLE pid = GetCurrentProcess();
DWORD bread;
if(!CreatePipe(&read_pipe, &write_pipe, &pipe_sattr, 0)) {
goto cannot_fork;
}
if(!DuplicateHandle(pid,read_pipe,pid,&read_pipe_dup,0,FALSE,DUPLICATE_SAME_ACCESS)) {
CloseHandle(read_pipe);
goto cannot_fork;
}
CloseHandle(read_pipe);
s_pid.use_wrapper = use_wrapper(EXT_GLOBAL(cfg)->wrapper);
ZeroMemory(&su_info, sizeof(STARTUPINFO));
su_info.cb = sizeof(STARTUPINFO);
su_info.dwFlags = STARTF_USESTDHANDLES;
su_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
su_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
su_info.hStdOutput = write_pipe;
EXT_GLOBAL(cfg)->cid=0;
if(CreateProcess(NULL, cmd,
NULL, NULL, 1, properties, NULL, NULL,
&su_info, &s_pid.p)) {
EXT_GLOBAL(cfg)->cid=s_pid.p.dwProcessId;
if(ReadFile(read_pipe_dup, buf, sizeof(buf), &bread, NULL) && bread) {
name = malloc(bread+1); if(!name) exit(9);
memcpy(name, buf, bread); name[bread]=0;
//php_printf("got server channel: %ld, %s", bread, name);
free(EXT_GLOBAL(cfg)->default_sockname);
EXT_GLOBAL(cfg)->default_sockname=name;
make_local_socket_info(TSRMLS_C);
} else {
make_local_socket_info(TSRMLS_C);
wait_server();
}
CloseHandle(s_pid.p.hThread);
CloseHandle(read_pipe_dup);
} else {
cannot_fork:
php_error(E_WARNING, "php_mod_"/**/EXT_NAME()/**/"(%d) system error: Could not start back-end: %s; Code: %ld.", 105, cmd, (long)GetLastError());
make_local_socket_info(TSRMLS_C);
}
free(cmd);
} else
#endif /* MINGW32 */
{
make_local_socket_info(TSRMLS_C);
EXT_GLOBAL(cfg)->cid=EXT_GLOBAL(cfg)->err=0;
}
if(test_server) free(test_server);
}
static void wait_for_daemon(void) {
#ifndef __MINGW32__
static const int sig[] = {SIGTERM, SIGKILL};
fd_set rfds;
int err, i;
assert(EXT_GLOBAL(cfg)->err); if(!(EXT_GLOBAL(cfg)->err)) return;
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 */
kill(EXT_GLOBAL(cfg)->cid, SIGTERM);
FD_ZERO(&rfds);
FD_SET(EXT_GLOBAL(cfg)->err, &rfds);
for(i=0; i<2; i++) {
struct timeval timeval = {2l, 0};
if(select(1+EXT_GLOBAL(cfg)->err, &rfds, 0, 0, &timeval) > 0) break;
kill(EXT_GLOBAL(cfg)->cid, sig[i]);
}
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); /* always -9 on windows */
#endif
}
void EXT_GLOBAL(shutdown_library)()
{
if(EXT_GLOBAL(cfg)->cid) wait_for_daemon();
}
void EXT_GLOBAL(sys_error)(const char *str, int code) {
#ifndef __MINGW32__
php_error(E_ERROR, "php_mod_"/**/EXT_NAME()/**/"(%d) system error: %s. %s.", code, strerror(errno), str);
#else
php_error(E_ERROR, "php_mod_"/**/EXT_NAME()/**/"(%d) system error code: %ld. %s.", code, (long)GetLastError(), str);
#endif
}
static void fcgi_do_rmtmpdir() {
#ifndef __MINGW32__
extern EXT_GLOBAL(is_parent);
static const char lck[] = ".lck";
DIR * dir;
struct dirent * file;
size_t len;
char *name, *lock_name;
int lock_file, err;
/* If this is not an fcgi child, do nothing. */
if(!EXT_GLOBAL(cfg) || !EXT_GLOBAL(cfg)->tmpdir) return;
len = strlen(EXT_GLOBAL(cfg)->tmpdir);
lock_name = malloc(len+sizeof lck);
strcpy(lock_name, (EXT_GLOBAL(cfg)->tmpdir));
strcat(lock_name, lck);
lock_file = open(lock_name, O_CREAT | O_EXCL, 0700);
unlink(lock_name);
free(lock_name);
if(lock_file==-1) return;
dir = opendir(EXT_GLOBAL(cfg)->tmpdir);
if(!dir) return;
while(file = readdir(dir)) {
if(file->d_name[0]!='p') continue;
name = malloc(len + strlen(file->d_name)+2);
if(!name) exit(6);
strcpy(name, (EXT_GLOBAL(cfg)->tmpdir));
strcat(name, "/");
strcat(name, file->d_name);
unlink(name);
free(name);
}
closedir(dir);
rmdir(EXT_GLOBAL(cfg)->tmpdir);
#endif
}
static void fcgi_rmtmpdir(int sig) {
fcgi_do_rmtmpdir();
exit(0);
}
/* Delete the temp directory which contains the comm. pipes */
static void rmtmpdir () {
#ifndef __MINGW32__
extern EXT_GLOBAL(is_parent);
DIR * dir;
struct dirent * file;
size_t len;
char *name;
int err;
int lock;
/* If this is a child, do nothing. */
if(!EXT_GLOBAL(cfg) || !EXT_GLOBAL(cfg)->tmpdir || EXT_GLOBAL(cfg)->pid!=getpid()) return;
len = strlen(EXT_GLOBAL(cfg)->tmpdir);
dir = opendir(EXT_GLOBAL(cfg)->tmpdir);
if(!dir) { EXT_GLOBAL(sys_error)("Could not open tmpdir", 65); return; }
while(file = readdir(dir)) {
if(file->d_name[0]!='p') continue;
name = malloc(len + strlen(file->d_name)+2);
if(!name) exit(6);
strcpy(name, (EXT_GLOBAL(cfg)->tmpdir));
strcat(name, "/");
strcat(name, file->d_name);
php_error(E_NOTICE, "php_mod_"/**/EXT_NAME()/**/"(%d): Removing %s which is not (yet?) connected. ", 66, name);
unlink(name);
free(name);
}
err = closedir(dir);
if(err==-1) { EXT_GLOBAL(sys_error)("Could not close tmpdir", 67); return; }
err = rmdir(EXT_GLOBAL(cfg)->tmpdir);
if(err==-1) { EXT_GLOBAL(sys_error)("Could not unlink tmpdir", 68); return; }
free(EXT_GLOBAL(cfg)->tmpdir);
EXT_GLOBAL(cfg)->tmpdir=0;
#endif
}
void EXT_GLOBAL(rmtmpdir) () {
/* see FastCGI comment below */
if(!EXT_GLOBAL(cfg)->is_fcgi_servlet) rmtmpdir();
}
#ifndef __MINGW32__
static char *makedtemp(char tmpl[]) {
#ifdef HAVE_MKDTEMP
char *str = 0, *s = mkdtemp (tmpl);
if(s) if(!(str=strdup(s))) { rmdir(s); exit(6); }
return str;
#else
char *s, *p = strrchr(tmpl, '/');
char c = *p;
*p=0;
p[6]=0;
s = tempnam(tmpl,p+1);
*p=c;
if(!s) return 0;
if(-1==mkdir(s, 0700)) { free(s); return 0; }
return s;
#endif
}
#endif
void EXT_GLOBAL(mktmpdir) () {
#ifndef __MINGW32__
char sockname[] = SOCKNAME;
char sockname_shm[] = SOCKNAME_SHM;
char *tmpdir;
/* The FastCGI SAPI is completely odd, it calls the parent(!)
mshutdown for each killed child. Ignore this nonsense and call
rmtmpdir ourselfs when the parent exits. */
if(EXT_GLOBAL(cfg)->is_fcgi_servlet) signal(SIGTERM, fcgi_rmtmpdir);
tmpdir = makedtemp(sockname_shm);
if(!tmpdir) tmpdir = makedtemp(sockname);
if(!tmpdir) {EXT_GLOBAL(cfg)->tmpdir=0; return;}
EXT_GLOBAL(cfg)->tmpdir=tmpdir;
chmod(tmpdir, 01777);
#else /* There's no standard tmpdir on windows */
EXT_GLOBAL(cfg)->tmpdir=0;
#endif
}
#ifndef PHP_WRAPPER_H
#error must include php_wrapper.h
#endif