/*
 * Copyright (c) 2013      Mellanox Technologies, Inc.
 *                         All rights reserved.
 * Copyright (c) 2016      IBM Corporation.  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */
#include "oshmem_config.h"

#include "oshmem/util/oshmem_util.h"
#include "oshmem/proc/proc.h"
#include "oshmem/mca/memheap/memheap.h"
#include "oshmem/mca/memheap/base/base.h"
#include "oshmem/mca/sshmem/base/base.h"
#include "oshmem/util/oshmem_util.h"

#include <stdio.h>
#include <limits.h>
#include <stdlib.h>
#include <pthread.h>

static int _check_perms(const char *perm);
static int _check_address(void *start, void **end);
static int _check_pathname(uint64_t inode, const char *pathname);

int mca_memheap_base_static_init(mca_memheap_map_t *map)
{
    /* read and parse segments from /proc/self/maps */
    int ret = OSHMEM_SUCCESS;
    uint64_t total_mem = 0;
    void* start;
    void* end;
    char perms[8];
    uint64_t offset;
    char dev[8];
    uint64_t inode;
    char pathname[MAXPATHLEN];
    FILE *fp;
    char line[1024];
    map_segment_t *s;

    assert(map);
    assert(HEAP_SEG_INDEX < map->n_segments);

    /* FIXME!!! Linux specific code */
    fp = fopen("/proc/self/maps", "r");
    if (NULL == fp) {
        MEMHEAP_ERROR("Failed to open /proc/self/maps");
        return OSHMEM_ERROR;
    }

#ifdef __linux__
    extern unsigned _end;
    if (mca_sshmem_base_start_address < (uintptr_t)&_end) {
        MEMHEAP_WARN("sshmem base start address is inside data region"
                     " (%p < %p)", mca_sshmem_base_start_address, &_end);
    }
#endif

    while (NULL != fgets(line, sizeof(line), fp)) {
        if (3 > sscanf(line,
               "%llx-%llx %s %llx %s %llx %s",
               (unsigned long long *) &start,
               (unsigned long long *) &end,
               perms,
               (unsigned long long *) &offset,
               dev,
               (unsigned long long *) &inode,
               pathname)) {
            MEMHEAP_ERROR("Failed to sscanf /proc/self/maps output %s", line);
            ret = OSHMEM_ERROR;
            goto out;
        }

        if (OSHMEM_ERROR == _check_address(start, &end))
            continue;

        if (OSHMEM_ERROR == _check_pathname(inode, pathname))
            continue;

        if (OSHMEM_ERROR == _check_perms(perms))
            continue;

        MEMHEAP_VERBOSE(5, "add: %s", line);

        if ((map->n_segments > 0) &&
            (start == map->mem_segs[map->n_segments - 1].super.va_end)) {
            s = &map->mem_segs[map->n_segments - 1];
            MEMHEAP_VERBOSE(5, "Coalescing segment");
            s->super.va_end = end;
            s->seg_size = ((uintptr_t)s->super.va_end - (uintptr_t)s->super.va_base);
            continue;
        }

        s = mca_memheap_base_allocate_segment(map);
        if (NULL == s) {
            MEMHEAP_ERROR("failed to allocate segment");
            ret = OSHMEM_ERR_OUT_OF_RESOURCE;
            goto out;
        }

        memset(s, 0, sizeof(*s));
        MAP_SEGMENT_RESET_FLAGS(s);
        s->seg_id        = MAP_SEGMENT_SHM_INVALID;
        s->super.va_base = start;
        s->super.va_end  = end;
        s->seg_size      = ((uintptr_t)s->super.va_end - (uintptr_t)s->super.va_base);
        s->type          = MAP_SEGMENT_STATIC;
        map->n_segments++;

        total_mem += ((uintptr_t)s->super.va_end - (uintptr_t)s->super.va_base);
    }

    MEMHEAP_VERBOSE(1,
                    "Memheap static memory: %llu byte(s), %d segments",
                    total_mem, map->n_segments);

out:
    fclose(fp);
    return ret;
}

void mca_memheap_base_static_exit(mca_memheap_map_t *map)
{
    assert(map);
}

static int _check_perms(const char *perms)
{
    if (!strcmp(perms, "rw-p") || !strcmp(perms, "rwxp"))
        return OSHMEM_SUCCESS;

    return OSHMEM_ERROR;
}

static int _check_address(void *start, void **end)
{
    /* FIXME Linux specific code */
#ifdef __linux__
    extern unsigned _end;
    uintptr_t data_end = (uintptr_t)&_end;

    /**
     * SGI shmem only supports globals&static in main program.
     * It does not support them in shared objects or in dlopen()
     * (Clarified on PGAS 2011 tutorial)
     *
     * So ignored any maps that start higher then process _end
     * FIXME: make sure we do not register symmetric heap twice
     * if we decide to allow shared objects
     */
    if ((uintptr_t)start > data_end) {
        MEMHEAP_VERBOSE(100,
                        "skip segment: data _end < segment start (%p < %p)",
                        data_end, start);
        return OSHMEM_ERROR;
    }

    if ((uintptr_t)*end > data_end) {
        MEMHEAP_VERBOSE(100,
                        "adjust segment: data _end < segment end (%p < %p",
                        data_end, *end);
         *end = (void*)data_end;
    }
#endif
    return OSHMEM_SUCCESS;
}

static int _check_pathname(uint64_t inode, const char *pathname)
{
    static const char *proc_self_exe = "/proc/self/exe";
    static int warned = 0;
    char exe_path[PATH_MAX];
    char module_path[PATH_MAX];
    char *path;

    if (0 == inode) {
        /* segment is not mapped to file, allow sharing it */
        return OSHMEM_SUCCESS;
    }

    path = realpath(proc_self_exe, exe_path);
    if (NULL == path) {
        if (0 == warned) {
            MEMHEAP_VERBOSE(100, "failed to read link %s: %m", proc_self_exe);
            MEMHEAP_VERBOSE(100, "all segments will be registered");
            warned = 1;
        }

        return OSHMEM_SUCCESS;
    }

    /* for file-mapped segments allow segments from start process only */
    path = realpath(pathname, module_path);
    if (NULL == path) {
        return OSHMEM_ERROR;
    }

    if (!strncmp(exe_path, module_path, sizeof(exe_path))) {
        return OSHMEM_SUCCESS;
    }

    return OSHMEM_ERROR;

    /* Probably we need more accurate path check
     * To press check coverity issue following code is disabled
     */
#if 0
    char *p;
    if ('\0' == seg->pathname[0])
    return OSHMEM_SUCCESS;

    if (0 == strncmp(seg->pathname, "/lib", 4))
    return OSHMEM_ERROR;

    if (0 == strncmp(seg->pathname, "/usr/lib", 8))
    return OSHMEM_ERROR;

    if (0 == strncmp(seg->pathname, "/dev", 4))
    return OSHMEM_ERROR;

    if (0 == strcmp(seg->pathname, "[stack]"))
    return OSHMEM_ERROR;

    if (0 == strcmp(seg->pathname, "[vdso]"))
    return OSHMEM_ERROR;

    if (0 == strcmp(seg->pathname, "[vsyscall]"))
    return OSHMEM_ERROR;

    p = rindex(seg->pathname, '/');
    if (p) {
        if (0 == strncmp(p+1, "libshmem.so", 11))
        return OSHMEM_ERROR;

        if (0 == strncmp(p+1, "lib" OMPI_LIBMPI_NAME ".so", 9))
        return OSHMEM_ERROR;

        if (0 == strncmp(p+1, "libmca_common_sm.so", 19))
        return OSHMEM_ERROR;
    }
#endif
    return OSHMEM_SUCCESS;
}

