#include "android_common.h"

// For 'system', 'product' (optional), 'vendor' (mandatory) and/or 'odm' (optional).
#define MAX_FILE_CONTEXT_SIZE 4

#ifdef __ANDROID_VNDK__
#ifndef LOG_EVENT_STRING
#define LOG_EVENT_STRING(...)
#endif  // LOG_EVENT_STRING
#endif  // __ANDROID_VNDK__

static const struct selinux_opt seopts_service_plat[] = {
    { SELABEL_OPT_PATH, "/system/etc/selinux/plat_service_contexts" },
    { SELABEL_OPT_PATH, "/plat_service_contexts" }
};
static const struct selinux_opt seopts_service_product[] = {
    { SELABEL_OPT_PATH, "/product/etc/selinux/product_service_contexts" },
    { SELABEL_OPT_PATH, "/product_service_contexts" }
};
static const struct selinux_opt seopts_service_vendor[] = {
    { SELABEL_OPT_PATH, "/vendor/etc/selinux/vendor_service_contexts" },
    { SELABEL_OPT_PATH, "/vendor_service_contexts" },
    // TODO: remove nonplat* when no need to retain backward compatibility.
    { SELABEL_OPT_PATH, "/vendor/etc/selinux/nonplat_service_contexts" },
    { SELABEL_OPT_PATH, "/nonplat_service_contexts" }
};

static const struct selinux_opt seopts_hwservice_plat[] = {
    { SELABEL_OPT_PATH, "/system/etc/selinux/plat_hwservice_contexts" },
    { SELABEL_OPT_PATH, "/plat_hwservice_contexts" }
};
static const struct selinux_opt seopts_hwservice_product[] = {
    { SELABEL_OPT_PATH, "/product/etc/selinux/product_hwservice_contexts" },
    { SELABEL_OPT_PATH, "/product_hwservice_contexts" }
};
static const struct selinux_opt seopts_hwservice_vendor[] = {
    { SELABEL_OPT_PATH, "/vendor/etc/selinux/vendor_hwservice_contexts" },
    { SELABEL_OPT_PATH, "/vendor_hwservice_contexts" },
    // TODO: remove nonplat* when no need to retain backward compatibility.
    { SELABEL_OPT_PATH, "/vendor/etc/selinux/nonplat_hwservice_contexts" },
    { SELABEL_OPT_PATH, "/nonplat_hwservice_contexts" }
};
static const struct selinux_opt seopts_hwservice_odm[] = {
    { SELABEL_OPT_PATH, "/odm/etc/selinux/odm_hwservice_contexts" },
    { SELABEL_OPT_PATH, "/odm_hwservice_contexts" }
};

static const struct selinux_opt seopts_vndservice =
    { SELABEL_OPT_PATH, "/vendor/etc/selinux/vndservice_contexts" };

static const struct selinux_opt seopts_vndservice_rootfs =
    { SELABEL_OPT_PATH, "/vndservice_contexts" };

struct selabel_handle* selinux_android_service_open_context_handle(const struct selinux_opt* seopts_service,
                                                                   unsigned nopts)
{
    struct selabel_handle* sehandle;

    sehandle = selabel_open(SELABEL_CTX_ANDROID_SERVICE,
            seopts_service, nopts);

    if (!sehandle) {
        selinux_log(SELINUX_ERROR, "%s: Error getting service context handle (%s)\n",
                __FUNCTION__, strerror(errno));
        return NULL;
    }
    selinux_log(SELINUX_INFO, "SELinux: Loaded service_contexts from:\n");
    for (unsigned i = 0; i < nopts; i++) {
        selinux_log(SELINUX_INFO, "    %s\n", seopts_service[i].value);
    }
    return sehandle;
}

struct selabel_handle* selinux_android_service_context_handle(void)
{
    struct selinux_opt seopts_service[MAX_FILE_CONTEXT_SIZE];
    int size = 0;
    unsigned int i;
    for (i = 0; i < ARRAY_SIZE(seopts_service_plat); i++) {
        if (access(seopts_service_plat[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_service_plat[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_service_product); i++) {
        if (access(seopts_service_product[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_service_product[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_service_vendor); i++) {
        if (access(seopts_service_vendor[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_service_vendor[i];
            break;
        }
    }

    return selinux_android_service_open_context_handle(seopts_service, size);
}

struct selabel_handle* selinux_android_hw_service_context_handle(void)
{
    struct selinux_opt seopts_service[MAX_FILE_CONTEXT_SIZE];
    int size = 0;
    unsigned int i;
    for (i = 0; i < ARRAY_SIZE(seopts_hwservice_plat); i++) {
        if (access(seopts_hwservice_plat[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_hwservice_plat[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_hwservice_product); i++) {
        if (access(seopts_hwservice_product[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_hwservice_product[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_hwservice_vendor); i++) {
        if (access(seopts_hwservice_vendor[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_hwservice_vendor[i];
            break;
        }
    }
    for (i = 0; i < ARRAY_SIZE(seopts_hwservice_odm); i++) {
        if (access(seopts_hwservice_odm[i].value, R_OK) != -1) {
            seopts_service[size++] = seopts_hwservice_odm[i];
            break;
        }
    }
    return selinux_android_service_open_context_handle(seopts_service, size);
}

struct selabel_handle* selinux_android_vendor_service_context_handle(void)
{
    const struct selinux_opt* seopts_service;
    if (access(seopts_vndservice.value, R_OK) != -1) {
        seopts_service = &seopts_vndservice;
    } else {
        seopts_service = &seopts_vndservice_rootfs;
    }

    return selinux_android_service_open_context_handle(seopts_service, 1);
}

int selinux_log_callback(int type, const char *fmt, ...)
{
    va_list ap;
    int priority;
    char *strp;

    switch(type) {
    case SELINUX_WARNING:
        priority = ANDROID_LOG_WARN;
        break;
    case SELINUX_INFO:
        priority = ANDROID_LOG_INFO;
        break;
    default:
        priority = ANDROID_LOG_ERROR;
        break;
    }

    va_start(ap, fmt);
    if (vasprintf(&strp, fmt, ap) != -1) {
        LOG_PRI(priority, "SELinux", "%s", strp);
        LOG_EVENT_STRING(AUDITD_LOG_TAG, strp);
        free(strp);
    }
    va_end(ap);
    return 0;
}

int selinux_vendor_log_callback(int type, const char *fmt, ...)
{
    va_list ap;
    int priority;
    char *strp;

    switch(type) {
    case SELINUX_WARNING:
        priority = ANDROID_LOG_WARN;
        break;
    case SELINUX_INFO:
        priority = ANDROID_LOG_INFO;
        break;
    default:
        priority = ANDROID_LOG_ERROR;
        break;
    }

    va_start(ap, fmt);
    if (vasprintf(&strp, fmt, ap) != -1) {
        LOG_PRI(priority, "SELinux", "%s", strp);
        free(strp);
    }
    va_end(ap);
    return 0;
}
