If devinfo.type != AUDIO_MIXER_CLASS is true, xstatbar will never make
progress and loop forever. Fix it by trying the next device.

Cope with various changes in OpenBSD since xstatbar was written.

Skip cpu% text if there are many cores, to avoid some horizontal overruns.

Index: stats.c
--- stats.c.orig
+++ stats.c
@@ -61,118 +61,104 @@ fmtmem(int m)
  * volume stuff
  ****************************************************************************/
 
-int
-volume_check_dev(int fd, int class, char *name)
+/*
+ * new control
+ */
+void
+volume_ondesc(void *unused, struct sioctl_desc *d, int val)
 {
-   mixer_devinfo_t devinfo;
+   if (d == NULL)
+      return;
 
-   if (class < 0 || name == NULL)
-      return (-1);
+   if (d->addr == volume.left_addr)
+      volume.left_addr = -1;
+   if (d->addr == volume.right_addr)
+      volume.right_addr = -1;
 
-   devinfo.index = 0;
-   while (ioctl(fd, AUDIO_MIXER_DEVINFO, &devinfo) >= 0) {
-      if ((devinfo.type == AUDIO_MIXER_VALUE)
-      &&  (devinfo.mixer_class == class)
-      &&  (strncmp(devinfo.label.name, name, MAX_AUDIO_DEV_LEN) == 0))
-         return (devinfo.index);
+   if (d->type == SIOCTL_NUM &&
+      strcmp(d->group, "") == 0 &&
+      strcmp(d->node0.name, "output") == 0 &&
+      strcmp(d->func, "level") == 0) {
 
-      devinfo.index++;
+      switch (d->node0.unit) {
+      case -1:	/* mono */
+      case 0:	/* left */
+         volume.left_addr = d->addr;
+         volume.left_max = d->maxval;
+         volume.left = val;
+         break;
+      case 1:	/* right */
+         volume.right_addr = d->addr;
+         volume.right_max = d->maxval;
+         volume.right = val;
+      }
    }
+}
 
-   return (-1);
+/*
+ * control value changed
+ */
+void
+volume_onval(void *unused, unsigned int addr, unsigned int value)
+{
+   if (addr == volume.left_addr)
+      volume.left = value;
+   if (addr == volume.right_addr)
+      volume.right = value;
 }
 
 void
 volume_init()
 {
-   mixer_devinfo_t devinfo;
-   int oclass_idx, iclass_idx;
-
    volume.is_setup = false;
+   volume.left_addr = volume.right_addr = -1;
 
-   /* open mixer */
-   if ((volume.dev_fd = open("/dev/mixer", O_RDWR)) < 0) {
-      warn("volume: failed to open /dev/mixer");
+   if ((volume.hdl = sioctl_open(SIO_DEVANY, SIOCTL_READ, 0)) == NULL) {
+      warn("volume: failed to open default device");
       return;
    }
 
-   /* find the outputs and inputs classes */
-   oclass_idx = iclass_idx = -1;
-   devinfo.index = 0;
-   while (ioctl(volume.dev_fd, AUDIO_MIXER_DEVINFO, &devinfo) >= 0) {
-
-      if (devinfo.type != AUDIO_MIXER_CLASS)
-         continue;
-
-      if (strncmp(devinfo.label.name, AudioCoutputs, MAX_AUDIO_DEV_LEN) == 0)
-         oclass_idx = devinfo.index;
-      if (strncmp(devinfo.label.name, AudioCinputs, MAX_AUDIO_DEV_LEN) == 0)
-         iclass_idx = devinfo.index;
-
-      if (oclass_idx != -1 && iclass_idx != -1)
-         break;
-
-      devinfo.index++;
+   volume.pfds = calloc(sioctl_nfds(volume.hdl), sizeof(struct pollfd));
+   if (volume.pfds == NULL) {
+      warnx("volume: cannot allocate pfds\n");
+      goto bad_close;
    }
+  
 
-   /* find the master device */
-   volume.master_idx = volume_check_dev(volume.dev_fd, oclass_idx, AudioNmaster);
-   if (volume.master_idx == -1)
-      volume.master_idx = volume_check_dev(volume.dev_fd, iclass_idx, AudioNdac);
-   if (volume.master_idx == -1)
-      volume.master_idx = volume_check_dev(volume.dev_fd, oclass_idx, AudioNdac);
-   if (volume.master_idx == -1)
-      volume.master_idx = volume_check_dev(volume.dev_fd, oclass_idx, AudioNoutput);
-
-   if (volume.master_idx == -1) {
-      warnx("volume: failed to find \"master\" mixer device");
-      return;
+   if (!sioctl_ondesc(volume.hdl, volume_ondesc, NULL)) {
+      warnx("volume: cannot get descriptions\n");
+      goto bad_free;
    }
 
-   devinfo.index = volume.master_idx;
-   if (ioctl(volume.dev_fd, AUDIO_MIXER_DEVINFO, &devinfo) == -1) {
-      warn("AUDIO_MIXER_DEVINFO");
-      return;
+   if (!sioctl_onval(volume.hdl, volume_onval, NULL)) {
+      warnx("volume: cannot get values\n");
+      goto bad_free;
    }
 
-   volume.max = AUDIO_MAX_GAIN;
-   volume.nchan = devinfo.un.v.num_channels;
-
-   /* finished... now close the device and reopen as read only */
-   close(volume.dev_fd);
-   volume.dev_fd = open("/dev/mixer", O_RDONLY);
-   if (volume.dev_fd < 0) {
-      warn("volume: failed to re-open /dev/mixer");
-      return;
-   }
-
    volume.is_setup = true;
+   return;
+bad_free:
+   free(volume.pfds);
+bad_close:
+   sioctl_close(volume.hdl);
 }
 
 void
 volume_update()
 {
-   static mixer_ctrl_t vinfo;
+   int n;
 
    if (!volume.is_setup)
       return;
 
-   /* query info */
-   vinfo.dev = volume.master_idx;
-   vinfo.type = AUDIO_MIXER_VALUE;
-   vinfo.un.value.num_channels = volume.nchan;
-   if (ioctl(volume.dev_fd, AUDIO_MIXER_READ, &(vinfo)) < 0) {
-      warn("volume update: AUDIO_MIXER_READ");
-      return;
+   n = sioctl_pollfd(volume.hdl, volume.pfds, POLLIN);
+   if (n > 0) {
+      n = poll(volume.pfds, n, 0);
+      if (n == -1)
+         return;
+      if (n > 0)
+         sioctl_revents(volume.hdl, volume.pfds);
    }
-
-   /* record in global struct */
-   if (volume.nchan == 1)
-      volume.left = volume.right = vinfo.un.value.level[AUDIO_MIXER_LEVEL_MONO];
-   else {
-      volume.left  = vinfo.un.value.level[AUDIO_MIXER_LEVEL_LEFT];
-      volume.right = vinfo.un.value.level[AUDIO_MIXER_LEVEL_RIGHT];
-   }
 }
 
 void
@@ -180,7 +166,7 @@ volume_close() {
    if (!volume.is_setup)
       return;
 
-   close(volume.dev_fd);
+   sioctl_close(volume.hdl);
 }
 
 int
@@ -199,8 +185,9 @@ volume_draw(XColor color, int x, int y)
    width = 5;
 
    /* get volume as percents */
-   left  = roundf(100.0 * (float)volume.left  / (float)volume.max);
-   right = roundf(100.0 * (float)volume.right / (float)volume.max);
+   left  = roundf(100.0 * (float)volume.left  / (float)volume.left_max);
+   right = (volume.right_addr == -1) ?
+      left : roundf(100.0 * (float)volume.right / (float)volume.right_max);
 
    /* determine height of green-part of bar graphs */
    lheight = (int)(left  * (float)XINFO.height / 100.0);
@@ -358,7 +345,7 @@ sysinfo_init(int hist_size)
       err(1, "sysinfo init: memory calloc failed");
 
    for (i = 0; i < hist_size; i++) {
-      if ((sysinfo.memory[i] = calloc(3, sizeof(int))) == NULL)
+      if ((sysinfo.memory[i] = calloc(4, sizeof(int))) == NULL)
          err(1, "sysinfo init: memory[%d] calloc failed", i);
 
       for (j = 0; j < 3; j++)
@@ -372,6 +359,7 @@ sysinfo_init(int hist_size)
 
    /* allocate cpu history */
    sysinfo.cpu_raw   = calloc(sysinfo.ncpu, sizeof(uint64_t**));
+   sysinfo.cpu_online = calloc(sysinfo.ncpu, sizeof(*sysinfo.cpu_online));
    sysinfo.cpu_pcnts = calloc(sysinfo.ncpu, sizeof(int**));
    if (sysinfo.cpu_raw == NULL || sysinfo.cpu_pcnts == NULL)
       err(1, "sysinfo init: cpu_raw/cpu_pcnts calloc failed");
@@ -404,10 +392,14 @@ sysinfo_update()
 {
    static int mib_nprocs[] = { CTL_KERN, KERN_NPROCS };
    static int mib_vm[] = { CTL_VM, VM_METER };
-   static int mib_cpus[] = { CTL_KERN, 0, 0 };
+   static int mib_cpus[] = { CTL_KERN, KERN_CPTIME2, 0 };
+   static int mib_cpustats[] = { CTL_KERN, KERN_CPUSTATS, 0 };
+   static int mib_bcstats[] = {CTL_VFS, VFS_GENERIC, VFS_BCACHESTAT};
    static int diffs[CPUSTATES] = { 0 };
    struct vmtotal vminfo;
    struct swapent *swapdev;
+   struct bcachestats bcstats;
+   struct cpustats cpustats;
    size_t    size;
    int       cpu, state;
    int       cur, prev;
@@ -431,47 +423,46 @@ sysinfo_update()
    if (sysctl(mib_vm, 2, &vminfo, &size, NULL, 0) < 0)
       err(1, "sysinfo update: VM.METER failed");
 
+   /* update bufcache statistics */
+   size = sizeof(bcstats);
+   if (sysctl(mib_bcstats, 3, &bcstats, &size, NULL, 0) < 0)
+      err(1, "sysinfo update: VFS_BCACHESTAT failed");
+
+
    sysinfo.memory[cur][MEM_ACT] = vminfo.t_arm << sysinfo.pageshift;
    sysinfo.memory[cur][MEM_TOT] = vminfo.t_rm << sysinfo.pageshift;
    sysinfo.memory[cur][MEM_FRE] = vminfo.t_free << sysinfo.pageshift;
+   sysinfo.memory[cur][MEM_CCH] = bcstats.numbufpages << sysinfo.pageshift;
 
-   /* get swap status */
-   if ((nswaps = swapctl(SWAP_NSWAP, 0, 0)) == 0)
-      errx(1, "swapctl(SWAP_NSWAP) failed... this shouldn't happen");
-   if ((swapdev = calloc(nswaps, sizeof(*swapdev))) == NULL)
-      err(1, "sysinfo update: swapdev calloc failed (%d)", nswaps);
-   if (swapctl(SWAP_STATS, swapdev, nswaps) == -1)
-      err(1, "sysinfo update: swapctl(SWAP_STATS) failed");
-
    sysinfo.swap_used = sysinfo.swap_total = 0;
-   for (size = 0; size < nswaps; size++) {
-      if (swapdev[size].se_flags & SWF_ENABLE) {
-         sysinfo.swap_used  += swapdev[size].se_inuse / (1024 / DEV_BSIZE);
-         sysinfo.swap_total += swapdev[size].se_nblks / (1024 / DEV_BSIZE);
+
+   /* get swap status if swap is in use */
+   if ((nswaps = swapctl(SWAP_NSWAP, 0, 0)) != 0) {
+      if ((swapdev = calloc(nswaps, sizeof(*swapdev))) == NULL)
+         err(1, "sysinfo update: swapdev calloc failed (%d)", nswaps);
+      if (swapctl(SWAP_STATS, swapdev, nswaps) == -1)
+         err(1, "sysinfo update: swapctl(SWAP_STATS) failed");
+
+      for (size = 0; size < nswaps; size++) {
+         if (swapdev[size].se_flags & SWF_ENABLE) {
+            sysinfo.swap_used  += swapdev[size].se_inuse / (1024 / DEV_BSIZE);
+            sysinfo.swap_total += swapdev[size].se_nblks / (1024 / DEV_BSIZE);
+         }
       }
+      free(swapdev);
    }
-   free(swapdev);
 
    /* get states for each cpu. note this is raw # of ticks */
    size = CPUSTATES * sizeof(int64_t);
-   if (sysinfo.ncpu > 1) {
-      mib_cpus[1] = KERN_CPTIME2;
-      for (cpu = 0; cpu < sysinfo.ncpu; cpu++) {
-         mib_cpus[2] = cpu;
-         if (sysctl(mib_cpus, 3, sysinfo.cpu_raw[cpu][cur], &size, NULL, 0) < 0)
-            err(1, "sysinfo update: KERN.CPTIME2.%d failed", cpu);
-      }
-   } else {
-      int i;
-      long cpu_raw_tmp[CPUSTATES];
-      size = sizeof(cpu_raw_tmp);
-      mib_cpus[1] = KERN_CPTIME;
-      
-      if (sysctl(mib_cpus, 2, cpu_raw_tmp, &size, NULL, 0) < 0)
-         err(1, "sysinfo update: KERN.CPTIME failed");
-
-      for (i = 0; i < CPUSTATES; i++)
-         sysinfo.cpu_raw[0][cur][i] = cpu_raw_tmp[i];
+   for (cpu = 0; cpu < sysinfo.ncpu; cpu++) {
+      mib_cpus[2] = cpu;
+      if (sysctl(mib_cpus, 3, sysinfo.cpu_raw[cpu][cur], &size, NULL, 0) < 0)
+         err(1, "sysinfo update: KERN.CPTIME2.%d failed", cpu);
+      mib_cpustats[2] = cpu;
+      size = sizeof(cpustats);
+      if (sysctl(mib_cpustats, 3, &cpustats, &size, NULL, 0) < 0)
+         err(1, "sysinfo update: KERN.CPUSTATS.%d failed", cpu);
+      sysinfo.cpu_online[cpu] = (cpustats.cs_flags & CPUSTATS_ONLINE) != 0;
    }
 
    /* convert ticks to percentages */
@@ -506,19 +497,19 @@ sysinfo_close()
 }
 
 int
-cpu_draw(int cpu, XColor color, int x, int y)
+cpu_draw(int cpu, XColor color, int x, int y, int firstcpu)
 {
    static char  str[1000];
-   static char *cpuStateNames[] = { "u", "n", "s", "i", "I" };
+   static char *cpuStateNames[] = { "u", "n", "s", "p", "i", "I" };
    static XColor *cpuStateColors[] = {
-      &COLOR_RED, &COLOR_BLUE, &COLOR_YELLOW, &COLOR_MAGENTA, &COLOR_GREEN
+      &COLOR_RED, &COLOR_BLUE, &COLOR_YELLOW, &COLOR_CYAN, &COLOR_MAGENTA, &COLOR_GREEN
    };
    int state, startx, time, col, h, i;
 
    startx = x;
 
-   snprintf(str, sizeof(str), "cpu%d:", cpu);
-   x += render_text(color, x, y, str) + 1;
+   if (firstcpu)
+      x += render_text(color, x, y, "cpu:") + 1;
 
    /* for the graph, draw a green rectangle to start with */
    XSetForeground(XINFO.disp, XINFO.gc, COLOR_GREEN.pixel);
@@ -531,7 +522,7 @@ cpu_draw(int cpu, XColor color, int x, int y)
 
       /* user time */
       h = 0;
-      for (i = 0; i < 4; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
+      for (i = 0; i < 5; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
       h = h * XINFO.height / 100;
       XSetForeground(XINFO.disp, XINFO.gc, COLOR_RED.pixel);
       XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
@@ -540,7 +531,7 @@ cpu_draw(int cpu, XColor color, int x, int y)
 
       /* nice time */
       h = 0;
-      for (i = 1; i < 4; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
+      for (i = 1; i < 5; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
       h = h * XINFO.height / 100;
       XSetForeground(XINFO.disp, XINFO.gc, COLOR_BLUE.pixel);
       XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
@@ -549,15 +540,24 @@ cpu_draw(int cpu, XColor color, int x, int y)
 
       /* system time */
       h = 0;
-      for (i = 2; i < 4; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
+      for (i = 2; i < 5; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
       h = h * XINFO.height / 100;
       XSetForeground(XINFO.disp, XINFO.gc, COLOR_YELLOW.pixel);
       XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
          x + col, XINFO.height - h,
          x + col, XINFO.height);
 
+      /* spin time */
+      h = 0;
+      for (i = 3; i < 5; i++) h += sysinfo.cpu_pcnts[cpu][time][i];
+      h = h * XINFO.height / 100;
+      XSetForeground(XINFO.disp, XINFO.gc, COLOR_CYAN.pixel);
+      XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
+         x + col, XINFO.height - h,
+         x + col, XINFO.height);
+
       /* interrupt time */
-      h = sysinfo.cpu_pcnts[cpu][time][3];
+      h = sysinfo.cpu_pcnts[cpu][time][4];
       h = h * XINFO.height / 100;
       XSetForeground(XINFO.disp, XINFO.gc, COLOR_MAGENTA.pixel);
       XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
@@ -569,13 +569,15 @@ cpu_draw(int cpu, XColor color, int x, int y)
 
    x += sysinfo.hist_size + 1;
 
-   /* draw the text */
-   time = sysinfo.current;
-   for (state = 0; state < CPUSTATES; state++) {
-      snprintf(str, sizeof(str), "%3d%%%s",
-         sysinfo.cpu_pcnts[cpu][time][state], cpuStateNames[state]);
+   /* draw text, skip if there are many cpus in which case try to avoid overrunning */
+   if (sysinfo.ncpu < 8) {
+      time = sysinfo.current;
+      for (state = 0; state < CPUSTATES; state++) {
+         snprintf(str, sizeof(str), "%3d%%%s",
+            sysinfo.cpu_pcnts[cpu][time][state], cpuStateNames[state]);
 
-      x += render_text(*(cpuStateColors[state]), x, y, str);
+         x += render_text(*(cpuStateColors[state]), x, y, str);
+      }
    }
 
    return x - startx;
@@ -594,7 +596,8 @@ mem_draw(XColor color, int x, int y)
    /* determine total memory */
    total = sysinfo.memory[cur][MEM_ACT]
          + sysinfo.memory[cur][MEM_TOT]
-         + sysinfo.memory[cur][MEM_FRE];
+         + sysinfo.memory[cur][MEM_FRE]
+         + sysinfo.memory[cur][MEM_CCH];
 
    /* start drawing ... */
    x += render_text(color, x, y, "mem:") + 1;
@@ -610,7 +613,8 @@ mem_draw(XColor color, int x, int y)
 
       if ((sysinfo.memory[time][MEM_ACT] != 0)
       ||  (sysinfo.memory[time][MEM_TOT] != 0)
-      ||  (sysinfo.memory[time][MEM_FRE] != 0)) {
+      ||  (sysinfo.memory[time][MEM_FRE] != 0)
+      ||  (sysinfo.memory[time][MEM_CCH] != 0)) {
 
 
          /* draw yellow (total) bar */
@@ -627,6 +631,13 @@ mem_draw(XColor color, int x, int y)
          XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
             x + col, XINFO.height - h,
             x + col, XINFO.height);
+
+         /* draw magenta (cache) bar */
+         h = sysinfo.memory[time][MEM_CCH] * XINFO.height / total;
+         XSetForeground(XINFO.disp, XINFO.gc, COLOR_MAGENTA.pixel);
+         XDrawLine(XINFO.disp, XINFO.buf, XINFO.gc,
+            x + col, XINFO.height - h,
+            x + col, XINFO.height);
       }
 
       time = (time + 1) % sysinfo.hist_size;
@@ -639,6 +650,8 @@ mem_draw(XColor color, int x, int y)
    x += render_text(COLOR_YELLOW, x, y, fmtmem(sysinfo.memory[cur][MEM_TOT]));
    x += render_text(color, x, y, "/");
    x += render_text(COLOR_GREEN, x, y, fmtmem(sysinfo.memory[cur][MEM_FRE]));
+   x += render_text(color, x, y, "/");
+   x += render_text(COLOR_MAGENTA, x, y, fmtmem(sysinfo.memory[cur][MEM_CCH]));
 
    /* draw swap, if any is used */
    if (sysinfo.swap_used > 0) {
