/*
 * Copyright (c) 2023-2023 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sched.h>
#include "los_pid_container_pri.h"
#include "los_config.h"
#include "los_process_pri.h"
#include "los_container_pri.h"

#ifdef LOSCFG_PID_CONTAINER

STATIC UINT32 g_currentPidContainerNum;
STATIC LosProcessCB *g_defaultProcessCB = NULL;
STATIC LosTaskCB *g_defaultTaskCB = NULL;

STATIC VOID FreeVpid(LosProcessCB *processCB)
{
    PidContainer *pidContainer = processCB->container->pidContainer;
    UINT32 vpid = processCB->processID;

    while ((pidContainer != NULL) && !OS_PID_CHECK_INVALID(vpid)) {
        ProcessVid *processVid = &pidContainer->pidArray[vpid];
        processVid->cb = (UINTPTR)g_defaultProcessCB;
        vpid = processVid->vpid;
        processVid->vpid = OS_INVALID_VALUE;
        LOS_ListTailInsert(&pidContainer->pidFreeList, &processVid->node);
        LOS_AtomicDec(&pidContainer->rc);
        pidContainer = pidContainer->parent;
    }
}

STATIC ProcessVid *OsGetFreeVpid(PidContainer *pidContainer)
{
    if (LOS_ListEmpty(&pidContainer->pidFreeList)) {
        return NULL;
    }

    ProcessVid *vpid = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&pidContainer->pidFreeList), ProcessVid, node);
    LOS_ListDelete(&vpid->node);
    return vpid;
}

UINT32 OsAllocSpecifiedVpidUnsafe(UINT32 vpid, LosProcessCB *processCB, LosProcessCB *parent)
{
    PidContainer *pidContainer = processCB->container->pidContainer;
    if ((pidContainer == NULL) || OS_PID_CHECK_INVALID(vpid)) {
        return OS_INVALID_VALUE;
    }

    if (LOS_AtomicRead(&pidContainer->lock) > 0) {
        return OS_INVALID_VALUE;
    }

    ProcessVid *processVid = &pidContainer->pidArray[vpid];
    if (processVid->cb != (UINTPTR)g_defaultProcessCB) {
        return OS_INVALID_VALUE;
    }

    processVid->cb = (UINTPTR)processCB;
    processCB->processID = vpid;
    LOS_ListDelete(&processVid->node);
    LOS_AtomicInc(&pidContainer->rc);

    if ((vpid == OS_USER_ROOT_PROCESS_ID) && (parent != NULL)) {
        ProcessVid *vppidItem = &pidContainer->pidArray[0];
        LOS_ListDelete(&vppidItem->node);
        vppidItem->cb = (UINTPTR)parent;
    }

    pidContainer = pidContainer->parent;
    while (pidContainer != NULL) {
        ProcessVid *item = OsGetFreeVpid(pidContainer);
        if (item == NULL) {
            break;
        }

        item->cb = (UINTPTR)processCB;
        processVid->vpid = item->vid;
        LOS_AtomicInc(&pidContainer->rc);
        processVid = item;
        pidContainer = pidContainer->parent;
    }
    return processCB->processID;
}

STATIC UINT32 OsAllocVpid(LosProcessCB *processCB)
{
    ProcessVid *oldProcessVid = NULL;
    PidContainer *pidContainer = processCB->container->pidContainer;
    if ((pidContainer == NULL) || (LOS_AtomicRead(&pidContainer->lock) > 0)) {
        return OS_INVALID_VALUE;
    }

    processCB->processID = OS_INVALID_VALUE;
    do {
        ProcessVid *vpid = OsGetFreeVpid(pidContainer);
        if (vpid == NULL) {
            break;
        }
        vpid->cb = (UINTPTR)processCB;
        if (processCB->processID == OS_INVALID_VALUE) {
            processCB->processID = vpid->vid;
        } else {
            oldProcessVid->vpid = vpid->vid;
        }
        LOS_AtomicInc(&pidContainer->rc);
        oldProcessVid = vpid;
        pidContainer = pidContainer->parent;
    } while (pidContainer != NULL);
    return processCB->processID;
}

STATIC ProcessVid *OsGetFreeVtid(PidContainer *pidContainer)
{
    if (LOS_ListEmpty(&pidContainer->tidFreeList)) {
        return NULL;
    }

    ProcessVid *vtid = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&pidContainer->tidFreeList), ProcessVid, node);
    LOS_ListDelete(&vtid->node);
    return vtid;
}

VOID OsFreeVtid(LosTaskCB *taskCB)
{
    PidContainer *pidContainer = taskCB->pidContainer;
    UINT32 vtid = taskCB->taskID;

    while ((pidContainer != NULL) && !OS_TID_CHECK_INVALID(vtid)) {
        ProcessVid *item = &pidContainer->tidArray[vtid];
        item->cb = (UINTPTR)g_defaultTaskCB;
        vtid = item->vpid;
        item->vpid = OS_INVALID_VALUE;
        LOS_ListTailInsert(&pidContainer->tidFreeList, &item->node);
        pidContainer = pidContainer->parent;
    }
    taskCB->pidContainer = NULL;
}

UINT32 OsAllocVtid(LosTaskCB *taskCB, const LosProcessCB *processCB)
{
    PidContainer *pidContainer = processCB->container->pidContainer;
    ProcessVid *oldTaskVid = NULL;

    do {
        ProcessVid *item = OsGetFreeVtid(pidContainer);
        if (item == NULL) {
            return OS_INVALID_VALUE;
        }

        if ((pidContainer->parent != NULL) && (item->vid == 0)) {
            item->cb = (UINTPTR)OsCurrTaskGet();
            item->vpid = OsCurrTaskGet()->taskID;
            continue;
        }

        item->cb = (UINTPTR)taskCB;
        if (taskCB->pidContainer == NULL) {
            taskCB->pidContainer = pidContainer;
            taskCB->taskID = item->vid;
        } else {
            oldTaskVid->vpid = item->vid;
        }
        oldTaskVid = item;
        pidContainer = pidContainer->parent;
    } while (pidContainer != NULL);

    return taskCB->taskID;
}

VOID OsPidContainerDestroyAllProcess(LosProcessCB *curr)
{
    INT32 ret;
    UINT32 intSave;
    PidContainer *pidContainer = curr->container->pidContainer;
    LOS_AtomicInc(&pidContainer->lock);

    for (UINT32 index = 2; index < LOSCFG_BASE_CORE_PROCESS_LIMIT; index++) { /* 2: ordinary process */
        SCHEDULER_LOCK(intSave);
        LosProcessCB *processCB = OS_PCB_FROM_PID(index);
        if (OsProcessIsUnused(processCB)) {
            SCHEDULER_UNLOCK(intSave);
            continue;
        }

        if (curr != processCB->parentProcess) {
            LOS_ListDelete(&processCB->siblingList);
            if (OsProcessIsInactive(processCB)) {
                LOS_ListTailInsert(&curr->exitChildList, &processCB->siblingList);
            } else {
                LOS_ListTailInsert(&curr->childrenList, &processCB->siblingList);
            }
            processCB->parentProcess = curr;
        }
        SCHEDULER_UNLOCK(intSave);

        ret = OsKillLock(index, SIGKILL);
        if (ret < 0) {
            PRINT_ERR("Pid container kill all process failed, pid %u, errno=%d\n", index, -ret);
        }

        ret = LOS_Wait(index, NULL, 0, NULL);
        if (ret != index) {
            PRINT_ERR("Pid container wait pid %d failed, errno=%d\n", index, -ret);
        }
    }
}

STATIC PidContainer *CreateNewPidContainer(PidContainer *parent)
{
    UINT32 index;
    PidContainer *newPidContainer = (PidContainer *)LOS_MemAlloc(m_aucSysMem1, sizeof(PidContainer));
    if (newPidContainer == NULL) {
        return NULL;
    }
    (VOID)memset_s(newPidContainer, sizeof(PidContainer), 0, sizeof(PidContainer));

    newPidContainer->containerID = OsAllocContainerID();
    LOS_ListInit(&newPidContainer->pidFreeList);
    for (index = 0; index < LOSCFG_BASE_CORE_PROCESS_LIMIT; index++) {
        ProcessVid *vpid = &newPidContainer->pidArray[index];
        vpid->vid = index;
        vpid->vpid = OS_INVALID_VALUE;
        vpid->cb = (UINTPTR)g_defaultProcessCB;
        LOS_ListTailInsert(&newPidContainer->pidFreeList, &vpid->node);
    }

    LOS_ListInit(&newPidContainer->tidFreeList);
    for (index = 0; index < LOSCFG_BASE_CORE_TSK_LIMIT; index++) {
        ProcessVid *vtid = &newPidContainer->tidArray[index];
        vtid->vid = index;
        vtid->vpid = OS_INVALID_VALUE;
        vtid->cb = (UINTPTR)g_defaultTaskCB;
        LOS_ListTailInsert(&newPidContainer->tidFreeList, &vtid->node);
    }

    newPidContainer->parent = parent;
    if (parent != NULL) {
        LOS_AtomicSet(&newPidContainer->level, parent->level + 1);
        newPidContainer->referenced = FALSE;
    } else {
        LOS_AtomicSet(&newPidContainer->level, 0);
        newPidContainer->referenced = TRUE;
    }
    return newPidContainer;
}

VOID OsPidContainerDestroy(LosProcessCB *curr)
{
    if (curr->container == NULL) {
        return;
    }

    PidContainer *pidContainer = curr->container->pidContainer;
    if (pidContainer == NULL) {
        return;
    }

    FreeVpid(curr);

    if (pidContainer != curr->container->pidForChildContainer) {
        LOS_AtomicDec(&curr->container->pidForChildContainer->rc);
        if (LOS_AtomicRead(&curr->container->pidForChildContainer->rc) <= 0) {
            g_currentPidContainerNum--;
            (VOID)LOS_MemFree(m_aucSysMem1, curr->container->pidForChildContainer);
            curr->container->pidForChildContainer = NULL;
        }
    }

    if (LOS_AtomicRead(&pidContainer->rc) <= 0) {
        g_currentPidContainerNum--;
        curr->container->pidContainer = NULL;
        curr->container->pidForChildContainer = NULL;
        (VOID)LOS_MemFree(m_aucSysMem1, pidContainer);
    }

    OsContainerFree(curr);
}

STATIC UINT32 CreatePidContainer(LosProcessCB *child, LosProcessCB *parent)
{
    UINT32 ret;
    UINT32 intSave;

    PidContainer *parentContainer = parent->container->pidContainer;
    PidContainer *newPidContainer = CreateNewPidContainer(parentContainer);
    if (newPidContainer == NULL) {
        return ENOMEM;
    }

    SCHEDULER_LOCK(intSave);
    if ((parentContainer->level + 1) >= PID_CONTAINER_LEVEL_LIMIT) {
        (VOID)LOS_MemFree(m_aucSysMem1, newPidContainer);
        SCHEDULER_UNLOCK(intSave);
        return EINVAL;
    }

    g_currentPidContainerNum++;
    newPidContainer->referenced = TRUE;
    child->container->pidContainer = newPidContainer;
    child->container->pidForChildContainer = newPidContainer;
    ret = OsAllocSpecifiedVpidUnsafe(OS_USER_ROOT_PROCESS_ID, child, parent);
    if (ret == OS_INVALID_VALUE) {
        g_currentPidContainerNum--;
        FreeVpid(child);
        child->container->pidContainer = NULL;
        child->container->pidForChildContainer = NULL;
        SCHEDULER_UNLOCK(intSave);
        (VOID)LOS_MemFree(m_aucSysMem1, newPidContainer);
        return ENOSPC;
    }
    SCHEDULER_UNLOCK(intSave);
    return LOS_OK;
}

STATIC UINT32 UnshareCreatePidContainer(LosProcessCB *child, LosProcessCB *parent)
{
    UINT32 ret;

    parent->container->pidForChildContainer->referenced = TRUE;
    child->container->pidContainer = parent->container->pidForChildContainer;
    child->container->pidForChildContainer = parent->container->pidForChildContainer;
    ret = OsAllocSpecifiedVpidUnsafe(OS_USER_ROOT_PROCESS_ID, child, parent);
    if (ret == OS_INVALID_VALUE) {
        FreeVpid(child);
        child->container->pidContainer = NULL;
        child->container->pidForChildContainer = NULL;
        return ENOSPC;
    }
    return LOS_OK;
}

UINT32 OsCopyPidContainer(UINTPTR flags, LosProcessCB *child, LosProcessCB *parent, UINT32 *processID)
{
    UINT32 ret;
    UINT32 intSave;

    SCHEDULER_LOCK(intSave);
    PidContainer *parentPidContainer = parent->container->pidContainer;
    PidContainer *parentPidContainerForChild = parent->container->pidForChildContainer;
    if ((parentPidContainer == parentPidContainerForChild) || (parentPidContainerForChild->referenced == TRUE)) {
        /* The current process is not executing unshare */
        if (!(flags & CLONE_NEWPID)) {
            child->container->pidContainer = parentPidContainer;
            child->container->pidForChildContainer = parentPidContainer;
            ret = OsAllocVpid(child);
            SCHEDULER_UNLOCK(intSave);
            if (ret == OS_INVALID_VALUE) {
                PRINT_ERR("[%s] alloc vpid failed\n", __FUNCTION__);
                return ENOSPC;
            }
            *processID = child->processID;
            return LOS_OK;
        }
        SCHEDULER_UNLOCK(intSave);

        ret = CreatePidContainer(child, parent);
        if (ret != LOS_OK) {
            return ret;
        }
    } else {
        /* Create the first process after unshare */
        ret = UnshareCreatePidContainer(child, parent);
        SCHEDULER_UNLOCK(intSave);
        if (ret != LOS_OK) {
            return ret;
        }
    }

    PidContainer *pidContainer = child->container->pidContainer;
    if (pidContainer->pidArray[child->processID].vpid == OS_INVALID_VALUE) {
        *processID = child->processID;
    } else {
        *processID = pidContainer->pidArray[child->processID].vpid;
    }
    return LOS_OK;
}

VOID UnshareDeInitPidContainer(Container *container)
{
    UINT32 intSave;
    PidContainer *pidForChildContainer = NULL;
    if (container == NULL) {
        return;
    }

    SCHEDULER_LOCK(intSave);
    if ((container->pidForChildContainer != NULL) && (container->pidContainer != container->pidForChildContainer)) {
        LOS_AtomicDec(&container->pidForChildContainer->rc);
        if (LOS_AtomicRead(&container->pidForChildContainer->rc) <= 0) {
            g_currentPidContainerNum--;
            pidForChildContainer = container->pidForChildContainer;
            container->pidForChildContainer = NULL;
            container->pidContainer = NULL;
        }
    }
    SCHEDULER_UNLOCK(intSave);
    (VOID)LOS_MemFree(m_aucSysMem1, pidForChildContainer);
}

UINT32 OsUnsharePidContainer(UINTPTR flags, LosProcessCB *curr, Container *newContainer)
{
    UINT32 intSave;
    if (!(flags & CLONE_NEWPID)) {
        SCHEDULER_LOCK(intSave);
        newContainer->pidContainer = curr->container->pidContainer;
        newContainer->pidForChildContainer = curr->container->pidForChildContainer;
        if (newContainer->pidContainer != newContainer->pidForChildContainer) {
            LOS_AtomicInc(&newContainer->pidForChildContainer->rc);
        }
        SCHEDULER_UNLOCK(intSave);
        return LOS_OK;
    }

    PidContainer *pidForChild = CreateNewPidContainer(curr->container->pidContainer);
    if (pidForChild == NULL) {
        return ENOMEM;
    }

    SCHEDULER_LOCK(intSave);
    if (curr->container->pidContainer != curr->container->pidForChildContainer) {
        SCHEDULER_UNLOCK(intSave);
        (VOID)LOS_MemFree(m_aucSysMem1, pidForChild);
        return EINVAL;
    }

    if (pidForChild->level >= PID_CONTAINER_LEVEL_LIMIT) {
        SCHEDULER_UNLOCK(intSave);
        (VOID)LOS_MemFree(m_aucSysMem1, pidForChild);
        return EINVAL;
    }

    g_currentPidContainerNum++;
    newContainer->pidContainer = curr->container->pidContainer;
    newContainer->pidForChildContainer = pidForChild;
    LOS_AtomicSet(&pidForChild->rc, 1);
    SCHEDULER_UNLOCK(intSave);
    return LOS_OK;
}

UINT32 OsInitRootPidContainer(PidContainer **pidContainer)
{
    UINT32 intSave;
    g_defaultTaskCB = OsGetDefaultTaskCB();
    g_defaultProcessCB = OsGetDefaultProcessCB();

    PidContainer *newPidContainer = CreateNewPidContainer(NULL);
    if (newPidContainer == NULL) {
        return ENOMEM;
    }

    SCHEDULER_LOCK(intSave);
    g_currentPidContainerNum++;
    *pidContainer = newPidContainer;
    SCHEDULER_UNLOCK(intSave);
    return LOS_OK;
}

UINT32 OsGetVpidFromCurrContainer(const LosProcessCB *processCB)
{
    UINT32 vpid = processCB->processID;
    PidContainer *pidContainer = processCB->container->pidContainer;
    PidContainer *currPidContainer = OsCurrTaskGet()->pidContainer;
    while (pidContainer != NULL) {
        ProcessVid *vid = &pidContainer->pidArray[vpid];
        if (currPidContainer != pidContainer) {
            vpid = vid->vpid;
            pidContainer = pidContainer->parent;
            continue;
        }

        return vid->vid;
    }
    return OS_INVALID_VALUE;
}

UINT32 OsGetVpidFromRootContainer(const LosProcessCB *processCB)
{
    UINT32 vpid = processCB->processID;
    PidContainer *pidContainer = processCB->container->pidContainer;
    while (pidContainer != NULL) {
        ProcessVid *vid = &pidContainer->pidArray[vpid];
        if (pidContainer->parent != NULL) {
            vpid = vid->vpid;
            pidContainer = pidContainer->parent;
            continue;
        }

        return vid->vid;
    }
    return OS_INVALID_VALUE;
}

UINT32 OsGetVtidFromCurrContainer(const LosTaskCB *taskCB)
{
    UINT32 vtid = taskCB->taskID;
    PidContainer *pidContainer = taskCB->pidContainer;
    PidContainer *currPidContainer = OsCurrTaskGet()->pidContainer;
    while (pidContainer != NULL) {
        ProcessVid *vid = &pidContainer->tidArray[vtid];
        if (currPidContainer != pidContainer) {
            vtid = vid->vpid;
            pidContainer = pidContainer->parent;
            continue;
        }
        return vid->vid;
    }
    return OS_INVALID_VALUE;
}

LosProcessCB *OsGetPCBFromVpid(UINT32 vpid)
{
    PidContainer *pidContainer = OsCurrTaskGet()->pidContainer;
    ProcessVid *processVid = &pidContainer->pidArray[vpid];
    return (LosProcessCB *)processVid->cb;
}

LosTaskCB *OsGetTCBFromVtid(UINT32 vtid)
{
    PidContainer *pidContainer = OsCurrTaskGet()->pidContainer;
    ProcessVid *taskVid = &pidContainer->tidArray[vtid];
    return (LosTaskCB *)taskVid->cb;
}

UINT32 OsGetPidContainerID(PidContainer *pidContainer)
{
    if (pidContainer == NULL) {
        return OS_INVALID_VALUE;
    }

    return pidContainer->containerID;
}
#endif
