All Downloads are FREE. Search and download functionalities are using the official Maven repository.

java.lang.ProcessHandleImpl$Info$_patch Maven / Gradle / Ivy

/*
 * This code is based on OpenJDK source file(s) which contain the following copyright notice:
 *
 * ------
 * Copyright (c) 2001, 2021, 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.
 * ------
 *
 * This file may contain additional modifications which are Copyright (c) Red Hat and other
 * contributors.
 */
package java.lang;

import static org.qbicc.runtime.CNative.*;
import static jdk.internal.sys.bsd.SysProc.*;
import static jdk.internal.sys.bsd.SysSysctl.*;
import static jdk.internal.sys.linux.Unistd.*;
import static jdk.internal.sys.posix.Errno.*;
import static jdk.internal.sys.posix.Fcntl.*;
import static jdk.internal.sys.posix.Limits.*;
import static jdk.internal.sys.posix.Pwd.*;
import static jdk.internal.sys.posix.String.*;
import static jdk.internal.sys.posix.SysResource.*;
import static jdk.internal.sys.posix.SysStat.*;
import static jdk.internal.sys.posix.SysTime.*;
import static jdk.internal.sys.posix.SysTypes.*;
import static jdk.internal.sys.posix.Unistd.*;
import static org.qbicc.runtime.stdc.Errno.*;
import static org.qbicc.runtime.stdc.Stddef.*;
import static org.qbicc.runtime.stdc.Stdint.*;
import static jdk.internal.sys.stdc.Stdio.*;
import static org.qbicc.runtime.stdc.Stdlib.*;
import static org.qbicc.runtime.stdc.String.*;

import org.qbicc.rt.annotation.Tracking;
import org.qbicc.runtime.Build;
import org.qbicc.runtime.patcher.Patch;
import org.qbicc.runtime.patcher.Add;
import org.qbicc.runtime.patcher.Replace;

@Patch("java.lang.ProcessHandleImpl$Info")
@Tracking("src/java.base/unix/native/libjava/ProcessHandleImpl_unix.c")
@Tracking("src/java.base/windows/native/libjava/ProcessHandleImpl_win.c")
class ProcessHandleImpl$Info$_patch {
    // alias
    String command;
    String commandLine;
    String[] arguments;
    long startTime;
    long totalTime;
    String user;

    @Replace
    static void initIDs() {
    }

    @Add(when = Build.Target.IsMacOs.class)
    private static pid_t getParentPidAndTimings_MacOs(pid_t pid, ptr totalTime, ptr startTime) {
        pid_t ppid = word(-1);
        struct_kinfo_proc kp = auto();
        size_t bufSize = auto(sizeof(kp));
        c_int[] mib = new c_int[]{CTL_KERN, KERN_PROC, KERN_PROC_PID, pid.cast()};

        if (sysctl(addr_of(mib[0]), word(4), addr_of(kp), addr_of(bufSize), word(0), word(0)).intValue() < 0) {
            throw new RuntimeException("sysctl failed");
        }

        if (bufSize.intValue() > 0 && kp.kp_proc.p_pid == pid) {
            ptr tvp = addr_of(kp.kp_proc.p_un).cast();
            struct_timeval tv = tvp.loadUnshared();
            long st = tv.tv_sec.longValue() * 1000 + tv.tv_usec.longValue() / 1000;
            startTime.storeUnshared(word(st));
            ppid = kp.kp_eproc.e_ppid;
        }

        // Get cputime if for current process
        if (pid == getpid()) {
            struct_rusage usage = auto();
            if (getrusage(RUSAGE_SELF, addr_of(usage)).intValue() == 0) {
                long microsecs = usage.ru_utime.tv_sec.longValue() * 1000 * 1000 + usage.ru_utime.tv_usec.longValue() +
                        usage.ru_stime.tv_sec.longValue() * 1000 * 1000 + usage.ru_stime.tv_usec.longValue();
                totalTime.storeUnshared(word(microsecs * 1000));
            }
        }
        return ppid;
    }

    /**
     * Read /proc//stat and return the ppid, total cputime and start time.
     * -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.
     */
    @Add(when = Build.Target.IsLinux.class)
    private static pid_t getParentPidAndTimings_Linux(pid_t pid, ptr totalTime, ptr startTime) {
        c_char[] buffer = new c_char[2048];
        c_char[] fn = new c_char[32];
        pid_t parentPid = auto();
        uint64_t utime = auto();
        uint64_t stime = auto();
        uint64_t start = auto();

        /*
         * Try to stat and then open /proc/%d/stat
         */
        snprintf(addr_of(fn[0]), sizeof(fn), utf8z("/proc/%d/stat"), pid);

        ptr fp = fopen(addr_of(fn[0]), utf8z("r"));
        if (fp.isNull()) {
            return word(-1);              // fail, no such /proc/pid/stat
        }

        /*
         * The format is: pid (command) state ppid ...
         * As the command could be anything we must find the right most
         * ")" and then skip the white spaces that follow it.
         */
        int statlen = fread(addr_of(buffer[0]).cast(), word(1), word(sizeof(buffer).intValue() - 1), fp.cast()).intValue();
        fclose(fp.cast());
        if (statlen < 0) {
            return word(-1);               // parent pid is not available
        }

        buffer[statlen] = word('\0');
        ptr s = strchr(addr_of(buffer[0]), word('('));
        if (s.isNull()) {
            return word(-1);               // parent pid is not available
        }
        // Found start of command, skip to end
        s = s.plus(1);
        s = strrchr(s.cast(), word(')'));
        if (s.isNull()) {
            return word(-1);               // parent pid is not available
        }
        s = s.plus(1);

        // Scan the needed fields from status, retaining only ppid(4),
        // utime (14), stime(15), starttime(22)
        if (4 != sscanf(s.cast(), utf8z(" %*c %d %*d %*d %*d %*d %*d %*u %*u %*u %*u %lu %lu %*d %*d %*d %*d %*d %*d %llu"),
                addr_of(parentPid), addr_of(utime), addr_of(stime), addr_of(start)).intValue()) {
            return word(0);              // not all values parsed; return error
        }

        totalTime.storeUnshared(word((utime.longValue() + stime.longValue()) * (1000000000L / ProcessHandleImpl$Info$_runtime.clock_ticks_per_second)));
        startTime.storeUnshared(word(ProcessHandleImpl$Info$_runtime.bootTime_ms + ((start.longValue() * 1000) / ProcessHandleImpl$Info$_runtime.clock_ticks_per_second)));

        return parentPid;
    }

    @Add
    private static size_t getpw_buf_size() {
        size_t buf_size = sysconf(_SC_GETPW_R_SIZE_MAX).cast();
        return buf_size.intValue() == -1 ? word(1024) : buf_size;
    }

    @Add
    private void getUserInfo(uid_t uid) {
        size_t buf_size = getpw_buf_size();
        ptr pwbuf = malloc(buf_size);
        if (pwbuf.isNull()) {
            throw new OutOfMemoryError("Unable to open getpwent");
        }
        struct_passwd pwent = auto();
        ptr p = auto(word(0));
        c_int result = word(0);
        do {
            result = getpwuid_r(uid, addr_of(pwent), pwbuf, buf_size, addr_of(p).cast());
        } while (result.intValue() == -1 && errno == EINTR.intValue());

        if (result.intValue() == 0 && !p.isNull() && !p.sel().pw_name.isNull() &&
                p.sel().pw_name.get(0) != word('\0')) {
            this.user = utf8zToJavaString(p.sel().pw_name.cast());
        }
        free(pwbuf);
    }

    @Add
    private void fillArgArray(int nargs, ptr<@c_const c_char> cp, ptr<@c_const c_char> argsEnd, ptr<@c_const c_char> cmdline) {
        if (nargs >= 1) {
            String[] argsArray = new String[nargs - 1];

            for (int i = 0; i < nargs - 1; i++) {
                cp = cp.plus(strlen(cp).intValue()).plus(1);
                if (cp.isGt(argsEnd) || cp.loadUnshared() == word('\0')) {
                    return;  // Off the end pointer or an empty argument is an error
                }
                argsArray[i] = utf8zToJavaString(cp.cast());
            }
            this.arguments = argsArray;
        }

        if (!cmdline.isNull()) {
            this.commandLine = utf8zToJavaString(cmdline.cast());
        }
    }

    @Add(when = Build.Target.IsMacOs.class)
    private static uid_t getUID(pid_t pid) {
        struct_kinfo_proc kp = auto();
        size_t bufSize = auto(sizeof(kp));
        c_int[] mib = new c_int[]{CTL_KERN, KERN_PROC, KERN_PROC_PID, pid.cast()};

        if (sysctl(addr_of(mib[0]), word(4), addr_of(kp), addr_of(bufSize), word(0), word(0)).intValue() == 0) {
            if (bufSize.intValue() > 0 && kp.kp_proc.p_pid == pid) {
                return kp.kp_eproc.e_ucred.cr_uid;
            }
        }
        return word(-1);
    }

    @Add(when = Build.Target.IsMacOs.class)
    private void getCmdlineAndUserInfo_MacOs(pid_t pid) {
        getUserInfo(getUID(pid));

        // Get the maximum size of the arguments
        c_int[] mib = new c_int[3];
        mib[0] = CTL_KERN;
        mib[1] = KERN_ARGMAX;
        c_int maxargs = auto();
        size_t size = auto(sizeof(maxargs));
        if (sysctl(addr_of(mib[0]), word(2), addr_of(maxargs), addr_of(size), word(0), word(0)).intValue() == -1) {
            throw new RuntimeException("sysctl failed");
        }

        ptr args = malloc(maxargs.cast());
        if (args.isNull()) {
            throw new OutOfMemoryError("malloc failed");
        }
        try {
            // Get the actual arguments
            mib[0] = CTL_KERN;
            mib[1] = KERN_PROCARGS2;
            mib[2] = pid.cast();
            size = maxargs.cast();
            if (sysctl(addr_of(mib[0]), word(3), args, addr_of(size), word(0), word(0)).intValue() == -1) {
                throw new RuntimeException("sysctl failed");
            }

            c_int nargs = args.loadUnshared(c_int.class);
            ptr cp = args.plus(sizeof(nargs).intValue());
            ptr argsEnd = args.plus(maxargs.intValue());

            // Store the command executable path
            this.command = utf8zToJavaString(cp.cast());

            // Skip trailing nulls after the executable path
            for (cp = cp.plus(strnlen(cp.cast(), word(maxargs.intValue() - sizeof(nargs).intValue())).intValue());
                 cp.isLt(argsEnd); cp = cp.plus(1)) {
                if (cp.loadUnshared() != word('\0')) {
                    break;
                }
            }

            fillArgArray(nargs.intValue(), cp.cast(), argsEnd.cast(), zero());
        } finally {
            free(args);
        }
    }

    @Add(when = Build.Target.IsLinux.class)
    private void getCmdlineAndUserInfo_Linux(pid_t pid) {
        ptr cmdline = zero();
        ptr cmdEnd = zero();
        ptr args = zero();
        c_char[] fn = new c_char[32];
        struct_stat stat_buf = auto();

        /*
         * Stat /proc/ to get the user id
         */
        snprintf(addr_of(fn[0]).cast(), sizeof(fn), utf8z("/proc/%d"), pid);
        if (stat(addr_of(fn[0]).cast(), addr_of(stat_buf)).isZero()) {
            getUserInfo(stat_buf.st_uid);
        }

        /*
         * Try to open /proc//cmdline
         */
        strncat(addr_of(fn[0]).cast(), utf8z("/cmdline").cast(), word(sizeof(fn).intValue() - strnlen(addr_of(fn[0]).cast(), sizeof(fn)).intValue() - 1));
        c_int fd = open(addr_of(fn[0]).cast(), O_RDONLY);
        if (fd.intValue() < 0) {
            return;
        }

        try {
            int i = 0;
            boolean truncated = false;
            int count;
            int pageSize = ProcessHandleImpl$Info$_runtime.pageSize;

            /*
             * The path name read by readlink() is limited to PATH_MAX characters.
             * The content of /proc//cmdline is limited to PAGE_SIZE characters.
             */
            cmdline = malloc(word((PATH_MAX.intValue() > pageSize ? PATH_MAX.intValue() : pageSize) + 1 ));
            if (cmdline.isNull()) {
                return;
            }

            /*
             * On Linux, the full path to the executable command is the link in
             * /proc//exe. But it is only readable for processes we own.
             */
            snprintf(addr_of(fn[0]), sizeof(fn), utf8z("/proc/%d/exe").cast(), pid);
            int cmdlen = readlink(addr_of(fn[0]), cmdline, PATH_MAX).intValue();
            if (cmdlen > 0) {
                // null terminate and create String to store for command
                cmdline.set(cmdlen, word('\0'));
                this.command = utf8zToJavaString(cmdline.cast());
            }

            /*
             * The command-line arguments appear as a set of strings separated by
             * null bytes ('\0'), with a further null byte after the last
             * string. The last string is only null terminated if the whole command
             * line is not exceeding (PAGE_SIZE - 1) characters.
             */
            cmdlen = 0;
            ptr s = cmdline;
            while ((count = read(fd, s.cast(), word(pageSize - cmdlen)).intValue()) > 0) {
                cmdlen += count;
                s = s.plus(count);
            }
            if (count < 0) {
                return;
            }
            // We have to null-terminate because the process may have changed argv[]
            // or because the content in /proc//cmdline is truncated.
            cmdline.set(cmdlen, word('\0'));
            if (cmdlen == pageSize && !cmdline.get(pageSize - 1).isZero()) {
                truncated = true;
            } else if (cmdlen == 0) {
                // /proc//cmdline was empty. This usually happens for kernel processes
                // like '[kthreadd]'. We could try to read /proc//comm in the future.
            }
            if (cmdlen > 0 && (this.command == null || truncated)) {
                // We have no exact command or the arguments are truncated.
                // In this case we save the command line from /proc//cmdline.
                args = malloc(word(pageSize + 1));
                if (!args.isNull()) {
                    memcpy(args.cast(), cmdline.cast(), word(cmdlen + 1));
                    for (i = 0; i < cmdlen; i++) {
                        if (args.get(i).isZero()) {
                            args.set(i, word(' '));
                        }
                    }
                }
            }
            i = 0;
            if (!truncated) {
                // Count the arguments
                cmdEnd = cmdline.plus(cmdlen);
                for (s = cmdline; s.loadUnshared() != word('\0') && (s.isLt(cmdEnd)); i++) {
                    s = s.plus(strnlen(s.cast(), cmdEnd.minus(s).cast()).intValue() + 1);
                }
            }
            fillArgArray(i, cmdline.cast(), cmdEnd.cast(), args.cast());
        } finally {
            free(cmdline);
            free(args);
            if (fd.intValue() > 0) {
                close(fd);
            }
        }
    }

    @Add
    static pid_t getParentPidAndTimings(pid_t pid, ptr totalTime, ptr startTime) {
        if (Build.Target.isMacOs()) {
            return getParentPidAndTimings_MacOs(pid, totalTime, startTime);
        } else if (Build.Target.isLinux()) {
            return getParentPidAndTimings_Linux(pid, totalTime, startTime);
        } else {
            throw new UnsupportedOperationException("TODO: getParentPidAndTimings");
        }
    }

    @Add
    private void getCmdlineAndUserInfo(pid_t pid) {
        if (Build.Target.isMacOs()) {
            getCmdlineAndUserInfo_MacOs(pid);
        } else if (Build.Target.isLinux()){
            getCmdlineAndUserInfo_Linux(pid);
        } else {
            throw new UnsupportedOperationException("TODO: getCmdlineAndUserInfo");
        }
    }

    @Replace
    private void info0(long jpid) {
        if (Build.Target.isPosix()) {
            pid_t pid = word(jpid);
            long totalTime = auto(-1L);
            long startTime = auto(-1L);
            pid_t ppid = getParentPidAndTimings(pid, addr_of(totalTime), addr_of(startTime));
            if (ppid.intValue() >= 0) {
                this.totalTime = totalTime;
                this.startTime = startTime;
            }
            getCmdlineAndUserInfo(pid);
        } else {
            throw new UnsupportedOperationException("Unsupported platform");
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy