| /* | |
| * Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. Oracle designates this | |
| * particular file as subject to the "Classpath" exception as provided | |
| * by Oracle in the LICENSE file that accompanied this code. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| #include "jni.h" | |
| #include "jni_util.h" | |
| #include "java_lang_ProcessHandleImpl.h" | |
| #include "java_lang_ProcessHandleImpl_Info.h" | |
| #include "ProcessHandleImpl_unix.h" | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <signal.h> | |
| #include <stdlib.h> | |
| #include <unistd.h> | |
| #include <string.h> | |
| #include <sys/sysctl.h> | |
| /** | |
| * Implementation of native ProcessHandleImpl functions for MAC OS X. | |
| * See ProcessHandleImpl_unix.c for more details. | |
| */ | |
| void os_initNative(JNIEnv *env, jclass clazz) {} | |
| /* | |
| * Return pids of active processes, and optionally parent pids and | |
| * start times for each process. | |
| * For a specific non-zero pid jpid, only the direct children are returned. | |
| * If the pid jpid is zero, all active processes are returned. | |
| * Uses sysctl to accumulates any process following the rules above. | |
| * The resulting pids are stored into an array of longs named jarray. | |
| * The number of pids is returned if they all fit. | |
| * If the parentArray is non-null, store also the parent pid. | |
| * In this case the parentArray must have the same length as the result pid array. | |
| * Of course in the case of a given non-zero pid all entries in the parentArray | |
| * will contain this pid, so this array does only make sense in the case of a given | |
| * zero pid. | |
| * If the jstimesArray is non-null, store also the start time of the pid. | |
| * In this case the jstimesArray must have the same length as the result pid array. | |
| * If the array(s) (is|are) too short, excess pids are not stored and | |
| * the desired length is returned. | |
| */ | |
| jint os_getChildren(JNIEnv *env, jlong jpid, jlongArray jarray, | |
| jlongArray jparentArray, jlongArray jstimesArray) { | |
| jlong* pids = NULL; | |
| jlong* ppids = NULL; | |
| jlong* stimes = NULL; | |
| jsize parentArraySize = 0; | |
| jsize arraySize = 0; | |
| jsize stimesSize = 0; | |
| jsize count = 0; | |
| size_t bufSize = 0; | |
| pid_t pid = (pid_t) jpid; | |
| arraySize = (*env)->GetArrayLength(env, jarray); | |
| JNU_CHECK_EXCEPTION_RETURN(env, -1); | |
| if (jparentArray != NULL) { | |
| parentArraySize = (*env)->GetArrayLength(env, jparentArray); | |
| JNU_CHECK_EXCEPTION_RETURN(env, -1); | |
| if (arraySize != parentArraySize) { | |
| JNU_ThrowIllegalArgumentException(env, "array sizes not equal"); | |
| return 0; | |
| } | |
| } | |
| if (jstimesArray != NULL) { | |
| stimesSize = (*env)->GetArrayLength(env, jstimesArray); | |
| JNU_CHECK_EXCEPTION_RETURN(env, -1); | |
| if (arraySize != stimesSize) { | |
| JNU_ThrowIllegalArgumentException(env, "array sizes not equal"); | |
| return 0; | |
| } | |
| } | |
| int errsysctl; | |
| int maxRetries = 100; | |
| void *buffer = NULL; | |
| do { | |
| int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0}; | |
| if (buffer != NULL) free(buffer); | |
| // Get buffer size needed to read all processes | |
| if (sysctl(mib, 4, NULL, &bufSize, NULL, 0) < 0) { | |
| JNU_ThrowByNameWithMessageAndLastError(env, | |
| "java/lang/RuntimeException", "sysctl failed"); | |
| return -1; | |
| } | |
| // Allocate buffer big enough for all processes; add a little | |
| // bit of space to be able to hold a few more proc infos | |
| // for processes started right after the first sysctl call | |
| buffer = malloc(bufSize + 4 * sizeof(struct kinfo_proc)); | |
| if (buffer == NULL) { | |
| JNU_ThrowOutOfMemoryError(env, "malloc failed"); | |
| return -1; | |
| } | |
| // Read process info for all processes | |
| errsysctl = sysctl(mib, 4, buffer, &bufSize, NULL, 0); | |
| } while (errsysctl < 0 && errno == ENOMEM && maxRetries-- > 0); | |
| if (errsysctl < 0) { | |
| JNU_ThrowByNameWithMessageAndLastError(env, | |
| "java/lang/RuntimeException", "sysctl failed to get info about all processes"); | |
| free(buffer); | |
| return -1; | |
| } | |
| do { // Block to break out of on Exception | |
| struct kinfo_proc *kp = (struct kinfo_proc *) buffer; | |
| unsigned long nentries = bufSize / sizeof (struct kinfo_proc); | |
| long i; | |
| pids = (*env)->GetLongArrayElements(env, jarray, NULL); | |
| if (pids == NULL) { | |
| break; | |
| } | |
| if (jparentArray != NULL) { | |
| ppids = (*env)->GetLongArrayElements(env, jparentArray, NULL); | |
| if (ppids == NULL) { | |
| break; | |
| } | |
| } | |
| if (jstimesArray != NULL) { | |
| stimes = (*env)->GetLongArrayElements(env, jstimesArray, NULL); | |
| if (stimes == NULL) { | |
| break; | |
| } | |
| } | |
| // Process each entry in the buffer | |
| for (i = nentries; --i >= 0; ++kp) { | |
| if (pid == 0 || kp->kp_eproc.e_ppid == pid) { | |
| if (count < arraySize) { | |
| // Only store if it fits | |
| pids[count] = (jlong) kp->kp_proc.p_pid; | |
| if (ppids != NULL) { | |
| // Store the parentPid | |
| ppids[count] = (jlong) kp->kp_eproc.e_ppid; | |
| } | |
| if (stimes != NULL) { | |
| // Store the process start time | |
| jlong startTime = kp->kp_proc.p_starttime.tv_sec * 1000 + | |
| kp->kp_proc.p_starttime.tv_usec / 1000; | |
| stimes[count] = startTime; | |
| } | |
| } | |
| count++; // Count to tabulate size needed | |
| } | |
| } | |
| } while (0); | |
| if (pids != NULL) { | |
| (*env)->ReleaseLongArrayElements(env, jarray, pids, 0); | |
| } | |
| if (ppids != NULL) { | |
| (*env)->ReleaseLongArrayElements(env, jparentArray, ppids, 0); | |
| } | |
| if (stimes != NULL) { | |
| (*env)->ReleaseLongArrayElements(env, jstimesArray, stimes, 0); | |
| } | |
| free(buffer); | |
| // If more pids than array had size for; count will be greater than array size | |
| return count; | |
| } | |
| /** | |
| * Use sysctl and return the ppid, total cputime and start time. | |
| * Return: -1 is fail; >= 0 is parent pid | |
| * 'total' will contain the running time of 'pid' in nanoseconds. | |
| * 'start' will contain the start time of 'pid' in milliseconds since epoch. | |
| */ | |
| pid_t os_getParentPidAndTimings(JNIEnv *env, pid_t jpid, | |
| jlong *totalTime, jlong *startTime) { | |
| const pid_t pid = (pid_t) jpid; | |
| pid_t ppid = -1; | |
| struct kinfo_proc kp; | |
| size_t bufSize = sizeof kp; | |
| // Read the process info for the specific pid | |
| int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; | |
| if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) < 0) { | |
| JNU_ThrowByNameWithLastError(env, | |
| "java/lang/RuntimeException", "sysctl failed"); | |
| return -1; | |
| } | |
| if (bufSize > 0 && kp.kp_proc.p_pid == pid) { | |
| *startTime = (jlong) (kp.kp_proc.p_starttime.tv_sec * 1000 + | |
| kp.kp_proc.p_starttime.tv_usec / 1000); | |
| ppid = kp.kp_eproc.e_ppid; | |
| } | |
| // Get cputime if for current process | |
| if (pid == getpid()) { | |
| struct rusage usage; | |
| if (getrusage(RUSAGE_SELF, &usage) == 0) { | |
| jlong microsecs = | |
| usage.ru_utime.tv_sec * 1000 * 1000 + usage.ru_utime.tv_usec + | |
| usage.ru_stime.tv_sec * 1000 * 1000 + usage.ru_stime.tv_usec; | |
| *totalTime = microsecs * 1000; | |
| } | |
| } | |
| return ppid; | |
| } | |
| /** | |
| * Return the uid of a process or -1 on error | |
| */ | |
| static uid_t getUID(pid_t pid) { | |
| struct kinfo_proc kp; | |
| size_t bufSize = sizeof kp; | |
| // Read the process info for the specific pid | |
| int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_PID, pid}; | |
| if (sysctl(mib, 4, &kp, &bufSize, NULL, 0) == 0) { | |
| if (bufSize > 0 && kp.kp_proc.p_pid == pid) { | |
| return kp.kp_eproc.e_ucred.cr_uid; | |
| } | |
| } | |
| return (uid_t)-1; | |
| } | |
| /** | |
| * Retrieve the command and arguments for the process and store them | |
| * into the Info object. | |
| */ | |
| void os_getCmdlineAndUserInfo(JNIEnv *env, jobject jinfo, pid_t pid) { | |
| int mib[3], maxargs, nargs; | |
| size_t size; | |
| char *args, *cp; | |
| // Get the UID first. This is done here because it is cheap to do it here | |
| // on other platforms like Linux/Solaris/AIX where the uid comes from the | |
| // same source like the command line info. | |
| unix_getUserInfo(env, jinfo, getUID(pid)); | |
| // Get the maximum size of the arguments | |
| mib[0] = CTL_KERN; | |
| mib[1] = KERN_ARGMAX; | |
| size = sizeof(maxargs); | |
| if (sysctl(mib, 2, &maxargs, &size, NULL, 0) == -1) { | |
| JNU_ThrowByNameWithLastError(env, | |
| "java/lang/RuntimeException", "sysctl failed"); | |
| return; | |
| } | |
| // Allocate an args buffer and get the arguments | |
| args = (char *)malloc(maxargs); | |
| if (args == NULL) { | |
| JNU_ThrowOutOfMemoryError(env, "malloc failed"); | |
| return; | |
| } | |
| do { // a block to break out of on error | |
| char *argsEnd; | |
| jstring cmdexe = NULL; | |
| mib[0] = CTL_KERN; | |
| mib[1] = KERN_PROCARGS2; | |
| mib[2] = pid; | |
| size = (size_t) maxargs; | |
| if (sysctl(mib, 3, args, &size, NULL, 0) == -1) { | |
| if (errno != EINVAL && errno != EIO) { | |
| // If the pid is invalid, the information returned is empty and no exception | |
| JNU_ThrowByNameWithLastError(env, | |
| "java/lang/RuntimeException", "sysctl failed"); | |
| } | |
| break; | |
| } | |
| memcpy(&nargs, args, sizeof(nargs)); | |
| cp = &args[sizeof(nargs)]; // Strings start after nargs | |
| argsEnd = &args[size]; | |
| // Store the command executable path | |
| if ((cmdexe = JNU_NewStringPlatform(env, cp)) == NULL) { | |
| break; | |
| } | |
| // Skip trailing nulls after the executable path | |
| for (cp = cp + strnlen(cp, argsEnd - cp); cp < argsEnd; cp++) { | |
| if (*cp != '\0') { | |
| break; | |
| } | |
| } | |
| unix_fillArgArray(env, jinfo, nargs, cp, argsEnd, cmdexe, NULL); | |
| } while (0); | |
| // Free the arg buffer | |
| free(args); | |
| } |