The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include <EXTERN.h>
#include <perl.h>
#include <XSUB.h>

#include <sys/param.h>
#include <sys/cpuset.h>
#include <sys/sysctl.h>
#include <sys/types.h>
#include <sched.h>
#include <stdio.h>

int num_cpus_sysctl()
{
  int mib[2], ncpu;
  size_t len;

  mib[0] = CTL_HW;
  mib[1] = HW_NCPU;
  len = sizeof(ncpu);
  sysctl(mib, 2, &ncpu, &len, NULL, 0);
  return ncpu;
}

int getaffinity_freebsd(int pid, AV *mask)
{
  cpulevel_t level = CPU_LEVEL_WHICH;
  cpuwhich_t which = CPU_WHICH_PID;
  id_t id = (id_t) pid;
  size_t setsize;
  cpuset_t cpumask;
  int i, r;

  setsize = sizeof(cpumask);
  r = cpuset_getaffinity(level, which, id, setsize, &cpumask);
  if (r != 0) {
    if (errno == EINVAL) {
      fprintf(stderr, "cpuset_getaffinity: invalid level or which arg\n");
    } else if (errno == EDEADLK) {
      fprintf(stderr, "cpuset_getaffinity: EDEADLK encountered\n");
    } else if (errno == EFAULT) {
      fprintf(stderr, "cpuset_getaffinity: EFAULT - invalid cpu mask\n");
    } else if (errno == ESRCH) {
      fprintf(stderr, "cpuset_getaffinity: ESRCH - invalid pid\n");
    } else if (errno == ERANGE) {
      fprintf(stderr, "cpuset_getaffinity: ERANGE - invalid cpusetsize\n");
    } else if (errno == EPERM) {
      fprintf(stderr, "cpuset_getaffinity: EPERM - "
                      "no permission to get affinity for pid=%d\n", pid);
    } else {
      fprintf(stderr, "cpuset_getaffinity: unknown error %d\n", errno);
    }
    return 0;
  } else {
    int ncpu = num_cpus_sysctl();
    int nset = 0;
    if (ncpu <= 0) {
      fprintf(stderr, "getaffinity_freebsd: "
                      "failed to get num cpus from sysctl\n");
      ncpu = 32;
    }
    for (i = 0; i < ncpu; i++) {
      if (CPU_ISSET(i, &cpumask)) {
        nset++;
        av_push(mask, newSViv(i));
      }
    }
    if (nset == 0) {
      fprintf(stderr, "getaffinity_freebsd: no cpu set in cpumask\n");
      for (i = 0; i < ncpu; i++) {
        av_push(mask, newSViv(i));
      }
    }
    return 1;
  }
}

int setaffinity_freebsd(int pid, AV *mask)
{
  cpulevel_t level = CPU_LEVEL_WHICH;
  cpuwhich_t which = CPU_WHICH_PID;
  id_t id = (id_t) pid;
  size_t setsize;
  cpuset_t cpumask;
  int i, r;

  int n = av_len(mask) + 1;
  int ncpu = num_cpus_sysctl();
  CPU_ZERO(&cpumask);
  if (ncpu > 0 && n > ncpu) {
    fprintf(stderr, "setaffinity_freebsd: "
                    "mask is larger than the number of cpus!\n");
  }
  setsize = sizeof(cpumask);
  for (i = 0; i < n; i++) {
    int proc_id = SvIV(*av_fetch(mask, i, 0));
    if (ncpu <= 0 || proc_id < ncpu) {
      CPU_SET(proc_id, &cpumask);
    } else {
      fprintf(stderr, "setaffinity_freebsd: ignoring request to set "
                      "processor %d which exceeds known num cpus %d\n",
                      proc_id, ncpu);
    }
  }
  r = cpuset_setaffinity(level, which, id, setsize, &cpumask);
  if (r != 0) {
    if (errno == EINVAL) {
      fprintf(stderr, "cpuset_setaffinity: EINVAL - "
                      "bad level, which, or mask arg\n");
    } else if (errno == EDEADLK) {
      fprintf(stderr, "cpuset_setaffinity: EDEADLK found\n");
    } else if (errno == EFAULT) {
      fprintf(stderr, "cpuset_setaffinity: EFAULT - invalid mask pointer\n");
    } else if (errno == ESRCH) {
      fprintf(stderr, "cpuset_setaffinity: ESRCH - invalid pid\n");
    } else if (errno == ERANGE) {
      fprintf(stderr, "cpuset_setaffinity: ERANGE - bad cpusetsize\n");
    } else if (errno == EPERM) {
      fprintf(stderr, "cpuset_setaffinity: EPERM - "
                      "no permission to set affinity on pid=%d\n", pid);
    } else {
      fprintf(stderr, "cpuset_setaffinity: unexpected error no=%d\n", errno);
    }
    return 0;
  } else {
    return 1;
  }
}

MODULE = Sys::CpuAffinity     PACKAGE = Sys::CpuAffinity

int
xs_getaffinity_freebsd(pid,mask)
    int pid
    AV *mask
  CODE:
    RETVAL = getaffinity_freebsd(pid,mask);
  OUTPUT:
    RETVAL

int 
xs_setaffinity_freebsd(pid,mask)
    int pid
    AV *mask
  CODE:
    RETVAL = setaffinity_freebsd(pid,mask);
  OUTPUT:
    RETVAL

int
xs_num_cpus_freebsd()
  CODE:
    RETVAL = num_cpus_sysctl();
  OUTPUT:
    RETVAL