blob: f88b839f994d8ae75e178467429c7c171880cc34 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Serguei S.Zapreyev
*
* This java_lang_Runtime.cpp source ("Software") is furnished under license and may
* only be used or copied in accordance with the terms of that license.
*
**/
// java_lang_Runtime.cpp : java.lang.Runtime class' native support.
//
#define LOG_DOMAIN "kernel.process"
#include "cxxlog.h"
#include "open/types.h"
#include "jni.h"
#include "exceptions.h"
#include "java_lang_Runtime_SubProcess.h"
#include "java_lang_Runtime_SubProcess_SubInputStream.h"
#include "java_lang_Runtime_SubProcess_SubOutputStream.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <errno.h>
static void Error (const char *lpcszMess, JNIEnv *env, jlongArray la)
{
jboolean jb = true;
jlong *lp = (jlong*)env->GetLongArrayElements(la, &jb);
lp[0] = 0;
env->ReleaseLongArrayElements(la, lp, 0);
if (lpcszMess != NULL) {
INFO(lpcszMess);
}
}
static void ThrowError(JNIEnv *env, const char *message = 0) {
jclass jc = env->FindClass((const char *)"java/io/IOException");
env->ThrowNew(jc, message ? message : strerror(errno));
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_createProcess0 (JNIEnv *env, jobject obj, jobjectArray cmdarray, jobjectArray envp, jstring dir, jlongArray la){
jobject jo;
const char *strChain;
int i;
char *cmdDir = NULL;
char *strCmd = NULL;
// Get the working directory of the subprocess:
if ( dir != NULL ) {
char* str = (char *)env->GetStringUTFChars(dir, 0);
cmdDir = (char *)malloc(1+strlen(str)); // + NUL symbol
*cmdDir = '\0';
strcat(cmdDir, str);
env->ReleaseStringUTFChars(dir, str);
}
// Get the the command to call and its arguments (it must be non-null):
int lenargv = 0;
lenargv = env->GetArrayLength(cmdarray);
char *argv[lenargv+1];
for ( i = 0; i < lenargv; i++ ) {
jo = env->GetObjectArrayElement((jobjectArray)((jobject)cmdarray), (jsize) i);
strChain = env->GetStringUTFChars((jstring) jo, 0);
strCmd = (char *)malloc(1+strlen(strChain)); // + NUL symbol
*strCmd = '\0';
strcat(strCmd, strChain);
argv[i] = strCmd;
env->ReleaseStringUTFChars((jstring) jo, strChain);
}
argv[lenargv] = (char *) 0; // NULL pointer
// Get the array, each element of which has environment variable settings:
int lenEnvp = 0;
if (envp != NULL) {
lenEnvp += env->GetArrayLength(envp);
}
char *strEnvpBeginAA[lenEnvp + 1];
if (envp != NULL) {
for ( i = 0; i < lenEnvp; i++ ) {
jo = env->GetObjectArrayElement((jobjectArray)((jobject)envp), (jsize) i);
strChain = env->GetStringUTFChars((jstring) jo, 0);
strCmd = (char *)malloc(1+strlen(strChain)); // + NUL symbol
*strCmd = '\0';
strcat(strCmd, strChain);
strEnvpBeginAA[i] = strCmd;
env->ReleaseStringUTFChars((jstring) jo, strChain);
}
}
strEnvpBeginAA[lenEnvp] = (char *) 0; // NULL pointer
//define stdI/O/E for future process:
int fildesO[2] = {-1,-1};
int fildesE[2] = {-1,-1};
int fildesI[2] = {-1,-1};
// Controlling pipe.
// Child process if successfully executed will close the handle (by system)
// If execv failed the write(..) call will write 4 bytes in this stream.
// Thus we can distinguish executed proccesses and failed ones.
int fildesInfo[2] = {-1,-1};
if (pipe(fildesO) == -1
|| pipe(fildesI) == -1
|| pipe(fildesE) == -1
|| pipe(fildesInfo) == -1) {
if (fildesO[0] != -1) close(fildesO[0]);
if (fildesO[1] != -1) close(fildesO[1]);
if (fildesE[0] != -1) close(fildesE[0]);
if (fildesE[1] != -1) close(fildesE[1]);
if (fildesI[0] != -1) close(fildesI[0]);
if (fildesI[1] != -1) close(fildesI[1]);
if (fildesInfo[0] != -1) close(fildesInfo[0]);
if (fildesInfo[1] != -1) close(fildesInfo[1]);
Error("Stdin/stdout pipes creation failed:", env, la);
Error(strerror(errno), env, la);
return;
}
int spid = fork();
if (spid == -1) {
close(fildesI[0]);
close(fildesI[1]);
close(fildesO[0]);
close(fildesO[1]);
close(fildesE[0]);
close(fildesE[1]);
close(fildesInfo[0]);
close(fildesInfo[1]);
Error("Fork failed\n", env, la);
}
if (spid==0) {
///// Child process code ///////////////
dup2(fildesI[0], 0);
dup2(fildesO[1], 1);
dup2(fildesE[1], 2);
close(fildesI[0]);
close(fildesI[1]);
close(fildesO[0]);
close(fildesO[1]);
close(fildesE[0]);
close(fildesE[1]);
close(fildesInfo[0]);
long close_on_exec = FD_CLOEXEC; // set close on exec bit
fcntl(fildesInfo[1], F_SETFD, close_on_exec);
// Get the working directory of the subprocess:
if ( cmdDir != NULL ) {
int res = chdir(cmdDir);
if (res == -1) {
write(fildesInfo[1], &errno, sizeof(int));
INFO("chdir failed: " << strerror(errno));
kill(getpid(), 9);
}
free(cmdDir);
}
if (lenEnvp == 0) {
execvp(argv[0], argv);
} else {
execve(argv[0], argv, strEnvpBeginAA);
if(strchr(argv[0], '/') == NULL) {
char* curDir = NULL;
char* cmdPath = NULL;
char* dirs = NULL;
if ((dirs = getenv("PATH")) != NULL) {
int len = 0;
curDir = strtok(dirs, ":");
while(curDir != NULL) {
if((len = strlen(curDir)) != 0) {
cmdPath = (char *)malloc(len+1+strlen(argv[0])+1);
*cmdPath = '\0';
strcat(strcat(strcat(cmdPath, curDir), "/"), argv[0]);
if (fopen(cmdPath, "r") != NULL) {
execve(cmdPath, argv, strEnvpBeginAA);
//XXX: should we inform only of a last error among all possible execve atempts?
}
free(cmdPath);
}
curDir = strtok(NULL, ":");
}
}
}
}
write(fildesInfo[1], &errno, sizeof(int));
INFO("Process initiation failed: " << strerror(errno));
// kill self
kill(getpid(), 9);
}
///// End of child process code ////////////
close(fildesO[1]);
close(fildesI[0]);
close(fildesE[1]);
close(fildesInfo[1]);
free(cmdDir);
free(strCmd);
// get execution status from child
int errno_child;
int res = read(fildesInfo[0], &errno_child, sizeof(int));
if (res == 4) {
Error("Process initiation failed", env, la);
Error(strerror(errno_child), env, la);
close(fildesO[0]);
close(fildesI[1]);
close(fildesE[0]);
close(fildesInfo[0]);
return;
}
jboolean jb = true;
jlong *lp = (jlong*)env->GetLongArrayElements(la, &jb);
lp[0] = (jlong) spid; // new process number
lp[1] = (jlong) fildesI[1];
lp[2] = (jlong) fildesO[0];
lp[3] = (jlong) fildesE[0];
fcntl(fildesO[0], F_SETFL, 0); //XXX:to set !O_NONBLOCK because it is sometimes set by default and it should be investigated
fcntl(fildesE[0], F_SETFL, 0); //XXX:to set !O_NONBLOCK because it is sometimes set by default and it should be investigated
env->ReleaseLongArrayElements(la, lp, 0);
close(fildesInfo[0]);
}
static jboolean waitFor(JNIEnv *env, jobject obj, jint handle) {
if (handle == -1) return true;
int status = 0;
pid_t pid = (pid_t) handle;
pid_t res = waitpid(pid, &status, WNOHANG);
if (res != pid) {
return false;
}
int exitCode = WEXITSTATUS(status);
if (WIFSIGNALED(status)) exitCode = 129;
// got the process event
// store exit code
jclass clss = env->GetObjectClass(obj);
jfieldID exitCodeField = env->GetFieldID(clss, "processExitCode", "I");
jfieldID handleField = env->GetFieldID(clss, "processHandle", "I");
env->SetIntField(obj, exitCodeField, exitCode);
env->SetIntField(obj, handleField, -1);
return true;
}
jboolean JNICALL Java_java_lang_Runtime_00024SubProcess_getState0 (JNIEnv *env, jobject obj, jint handle) {
return waitFor(env, obj, handle);
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_destroy0 (JNIEnv *env, jobject obj, jint handle) {
if (waitFor(env, obj, handle)) return;
kill(handle, SIGKILL);
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_close0 (JNIEnv *env, jobject obj, jint handle) {
}
//###############################################################################################
jint JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubInputStream_readInputByte0 (JNIEnv *env, jobject obj, jlong inputHandle) {
char ca[1];
int res = read((int)inputHandle, ca, (unsigned) 1);
if (res == 1) {
return ((unsigned char) ca[0])&0xFF;
}
if (res == 0) {
return -1;
}
jclass jc = env->FindClass((const char *)"java/io/IOException");
env->ThrowNew(jc, strerror(errno));
return -1;
}
jint JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubInputStream_available0 (JNIEnv *env, jobject obj, jlong inputHandle) {
int res;
if (ioctl((int) inputHandle, FIONREAD, &res) == -1) {
if (errno == EINVAL) {
struct pollfd r[1];
r[0].fd = (int) inputHandle;
r[0].events = POLLRDNORM;
r[0].revents = 0;
do {
res = poll(r, 1, 0);
} while (res == -1 && errno == EINTR);
if (res == 1) {
if(r[0].revents & r[0].events) {
return 1; // So, in that case we can define one byte is available at least
} else if(r[0].revents & (POLLERR | POLLNVAL)) {
char mess[100];
mess[0] = '\0';
sprintf(mess, "%s", (r[0].revents & POLLERR ? "Some error condition has raised." : "Invalid request: handle closed."));
jclass jc = env->FindClass((const char *)"java/io/IOException");
env->ThrowNew(jc, (const char *) mess);
}
return 0;
} else if (res < 0) {
char mess[100];
mess[0] = '\0';
sprintf(mess, "It's impossible to identify if there are available bytes in the input stream! ERRNO=%d. %s", errno, strerror(errno));
jclass jc = env->FindClass((const char *)"java/io/IOException");
env->ThrowNew(jc, (const char *) mess);
}
return 0;
} else {
char mess[100];
mess[0] = '\0';
sprintf(mess, "%s", "Some error condition has raised.");
jclass jc = env->FindClass((const char *)"java/io/IOException");
env->ThrowNew(jc, (const char *) mess);
}
}
return res;
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubInputStream_close0 (JNIEnv *env, jobject obj, jlong inputHandle) {
int res = close((int) inputHandle);
if (res == -1 && errno != EBADF) {
ThrowError(env);
}
}
//###############################################################################################
void JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubOutputStream_writeOutputByte0 (JNIEnv *env, jobject obj, jlong outputHandle, jint byte) {
if (outputHandle == -1) {
ThrowError(env, "file already closed");
return;
}
char b = (char) byte;
int res = write((int)outputHandle, &b, 1);
if (res != 1) {
ThrowError(env);
}
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubOutputStream_writeOutputBytes0(JNIEnv *env,
jobject obj, jlong outputHandle, jbyteArray byte, jint off, jint len) {
if (outputHandle == -1) {
ThrowError(env, "file already closed");
return;
}
jboolean jb = true;
char *cp = (char*)env->GetByteArrayElements((jbyteArray)byte, &jb);
while (true) {
int res = write((int)outputHandle, cp + off, len);
if (res == len) break;
if (res <= 0) {
ThrowError(env);
break;
}
len -= res;
off += res;
}
env->ReleaseByteArrayElements((jbyteArray)byte, (signed char*)cp, 0);
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubOutputStream_flush0 (JNIEnv *env, jobject obj, jlong outputHandle) {
return;
}
void JNICALL Java_java_lang_Runtime_00024SubProcess_00024SubOutputStream_close0 (JNIEnv *env, jobject obj, jlong outputHandle) {
int res = close((int) outputHandle);
if (res == -1 && errno != EBADF) {
ThrowError(env);
}
}