/* -*- c -*-
 *
 * Copyright (c) 2004-2006 The Trustees of Indiana University.
 *                         All rights reserved.
 * Copyright (c) 2004-2005 The Regents of the University of California.
 *                         All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include <errno.h>
#include <sys/syscall.h>
#include <unistd.h>
#include <plpa.h>
#include <plpa_internal.h>

/*
 * Call the kernel's setaffinity, massaging the user's input
 * parameters as necessary
 */
int PLPA_NAME(sched_setaffinity)(pid_t pid, size_t cpusetsize,
                                 const PLPA_NAME(cpu_set_t) *cpuset)
{
    int ret;
    size_t i;
    PLPA_NAME(cpu_set_t) tmp;

    switch (PLPA_NAME(api_probe)()) {
    case PLPA_PROBE_OK:
        /* This shouldn't happen, but check anyway */
        if (cpusetsize > sizeof(*cpuset)) {
            return EINVAL;
        }
        if (NULL == cpuset) {
            return EINVAL;
        }

        /* If the user-supplied bitmask is smaller than what the
           kernel wants, zero out a temporary buffer of the size that
           the kernel wants and copy the user-supplied bitmask to the
           lower part of the temporary buffer.  This could be done
           more efficiently, but we're looking for clarity/simplicity
           of code here -- this is not intended to be
           performance-critical. */
        if (cpusetsize < PLPA_NAME(len)) {
            memset(&tmp, 0, sizeof(tmp));
            for (i = 0; i < cpusetsize * 8; ++i) {
                if (PLPA_CPU_ISSET(i, cpuset)) {
                    PLPA_CPU_SET(i, &tmp);
                }
            }
        }

        /* If the user-supplied bitmask is larger than what the kernel
           will accept, scan it and see if there are any set bits in
           the part larger than what the kernel will accept.  If so,
           return EINVAL.  Otherwise, copy the part that the kernel
           will accept into a temporary and use that.  Again,
           efficinency is not the issue of this code -- clarity is. */
        else if (cpusetsize > PLPA_NAME(len)) {
            for (i = PLPA_NAME(len) * 8; i < cpusetsize * 8; ++i) {
                if (PLPA_CPU_ISSET(i, cpuset)) {
                    return EINVAL;
                }
            }
            /* No upper-level bits are set, so now copy over the bits
               that the kernel will look at */
            memset(&tmp, 0, sizeof(tmp));
            for (i = 0; i < PLPA_NAME(len) * 8; ++i) {
                if (PLPA_CPU_ISSET(i, cpuset)) {
                    PLPA_CPU_SET(i, &tmp);
                }
            }
        }

        /* Otherwise, the user supplied a buffer that is exactly the
           right size.  Just for clarity of code, copy the user's
           buffer into the temporary and use that. */
        else {
            memcpy(&tmp, cpuset, cpusetsize);
        }

        /* Now do the syscall */
        ret = syscall(__NR_sched_setaffinity, 0, PLPA_NAME(len), &tmp);

        /* Return 0 upon success.  According to
           http://www.open-mpi.org/community/lists/plpa-users/2006/02/0016.php,
           all the kernel implementations return >= 0 upon success. */
        if (ret >= 0) {
            return 0;
        } else {
            return ret;
        }
        break;

    case PLPA_PROBE_NOT_SUPPORTED:
        /* Process affinity not supported here */
        return ENOSYS;
        break;

    default:
        /* Something went wrong */
        /* JMS: would be good to have something other than EINVAL here
           -- suggestions? */
        return EINVAL;
        break;
    }
}


/*
 * Call the kernel's getaffinity, massaging the user's input
 * parameters as necessary
 */
int PLPA_NAME(sched_getaffinity)(pid_t pid, size_t cpusetsize,
                                PLPA_NAME(cpu_set_t) *cpuset)
{
    int ret;

    switch (PLPA_NAME(api_probe)()) {
    case PLPA_PROBE_OK:
        /* This shouldn't happen, but check anyway */
        if (PLPA_NAME(len) > sizeof(*cpuset)) {
            return EINVAL;
        }
        if (NULL == cpuset) {
            return EINVAL;
        }

        /* If the user supplied a buffer that is too small, then don't
           even bother */
        if (cpusetsize < PLPA_NAME(len)) {
            return EINVAL;
        }

        /* Now we know that the user's buffer is >= the size required
           by the kernel.  If it's >, then zero it out so that the
           bits at the top are cleared (since they won't be set by the
           kernel) */
        if (cpusetsize > PLPA_NAME(len)) {
            memset(cpuset, 0, sizeof(*cpuset));
        }

        /* Now do the syscall */
        ret = syscall(__NR_sched_getaffinity, 0, PLPA_NAME(len), cpuset);

        /* Return 0 upon success.  According to
           http://www.open-mpi.org/community/lists/plpa-users/2006/02/0016.php,
           all the kernel implementations return >= 0 upon success. */
        if (ret >= 0) {
            return 0;
        } else {
            return ret;
        }
        break;

    case PLPA_PROBE_NOT_SUPPORTED:
        /* Process affinity not supported here */
        return ENOSYS;
        break;

    default:
        /* Something went wrong */
        return EINVAL;
        break;
    }
}

