/*
 * 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.
 */

#ifdef LOSCFG_KERNEL_DEV_PLIMIT
#include "los_seq_buf.h"
#include "los_bitmap.h"
#include "los_process_pri.h"
#include "los_devicelimit.h"

#define TYPE_CHAR_LEN            (1)
#define DEVICE_NAME_PREFIX_SPACE (1)
#define DEVICE_ACCESS_MAXLEN     (3)
#define BUF_SEPARATOR            (5)

STATIC ProcDevLimit *g_procDevLimit = NULL;

VOID OsDevLimitInit(UINTPTR limit)
{
    ProcDevLimit *deviceLimit = (ProcDevLimit *)limit;
    deviceLimit->behavior = DEVLIMIT_DEFAULT_ALLOW;
    LOS_ListInit(&(deviceLimit->accessList));
    g_procDevLimit = deviceLimit;
}

VOID *OsDevLimitAlloc(VOID)
{
    ProcDevLimit *plimit = (ProcDevLimit *)LOS_MemAlloc(m_aucSysMem1, sizeof(ProcDevLimit));
    if (plimit == NULL) {
        return NULL;
    }
    (VOID)memset_s(plimit, sizeof(ProcDevLimit), 0, sizeof(ProcDevLimit));
    LOS_ListInit(&(plimit->accessList));
    plimit->behavior = DEVLIMIT_DEFAULT_NONE;
    return (VOID *)plimit;
}

STATIC VOID DevAccessListDelete(ProcDevLimit *devLimit)
{
    DevAccessItem *delItem = NULL;
    DevAccessItem *tmpItem = NULL;
    LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(delItem, tmpItem, &devLimit->accessList, DevAccessItem, list) {
        LOS_ListDelete(&delItem->list);
        (VOID)LOS_MemFree(m_aucSysMem1, (VOID *)delItem);
    }
}

VOID OsDevLimitFree(UINTPTR limit)
{
    ProcDevLimit *devLimit = (ProcDevLimit *)limit;
    if (devLimit == NULL) {
        return;
    }

    DevAccessListDelete(devLimit);
    (VOID)LOS_MemFree(m_aucSysMem1, devLimit);
}

STATIC UINT32 DevLimitCopyAccess(ProcDevLimit *devLimitDest, ProcDevLimit *devLimitSrc)
{
    DevAccessItem *tmpItem = NULL;
    INT32 itemSize = sizeof(DevAccessItem);
    devLimitDest->behavior = devLimitSrc->behavior;
    LOS_DL_LIST_FOR_EACH_ENTRY(tmpItem, &devLimitSrc->accessList, DevAccessItem, list) {
        DevAccessItem *newItem = (DevAccessItem *)LOS_MemAlloc(m_aucSysMem1, itemSize);
        if (newItem == NULL) {
            return ENOMEM;
        }
        (VOID)memcpy_s(newItem, sizeof(DevAccessItem), tmpItem, sizeof(DevAccessItem));
        LOS_ListTailInsert(&devLimitDest->accessList, &newItem->list);
    }
    return LOS_OK;
}

VOID OsDevLimitCopy(UINTPTR dest, UINTPTR src)
{
    ProcDevLimit *devLimitDest = (ProcDevLimit *)dest;
    ProcDevLimit *devLimitSrc = (ProcDevLimit *)src;
    (VOID)DevLimitCopyAccess(devLimitDest, devLimitSrc);
    devLimitDest->parent = (ProcDevLimit *)src;
}

STATIC INLINE INT32 IsSpace(INT32 c)
{
    return (c == ' ' || (unsigned)c - '\t' < BUF_SEPARATOR);
}

STATIC UINT32 ParseItemAccess(const CHAR *buf, DevAccessItem *item)
{
    switch (*buf) {
        case 'a':
            item->type = DEVLIMIT_DEV_ALL;
            return LOS_OK;
        case 'b':
            item->type = DEVLIMIT_DEV_BLOCK;
            break;
        case 'c':
            item->type = DEVLIMIT_DEV_CHAR;
            break;
        default:
            return EINVAL;
    }
    buf += DEVICE_NAME_PREFIX_SPACE;
    if (!IsSpace(*buf)) {
        return EINVAL;
    }
    buf += DEVICE_NAME_PREFIX_SPACE;

    for (INT32 count = 0; count < sizeof(item->name) - 1; count++) {
        if (IsSpace(*buf)) {
            break;
        }
        item->name[count] = *buf;
        buf += TYPE_CHAR_LEN;
    }
    if (!IsSpace(*buf)) {
        return EINVAL;
    }

    buf += DEVICE_NAME_PREFIX_SPACE;
    for (INT32 i = 0; i < DEVICE_ACCESS_MAXLEN; i++) {
        switch (*buf) {
            case 'r':
                item->access |= DEVLIMIT_ACC_READ;
                break;
            case 'w':
                item->access |= DEVLIMIT_ACC_WRITE;
                break;
            case 'm':
                item->access |= DEVLIMIT_ACC_MKNOD;
                break;
            case '\n':
            case '\0':
                i = DEVICE_ACCESS_MAXLEN;
                break;
            default:
                return EINVAL;
        }
        buf += TYPE_CHAR_LEN;
    }
    return LOS_OK;
}

STATIC BOOL DevLimitMayAllowAll(ProcDevLimit *parent)
{
    if (parent == NULL) {
        return TRUE;
    }
    return (parent->behavior == DEVLIMIT_DEFAULT_ALLOW);
}

STATIC BOOL DevLimitHasChildren(ProcLimitSet *procLimitSet, ProcDevLimit *devLimit)
{
    ProcLimitSet *parent = procLimitSet;
    ProcLimitSet *childProcLimitSet = NULL;
    if (devLimit == NULL) {
        return FALSE;
    }

    LOS_DL_LIST_FOR_EACH_ENTRY(childProcLimitSet, &(procLimitSet->childList), ProcLimitSet, childList) {
        if (childProcLimitSet == NULL) {
            continue;
        }
        if (childProcLimitSet->parent != parent) {
            continue;
        }
        if (!((childProcLimitSet->mask) & BIT(PROCESS_LIMITER_ID_DEV))) {
            continue;
        }
        return TRUE;
    }
    return FALSE;
}

STATIC UINT32 DealItemAllAccess(ProcLimitSet *procLimitSet, ProcDevLimit *devLimit,
                                ProcDevLimit *devParentLimit, INT32 filetype)
{
    switch (filetype) {
        case DEVLIMIT_ALLOW: {
            if (DevLimitHasChildren(procLimitSet, devLimit)) {
                return EINVAL;
            }
            if (!DevLimitMayAllowAll(devParentLimit)) {
                return EPERM;
            }
            DevAccessListDelete(devLimit);
            devLimit->behavior = DEVLIMIT_DEFAULT_ALLOW;
            if (devParentLimit == NULL) {
                break;
            }
            DevLimitCopyAccess(devLimit, devParentLimit);
            break;
        }
        case DEVLIMIT_DENY: {
            if (DevLimitHasChildren(procLimitSet, devLimit)) {
                return EINVAL;
            }
            DevAccessListDelete(devLimit);
            devLimit->behavior = DEVLIMIT_DEFAULT_DENY;
            break;
        }
        default:
            return EINVAL;
    }
    return LOS_OK;
}

STATIC BOOL DevLimitMatchItemPartial(LOS_DL_LIST *list, DevAccessItem *item)
{
    if ((list == NULL) || (item == NULL)) {
        return FALSE;
    }
    if (LOS_ListEmpty(list)) {
        return FALSE;
    }
    DevAccessItem *walk = NULL;
    LOS_DL_LIST_FOR_EACH_ENTRY(walk, list, DevAccessItem, list) {
        if (item->type != walk->type) {
            continue;
        }
        if ((strcmp(walk->name, "*") != 0) && (strcmp(item->name, "*") != 0)
        && (strcmp(walk->name, item->name) != 0)) {
            continue;
        }
        if (!(item->access & ~(walk->access))) {
            return TRUE;
        }
    }
    return FALSE;
}

STATIC BOOL DevLimitParentAllowsRmItem(ProcDevLimit *devParentLimit, DevAccessItem *item)
{
    if (devParentLimit == NULL) {
        return TRUE;
    }
     /* Make sure you're not removing part or a whole item existing in the parent plimits */
    return !DevLimitMatchItemPartial(&devParentLimit->accessList, item);
}

STATIC BOOL DevLimitMatchItem(LOS_DL_LIST *list, DevAccessItem *item)
{
    if ((list == NULL) || (item == NULL)) {
        return FALSE;
    }
    if (LOS_ListEmpty(list)) {
        return FALSE;
    }
    DevAccessItem *walk = NULL;
    LOS_DL_LIST_FOR_EACH_ENTRY(walk, list, DevAccessItem, list) {
        if (item->type != walk->type) {
            continue;
        }
        if ((strcmp(walk->name, "*") != 0) && (strcmp(walk->name, item->name) != 0)) {
            continue;
        }
        if (!(item->access & ~(walk->access))) {
            return TRUE;
        }
    }
    return FALSE;
}

/**
 * This is used to make sure a child plimits won't have more privileges than its parent
 */
STATIC BOOL DevLimitVerifyNewItem(ProcDevLimit *parent, DevAccessItem *item, INT32 currBehavior)
{
    if (parent == NULL) {
        return TRUE;
    }

    if (parent->behavior == DEVLIMIT_DEFAULT_ALLOW) {
        if (currBehavior == DEVLIMIT_DEFAULT_ALLOW) {
            return TRUE;
        }
        return !DevLimitMatchItemPartial(&parent->accessList, item);
    }
    return DevLimitMatchItem(&parent->accessList, item);
}

STATIC BOOL DevLimitParentAllowsAddItem(ProcDevLimit *devParentLimit, DevAccessItem *item, INT32 currBehavior)
{
    return DevLimitVerifyNewItem(devParentLimit, item, currBehavior);
}

STATIC VOID DevLimitAccessListRm(ProcDevLimit *devLimit, DevAccessItem *item)
{
    if ((item == NULL) || (devLimit == NULL)) {
        return;
    }
    DevAccessItem *walk, *tmp = NULL;
    LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(walk, tmp, &devLimit->accessList, DevAccessItem, list) {
        if (walk->type != item->type) {
            continue;
        }
        if (strcmp(walk->name, item->name) != 0) {
            continue;
        }
        walk->access &= ~item->access;
        if (!walk->access) {
            LOS_ListDelete(&walk->list);
            (VOID)LOS_MemFree(m_aucSysMem1, (VOID *)walk);
        }
    }
}

STATIC UINT32 DevLimitAccessListAdd(ProcDevLimit *devLimit, DevAccessItem *item)
{
    if ((item == NULL) || (devLimit == NULL)) {
        return ENOMEM;
    }

    DevAccessItem *walk = NULL;
    DevAccessItem *newItem = (DevAccessItem *)LOS_MemAlloc(m_aucSysMem1, sizeof(DevAccessItem));
    if (newItem == NULL) {
        return ENOMEM;
    }
    (VOID)memcpy_s(newItem, sizeof(DevAccessItem), item, sizeof(DevAccessItem));
    LOS_DL_LIST_FOR_EACH_ENTRY(walk, &devLimit->accessList, DevAccessItem, list) {
        if (walk->type != item->type) {
            continue;
        }
        if (strcmp(walk->name, item->name) != 0) {
            continue;
        }
        walk->access |= item->access;
        (VOID)LOS_MemFree(m_aucSysMem1, (VOID *)newItem);
        newItem = NULL;
    }

    if (newItem != NULL) {
        LOS_ListTailInsert(&devLimit->accessList, &newItem->list);
    }
    return LOS_OK;
}

/**
 * Revalidate permissions
 */
STATIC VOID DevLimitRevalidateActiveItems(ProcDevLimit *devLimit, ProcDevLimit *devParentLimit)
{
    if ((devLimit == NULL) || (devParentLimit == NULL)) {
        return;
    }
    DevAccessItem *walK = NULL;
    DevAccessItem *tmp = NULL;
    LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(walK, tmp, &devLimit->accessList, DevAccessItem, list) {
        if (!DevLimitParentAllowsAddItem(devParentLimit, walK, devLimit->behavior)) {
            DevLimitAccessListRm(devLimit, walK);
        }
    }
}

/**
 * propagates a new item to the children
 */
STATIC UINT32 DevLimitPropagateItem(ProcLimitSet *procLimitSet, ProcDevLimit *devLimit, DevAccessItem *item)
{
    UINT32 ret = LOS_OK;
    ProcLimitSet *parent = procLimitSet;
    ProcLimitSet *childProcLimitSet = NULL;

    if ((procLimitSet == NULL) || (item == NULL)) {
        return ENOMEM;
    }

    if (devLimit == NULL) {
        return LOS_OK;
    }

    LOS_DL_LIST_FOR_EACH_ENTRY(childProcLimitSet, &procLimitSet->childList, ProcLimitSet, childList) {
        if (childProcLimitSet == NULL) {
            continue;
        }
        if (childProcLimitSet->parent != parent) {
            continue;
        }
        if (!((childProcLimitSet->mask) & BIT(PROCESS_LIMITER_ID_DEV))) {
            continue;
        }
        ProcDevLimit *devLimitChild = (ProcDevLimit *)childProcLimitSet->limitsList[PROCESS_LIMITER_ID_DEV];
        if (devLimit->behavior == DEVLIMIT_DEFAULT_ALLOW &&
            devLimitChild->behavior == DEVLIMIT_DEFAULT_ALLOW) {
            ret = DevLimitAccessListAdd(devLimitChild, item);
        } else {
            DevLimitAccessListRm(devLimitChild, item);
        }
        DevLimitRevalidateActiveItems(devLimitChild, (ProcDevLimit *)parent->limitsList[PROCESS_LIMITER_ID_DEV]);
    }
    return ret;
}

STATIC UINT32 DevLimitUpdateAccess(ProcLimitSet *procLimitSet, const CHAR *buf, INT32 filetype)
{
    UINT32 ret;
    UINT32 intSave;
    DevAccessItem item = {0};

    SCHEDULER_LOCK(intSave);
    ProcDevLimit *devLimit = (ProcDevLimit *)(procLimitSet->limitsList[PROCESS_LIMITER_ID_DEV]);
    ProcDevLimit *devParentLimit = devLimit->parent;

    ret = ParseItemAccess(buf, &item);
    if (ret != LOS_OK) {
        SCHEDULER_UNLOCK(intSave);
        return ret;
    }
    if (item.type == DEVLIMIT_DEV_ALL) {
        ret = DealItemAllAccess(procLimitSet, devLimit, devParentLimit, filetype);
        SCHEDULER_UNLOCK(intSave);
        return ret;
    }
    switch (filetype) {
        case DEVLIMIT_ALLOW: {
            if (devLimit->behavior == DEVLIMIT_DEFAULT_ALLOW) {
                if (!DevLimitParentAllowsRmItem(devParentLimit, &item)) {
                    SCHEDULER_UNLOCK(intSave);
                    return EPERM;
                }
                DevLimitAccessListRm(devLimit, &item);
                break;
            }
            if (!DevLimitParentAllowsAddItem(devParentLimit, &item, devLimit->behavior)) {
                SCHEDULER_UNLOCK(intSave);
                return EPERM;
            }
            ret = DevLimitAccessListAdd(devLimit, &item);
            break;
        }
        case DEVLIMIT_DENY: {
            if (devLimit->behavior == DEVLIMIT_DEFAULT_DENY) {
                DevLimitAccessListRm(devLimit, &item);
            } else {
                ret = DevLimitAccessListAdd(devLimit, &item);
            }
            // update child access list
            ret = DevLimitPropagateItem(procLimitSet, devLimit, &item);
            break;
        }
        default:
            ret = EINVAL;
            break;
    }
    SCHEDULER_UNLOCK(intSave);
    return ret;
}


UINT32 OsDevLimitWriteAllow(ProcLimitSet *plimit, const CHAR *buf, UINT32 size)
{
    (VOID)size;
    return DevLimitUpdateAccess(plimit, buf, DEVLIMIT_ALLOW);
}

UINT32 OsDevLimitWriteDeny(ProcLimitSet *plimit, const CHAR *buf, UINT32 size)
{
    (VOID)size;
    return DevLimitUpdateAccess(plimit, buf, DEVLIMIT_DENY);
}

STATIC VOID DevLimitItemSetAccess(CHAR *acc, INT16 access)
{
    INT32 index = 0;
    (VOID)memset_s(acc, ACCLEN, 0, ACCLEN);
    if (access & DEVLIMIT_ACC_READ) {
        acc[index++] = 'r';
    }
    if (access & DEVLIMIT_ACC_WRITE) {
        acc[index++] = 'w';
    }
    if (access & DEVLIMIT_ACC_MKNOD) {
        acc[index++] = 'm';
    }
}

STATIC CHAR DevLimitItemTypeToChar(INT16 type)
{
    if (type == DEVLIMIT_DEV_ALL) {
        return 'a';
    } else if (type == DEVLIMIT_DEV_CHAR) {
        return 'c';
    } else if (type == DEVLIMIT_DEV_BLOCK) {
        return 'b';
    }
    return 'X';
}

UINT32 OsDevLimitShow(ProcDevLimit *devLimit, struct SeqBuf *seqBuf)
{
    DevAccessItem *item = NULL;
    CHAR acc[ACCLEN];
    UINT32 intSave;

    if ((devLimit == NULL) || (seqBuf == NULL)) {
        return EINVAL;
    }

    SCHEDULER_LOCK(intSave);
    if (devLimit->behavior == DEVLIMIT_DEFAULT_ALLOW) {
        DevLimitItemSetAccess(acc, DEVLIMIT_ACC_MASK);
        SCHEDULER_UNLOCK(intSave);
        LosBufPrintf(seqBuf, "%c %s %s\n", DevLimitItemTypeToChar(DEVLIMIT_DEV_ALL), "*", acc);
        return LOS_OK;
    }
    LOS_DL_LIST_FOR_EACH_ENTRY(item, &devLimit->accessList, DevAccessItem, list) {
        DevLimitItemSetAccess(acc, item->access);
        LosBufPrintf(seqBuf, "%c %s %s\n", DevLimitItemTypeToChar(item->type), item->name, acc);
    }
    SCHEDULER_UNLOCK(intSave);
    return LOS_OK;
}

STATIC INLINE INT16 ConversionDevType(INT32 vnodeType)
{
    INT16 type = 0;
    if (vnodeType == VNODE_TYPE_BLK) {
        type = DEVLIMIT_DEV_BLOCK;
    } else if (vnodeType == VNODE_TYPE_CHR) {
        type = DEVLIMIT_DEV_CHAR;
    }
    return type;
}

STATIC INLINE INT16 ConversionDevAccess(INT32 flags)
{
    INT16 access = 0;
    if ((flags & O_ACCMODE) == O_RDONLY) {
        access |= DEVLIMIT_ACC_READ;
    }
    if (flags & O_WRONLY) {
        access |= DEVLIMIT_ACC_WRITE;
    }
    if (flags & O_RDWR) {
        access |= DEVLIMIT_ACC_WRITE | DEVLIMIT_ACC_READ;
    }
    if (flags & O_CREAT) {
        access |= DEVLIMIT_ACC_MKNOD;
    }
    return access;
}

UINT32 OsDevLimitCheckPermission(INT32 vnodeType, const CHAR *pathName, INT32 flags)
{
    BOOL matched = FALSE;
    DevAccessItem item = {0};
    LosProcessCB *run = OsCurrProcessGet();
    if ((run == NULL) || (run->plimits == NULL)) {
        return LOS_OK;
    }

    if (pathName == NULL) {
        return EINVAL;
    }

    ProcDevLimit *devLimit = (ProcDevLimit *)run->plimits->limitsList[PROCESS_LIMITER_ID_DEV];

    item.type = ConversionDevType(vnodeType);
    item.access = ConversionDevAccess(flags);
    LOS_ListInit(&(item.list));
    (VOID)strncpy_s(item.name, PATH_MAX, pathName, PATH_MAX);

    if (devLimit->behavior == DEVLIMIT_DEFAULT_ALLOW) {
        matched = !DevLimitMatchItemPartial(&devLimit->accessList, &item);
    } else {
        matched = DevLimitMatchItem(&devLimit->accessList, &item);
    }
    if (!matched) {
        return EPERM;
    }
    return LOS_OK;
}
#endif
