/*
 * Copyright (c) 2015, 2025, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2025 SAP SE. 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 "ProcessHandleImpl_unix.h"

#include <sys/procfs.h>
#include <procinfo.h>
#include <string.h>

/*
 * Implementation of native ProcessHandleImpl functions for AIX.
 * 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, only the direct children are returned.
 * If the pid is zero, all active processes are returned.
 * Use getprocs64 to accumulate 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) {
    pid_t pid = (pid_t) jpid;
    jlong* pids = NULL;
    jlong* ppids = NULL;
    jlong* stimes = NULL;
    jsize parentArraySize = 0;
    jsize arraySize = 0;
    jsize stimesSize = 0;
    jsize count = 0;

    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;
        }
    }

    const int chunk = 100;
    struct procentry64 ProcessBuffer[chunk];
    pid_t idxptr = 0;
    int i, num = 0;

    do { // Block to break out of on Exception
        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;
            }
        }

        while ((num = getprocs64(ProcessBuffer, sizeof(struct procentry64), NULL,
                                 sizeof(struct fdsinfo64), &idxptr, chunk)) != -1) {
            for (i = 0; i < num; i++) {
                pid_t childpid = (pid_t) ProcessBuffer[i].pi_pid;
                pid_t ppid = (pid_t) ProcessBuffer[i].pi_ppid;

                // Get the parent pid, and start time
                if (pid == 0 || ppid == pid) {
                    if (count < arraySize) {
                        // Only store if it fits
                        pids[count] = (jlong) childpid;

                        if (ppids != NULL) {
                            // Store the parentPid
                            ppids[count] = (jlong) ppid;
                        }
                        if (stimes != NULL) {
                            // Store the process start time
                            stimes[count] = ((jlong) ProcessBuffer[i].pi_start) * 1000;;
                        }
                    }
                    count++; // Count to tabulate size needed
                }
            }
            if (num < chunk) {
                break;
            }
        }
    } 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);
    }

    if (num == -1) {
        JNU_ThrowByNameWithLastError(env,
            "java/lang/RuntimeException", "Unable to retrieve Process info");
        return -1;
    }

    // If more pids than array had size for; count will be greater than array size
    return count;
}

pid_t os_getParentPidAndTimings(JNIEnv *env, pid_t pid, jlong *total, jlong *start) {
    pid_t the_pid = pid;
    struct procentry64 ProcessBuffer;

    if (getprocs64(&ProcessBuffer, sizeof(ProcessBuffer), NULL, sizeof(struct fdsinfo64), &the_pid, 1) <= 0) {
        return -1;
    }

    // Validate the pid before returning the info
    if (kill(pid, 0) < 0) {
        return -1;
    }

    *total = ((ProcessBuffer.pi_ru.ru_utime.tv_sec + ProcessBuffer.pi_ru.ru_stime.tv_sec) * 1000000000L) +
             ((ProcessBuffer.pi_ru.ru_utime.tv_usec + ProcessBuffer.pi_ru.ru_stime.tv_usec));

    *start = ProcessBuffer.pi_start * (jlong)1000;

    return (pid_t) ProcessBuffer.pi_ppid;
}

/**
 * Helper function to get the 'psinfo_t' data from "/proc/%d/psinfo".
 * Returns 0 on success and -1 on error.
 */
static int getPsinfo(pid_t pid, psinfo_t *psinfo) {
    FILE* fp;
    char fn[32];
    size_t ret;

    /*
     * Try to open /proc/%d/psinfo
     */
    snprintf(fn, sizeof fn, "/proc/%d/psinfo", pid);
    fp = fopen(fn, "r");
    if (fp == NULL) {
        return -1;
    }

    ret = fread(psinfo, 1, sizeof(psinfo_t), fp);
    fclose(fp);
    if (ret < sizeof(psinfo_t)) {
        return -1;
    }
    return 0;
}

void os_getCmdlineAndUserInfo(JNIEnv *env, jobject jinfo, pid_t pid) {
    psinfo_t psinfo;
    char prargs[PRARGSZ + 1];
    jstring cmdexe = NULL;

    /*
     * Now try to open /proc/%d/psinfo
     */
    if (getPsinfo(pid, &psinfo) < 0) {
        unix_fillArgArray(env, jinfo, 0, NULL, NULL, cmdexe, NULL);
        return;
    }

    unix_getUserInfo(env, jinfo, psinfo.pr_uid);

    /*
     * Now read psinfo.pr_psargs which contains the first PRARGSZ characters of the
     * argument list (i.e. arg[0] arg[1] ...). Unfortunately, PRARGSZ is usually set
     * to 80 characters only. Nevertheless it's better than nothing :)
     */
    strncpy(prargs, psinfo.pr_psargs, PRARGSZ);
    prargs[PRARGSZ] = '\0';
    if (prargs[0] == '\0') {
        /* If psinfo.pr_psargs didn't contain any strings, use psinfo.pr_fname
         * (which only contains the last component of exec()ed pathname) as a
         * last resort. This is true for AIX kernel processes for example.
         */
        strncpy(prargs, psinfo.pr_fname, PRARGSZ);
        prargs[PRARGSZ] = '\0';
    }
    unix_fillArgArray(env, jinfo, 0, NULL, NULL, cmdexe,
                      prargs[0] == '\0' ? NULL : prargs);
}
