/*
 *
 * Conky, a system monitor, based on torsmo
 *
 * Please see COPYING for details
 *
 * Copyright (c) 2004, Hannu Saransaari and Lauri Hakkarainen
 * Copyright (c) 2007 Toni Spets
 * Copyright (c) 2005-2024 Brenden Matthews, Philip Kovacs, et. al.
 *	(see AUTHORS)
 * All rights reserved.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

#include "linux.h"
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <limits.h>
#include <sys/stat.h>
#include <sys/sysinfo.h>
#include <sys/types.h>
#include <clocale>
#include "../../common.h"
#include "../../conky.h"
#include "../../content/temphelper.h"
#include "../../logging.h"
#include "../hardware/diskio.h"
#include "../network/net_stat.h"
#include "../proc.h"
#ifndef HAVE_CLOCK_GETTIME
#include <sys/time.h>
#endif
#include <fcntl.h>
#include <unistd.h>
// #include <assert.h>
#include <time.h>
#include <algorithm>
#include <unordered_map>
#include <vector>
#include "../../lua/setting.hh"
#include "../top.h"

#include <arpa/inet.h>
#include <linux/sockios.h>
#include <net/if.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#ifdef _NET_IF_H
#define _LINUX_IF_H
#endif
#include <linux/route.h>
#include <linux/version.h>
#include <math.h>
#include <pthread.h>
#include <atomic>
#include <fstream>
#include <mutex>
#include <string_view>

/* The following ifdefs were adapted from gkrellm */
#include <linux/major.h>

#if !defined(MD_MAJOR)
#define MD_MAJOR 9
#endif

#if !defined(LVM_BLK_MAJOR)
#define LVM_BLK_MAJOR 58
#endif

#if !defined(NBD_MAJOR)
#define NBD_MAJOR 43
#endif

#if !defined(DM_MAJOR)
#define DM_MAJOR 253
#endif

#ifdef BUILD_WLAN
#include <iwlib.h>
#endif

struct sysfs {
  int fd;
  int arg;
  char devtype[256];
  char type[64];
  float factor, offset;
};

/* To be used inside upspeed/f downspeed/f as ${gw_iface} variable */
char e_iface[64];

/* To use ${iface X} where X is a number and will
 * return the current X NIC name */
static const unsigned int iface_len = 64U;
char interfaces_arr[MAX_NET_INTERFACES][iface_len] = {""};

#define SHORTSTAT_TEMPL "%*s %llu %llu %llu"
#define LONGSTAT_TEMPL "%*s %llu %llu %llu "

static conky::simple_config_setting<bool> top_cpu_separate("top_cpu_separate",
                                                           false, true);

/* This flag tells the linux routines to use the /proc system where possible,
 * even if other api's are available, e.g. sysinfo() or getloadavg().
 * the reason for this is to allow for /proc-based distributed monitoring.
 * using a flag in this manner creates less confusing code. */
static int prefer_proc = 0;

/* To tell 'print_sysfs_sensor' whether to print the temperature
 * in int or float */
static const char *temp2 = "empty";

void prepare_update(void) {}

int update_uptime(void) {
#ifdef HAVE_SYSINFO
  if (!prefer_proc) {
    struct sysinfo s_info;

    sysinfo(&s_info);
    info.uptime = (double)s_info.uptime;
  } else
#endif
  {
    static int reported = 0;
    FILE *fp;

    if (!(fp = open_file("/proc/uptime", &reported))) {
      info.uptime = 0.0;
      return 0;
    }
    if (fscanf(fp, "%lf", &info.uptime) <= 0) info.uptime = 0;
    fclose(fp);
  }
  return 0;
}

int check_mount(struct text_object *obj) {
  int ret = 0;
  FILE *mtab;

  if (!obj->data.s) return 0;

  if ((mtab = fopen("/proc/mounts", "r"))) {
    char buf1[256], buf2[129];

    while (fgets(buf1, 256, mtab)) {
      sscanf(buf1, "%*s %128s", buf2);
      if (!strcmp(obj->data.s, buf2)) {
        ret = 1;
        break;
      }
    }
    fclose(mtab);
  } else {
    LOG_ERROR("could not open mtab: {}", strerror(errno));
  }
  return ret;
}

/* these things are also in sysinfo except Buffers:
 * (that's why I'm reading them from proc) */

int update_meminfo(void) {
  FILE *meminfo_fp;
  static int reported = 0;

  /* unsigned int a; */
  char buf[256];

  /* With multi-threading, calculations that require
   * multiple steps to reach a final result can cause havok
   * if the intermediary calculations are directly assigned to the
   * information struct (they may be read by other functions in the meantime).
   * These variables keep the calculations local to the function and finish off
   * the function by assigning the results to the information struct */
  unsigned long long sreclaimable = 0, curmem = 0, curbufmem = 0,
                     cureasyfree = 0;

  info.memmax = info.memdirty = info.swap = info.swapfree = info.swapmax =
      info.memwithbuffers = info.buffers = info.cached = info.memfree =
          info.memeasyfree = info.legacymem = info.shmem = info.memavail =
              info.free_bufcache = info.free_cached = 0;

  if (!(meminfo_fp = open_file("/proc/meminfo", &reported))) { return 0; }

  while (!feof(meminfo_fp)) {
    if (fgets(buf, 255, meminfo_fp) == nullptr) { break; }

    if (strncmp(buf, "MemTotal:", 9) == 0) {
      sscanf(buf, "%*s %llu", &info.memmax);
    } else if (strncmp(buf, "MemFree:", 8) == 0) {
      sscanf(buf, "%*s %llu", &info.memfree);
    } else if (strncmp(buf, "SwapTotal:", 10) == 0) {
      sscanf(buf, "%*s %llu", &info.swapmax);
    } else if (strncmp(buf, "SwapFree:", 9) == 0) {
      sscanf(buf, "%*s %llu", &info.swapfree);
    } else if (strncmp(buf, "Buffers:", 8) == 0) {
      sscanf(buf, "%*s %llu", &info.buffers);
    } else if (strncmp(buf, "Cached:", 7) == 0) {
      sscanf(buf, "%*s %llu", &info.cached);
    } else if (strncmp(buf, "Dirty:", 6) == 0) {
      sscanf(buf, "%*s %llu", &info.memdirty);
    } else if (strncmp(buf, "MemAvailable:", 13) == 0) {
      sscanf(buf, "%*s %llu", &info.memavail);
    } else if (strncmp(buf, "Shmem:", 6) == 0) {
      sscanf(buf, "%*s %llu", &info.shmem);
    } else if (strncmp(buf, "SReclaimable:", 13) == 0) {
      sscanf(buf, "%*s %llu", &sreclaimable);
    }
  }

  curmem = info.memwithbuffers = info.memmax - info.memfree;
  cureasyfree = info.memfree;
  info.swap = info.swapmax - info.swapfree;

  /* Reclaimable memory: does not include shared memory, which is part of cached
     but unreclaimable. Includes the reclaimable part of the Slab cache though.
     Note: when shared memory is swapped out, shmem decreases and swapfree
     decreases - we want this.
  */
  curbufmem = (info.cached - info.shmem) + info.buffers + sreclaimable;

  /* Calculate the memory usage.
   *
   * The Linux Kernel introduced a new field for memory available,
   * when possible, use that.
   * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=34e431b0ae398fc54ea69ff85ec700722c9da773
   */
  if (no_buffers.get(*state)) {
#if LINUX_VERSION_CODE <= KERNEL_VERSION(3, 14, 0)
    /* Now ('info.mem' - 'info.bufmem') is the *really used* (aka unreclaimable)
       memory. When this value reaches the size of the physical RAM, and swap is
       full or non-present, OOM happens. Therefore this is the value users want
       to monitor, regarding their RAM.
    */
    curmem -= curbufmem;
    cureasyfree += curbufmem;
#else  /* LINUX_VERSION_CODE <= KERNEL_VERSION(3, 14, 0) */
    curmem = info.memmax - info.memavail;
    cureasyfree += curbufmem;
#endif /* LINUX_VERSION_CODE <= KERNEL_VERSION(3, 14, 0) */
  }

  /* Now that we know that every calculation is finished we can wrap up
   * by assigning the values to the information structure */
  info.mem = curmem;
  info.bufmem = curbufmem;
  info.memeasyfree = cureasyfree;
  info.legacymem =
      info.memmax - (info.memfree + info.buffers + info.cached + sreclaimable);
  info.free_cached = info.cached + sreclaimable;
  info.free_bufcache = info.free_cached + info.buffers;

  fclose(meminfo_fp);
  return 0;
}

void print_laptop_mode(struct text_object *obj, char *p,
                       unsigned int p_max_size) {
  FILE *fp;
  int val = -1;

  (void)obj;

  if ((fp = fopen("/proc/sys/vm/laptop_mode", "r")) != nullptr) {
    if (fscanf(fp, "%d\n", &val) <= 0) val = 0;
    fclose(fp);
  }
  snprintf(p, p_max_size, "%d", val);
}

/* my system says:
 * # cat /sys/block/sda/queue/scheduler
 * noop [anticipatory] cfq
 */
void print_ioscheduler(struct text_object *obj, char *p,
                       unsigned int p_max_size) {
  FILE *fp;
  char buf[128];

  if (!obj->data.s) goto out_fail;

  snprintf(buf, 127, "/sys/block/%s/queue/scheduler", obj->data.s);
  if ((fp = fopen(buf, "r")) == nullptr) goto out_fail;

  while (fscanf(fp, "%127s", buf) == 1) {
    if (buf[0] == '[') {
      buf[strlen(buf) - 1] = '\0';
      snprintf(p, p_max_size, "%s", buf + 1);
      fclose(fp);
      return;
    }
  }
  fclose(fp);
out_fail:
  snprintf(p, p_max_size, "%s", "n/a");
  return;
}

class gw_info_s {
 public:
  gw_info_s() : iface(nullptr), ip(nullptr), count(0) {}
  char *iface;
  char *ip;
  std::atomic<int> count;
  std::mutex mutex;

  void reset() {
    std::lock_guard<std::mutex> lock(mutex);
    free_and_zero(iface);
    free_and_zero(ip);
  }
};
static gw_info_s gw_info;

char *save_set_string(char *x, char *y) {
  if (x != nullptr && strcmp((char *)x, (char *)y)) {
    free_and_zero(x);
    x = strndup("multiple", text_buffer_size.get(*state));
  } else if (x == nullptr && y != nullptr) {
    x = strndup(y, text_buffer_size.get(*state));
  }
  return x;
}

void update_gateway_info_failure(const char *reason) {
  if (reason != nullptr) { LOG_ERROR("{}: {}", reason, strerror(errno)); }
  // 2 pointers to 1 location causes a crash when we try to free them both
  std::unique_lock<std::mutex> lock(gw_info.mutex);
  free_and_zero(gw_info.iface);
  free_and_zero(gw_info.ip);
  gw_info.iface = strndup("failed", text_buffer_size.get(*state));
  gw_info.ip = strndup("failed", text_buffer_size.get(*state));
}

/* Iface Destination Gateway Flags RefCnt Use Metric Mask MTU Window IRTT */
#define RT_ENTRY_FORMAT "%63s %lx %lx %x %*d %*d %*d %lx %*d %*d %*d\n"

FILE *check_procroute() {
  FILE *fp;
  if ((fp = fopen("/proc/net/route", "r")) == nullptr) {
    update_gateway_info_failure("fopen()");
    return nullptr;
  }

  /* skip over the table header line, which is always present */
  if (fscanf(fp, "%*[^\n]\n") < 0) {
    fclose(fp);
    return nullptr;
  }

  return fp;
}

int update_gateway_info2(void) {
  FILE *fp;
  char iface[iface_len];
  unsigned long dest;
  unsigned long gate;
  unsigned long mask;
  unsigned int flags;
  unsigned int x = 1;
  unsigned int z = 1;
  int strcmpreturn;

  if ((fp = check_procroute()) != nullptr) {
    while (!feof(fp)) {
      strcmpreturn = 1;
      if (fscanf(fp, RT_ENTRY_FORMAT, iface, &dest, &gate, &flags, &mask) !=
          5) {
        update_gateway_info_failure("fscanf()");
        break;
      }
      if (!(dest || mask) && ((flags & RTF_GATEWAY) || !gate)) {
        snprintf(e_iface, 64, "%s", iface);
      }
      if (1U == x) {
        snprintf(interfaces_arr[x++], iface_len, "%s", iface);
        continue;
      } else if (0 == strcmp(iface, interfaces_arr[x - 1])) {
        continue;
      }
      for (z = 1; z < iface_len - 1 && strcmpreturn == 1; z++) {
        strcmpreturn = strcmp(iface, interfaces_arr[z]);
      }
      if (strcmpreturn == 1) {
        snprintf(interfaces_arr[x++], iface_len, "%s", iface);
      }
    }
    fclose(fp);
  }
  return 0;
}

int update_gateway_info(void) {
  FILE *fp;
  struct in_addr ina;
  char iface[iface_len];
  unsigned long dest, gate, mask;
  unsigned int flags;

  gw_info.reset();
  gw_info.count = 0;

  if ((fp = check_procroute()) != nullptr) {
    while (!feof(fp)) {
      if (fscanf(fp, RT_ENTRY_FORMAT, iface, &dest, &gate, &flags, &mask) !=
          5) {
        update_gateway_info_failure("fscanf()");
        break;
      }
      if (!(dest || mask) && ((flags & RTF_GATEWAY) || !gate)) {
        gw_info.count++;
        snprintf(e_iface, 64, "%s", iface);
        std::unique_lock<std::mutex> lock(gw_info.mutex);
        gw_info.iface = save_set_string(gw_info.iface, iface);
        ina.s_addr = gate;
        gw_info.ip = save_set_string(gw_info.ip, inet_ntoa(ina));
      }
    }
    fclose(fp);
  }
  return 0;
}

void free_gateway_info(struct text_object *obj) {
  (void)obj;
  gw_info.reset();
}

int gateway_exists(struct text_object *obj) {
  (void)obj;
  return !!gw_info.count;
}

void print_gateway_iface(struct text_object *obj, char *p,
                         unsigned int p_max_size) {
  (void)obj;
  std::lock_guard<std::mutex> lock(gw_info.mutex);
  snprintf(p, p_max_size, "%s", gw_info.iface);
}

void print_gateway_iface2(struct text_object *obj, char *p,
                          unsigned int p_max_size) {
  long int z = 0;
  unsigned int x = 1;
  unsigned int found = 0;
  char buf[iface_len * iface_len] = {""};
  char *buf_ptr = buf;

  if (0 == strcmp(obj->data.s, "")) {
    for (; x < iface_len - 1; x++) {
      if (0 == strcmp("", interfaces_arr[x])) { break; }
      buf_ptr += snprintf(buf_ptr, iface_len - 1, "%s, ", interfaces_arr[x]);
      found = 1;
    }
    if (1 == found) {
      --buf_ptr;
      *(--buf_ptr) = '\0';
    }
    snprintf(p, p_max_size, "%s", buf);
    return;
  }

  z = strtol(obj->data.s, (char **)NULL, 10);
  if ((iface_len - 1) > z) { snprintf(p, p_max_size, "%s", interfaces_arr[z]); }
}

void print_gateway_ip(struct text_object *obj, char *p,
                      unsigned int p_max_size) {
  (void)obj;
  std::lock_guard<std::mutex> lock(gw_info.mutex);
  snprintf(p, p_max_size, "%s", gw_info.ip);
}

void update_net_interfaces(FILE *net_dev_fp, bool is_first_update,
                           double time_between_updates) {
  /* read each interface */
#ifdef BUILD_WLAN
  // wireless info variables
  struct wireless_info *winfo;
  struct iwreq wrq;
#endif

  for (int i = 0; i < MAX_NET_INTERFACES; i++) {
    struct net_stat *ns;
    char *s, *p;
    long long r, t, last_recv, last_trans;

    /* quit only after all non-header lines from /proc/net/dev parsed */
    // FIXME: arbitrary size chosen to keep code simple.
    char buf[256];
    if (fgets(buf, 255, net_dev_fp) == nullptr) { break; }
    p = buf;
    /* change char * p to first non-space character, which is the beginning
     * of the interface name */
    while (*p != '\0' && isspace((unsigned char)*p)) { p++; }

    s = p;

    /* increment p until the end of the interface name has been reached */
    while (*p != '\0' && *p != ':') { p++; }
    if (*p == '\0') { continue; }
    /* replace ':' with '\0' in output of /proc/net/dev */
    *p = '\0';
    p++;

    /* get pointer to interface statistics with the interface name in s */
    ns = get_net_stat(s, nullptr, NULL);
    ns->up = 1;
    memset(&(ns->addr.sa_data), 0, 14);

    memset(ns->addrs, 0,
           17 * MAX_NET_INTERFACES +
               1); /* Up to 17 chars per ip, max MAX_NET_INTERFACES interfaces.
                      Nasty memory usage... */

    /* bytes packets errs drop fifo frame compressed multicast|bytes ... */
    sscanf(p, "%lld  %*d     %*d  %*d  %*d  %*d   %*d        %*d       %lld",
           &r, &t);

    /* if the interface is parsed the first time, then set recv and trans
     * to currently received, meaning the change in network traffic is 0 */
    if (ns->last_read_recv == -1) {
      ns->recv = r;
      is_first_update = true;
      ns->last_read_recv = r;
    }
    if (ns->last_read_trans == -1) {
      ns->trans = t;
      is_first_update = true;
      ns->last_read_trans = t;
    }
    /* move current traffic statistic to last thereby obsoleting the
     * current statistic */
    last_recv = ns->recv;
    last_trans = ns->trans;

    /* If recv or trans is less than last time, an overflow happened.
     * In that case set the last traffic to the current one, don't set
     * it to 0, else a spike in the download and upload speed will occur! */
    if (r < ns->last_read_recv) {
      last_recv = r;
    } else {
      ns->recv += (r - ns->last_read_recv);
    }
    ns->last_read_recv = r;

    if (t < ns->last_read_trans) {
      last_trans = t;
    } else {
      ns->trans += (t - ns->last_read_trans);
    }
    ns->last_read_trans = t;

    /*** ip addr patch ***/
    int file_descriptor = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);

    struct ifconf conf;
    conf.ifc_buf = (char *)malloc(sizeof(struct ifreq) * MAX_NET_INTERFACES);
    conf.ifc_len = sizeof(struct ifreq) * MAX_NET_INTERFACES;
    memset(conf.ifc_buf, 0, conf.ifc_len);

    ioctl(file_descriptor, SIOCGIFCONF, &conf);

    for (unsigned int k = 0; k < conf.ifc_len / sizeof(struct ifreq); k++) {
      struct net_stat *ns2;

      ns2 = get_net_stat(conf.ifc_req[k].ifr_ifrn.ifrn_name, nullptr, NULL);
      ns2->addr = conf.ifc_req[k].ifr_ifru.ifru_addr;
      char temp_addr[18];
      snprintf(temp_addr, sizeof(temp_addr), "%u.%u.%u.%u, ",
               ns2->addr.sa_data[2] & 255, ns2->addr.sa_data[3] & 255,
               ns2->addr.sa_data[4] & 255, ns2->addr.sa_data[5] & 255);
      if (nullptr == strstr(ns2->addrs, temp_addr))
        strncpy(ns2->addrs + strlen(ns2->addrs), temp_addr, 17);
    }

    close(file_descriptor);

    free(conf.ifc_buf);
    /*** end ip addr patch ***/

    if (!is_first_update) {
      /* calculate instantaneous speeds */
      ns->net_rec[0] = (ns->recv - last_recv) / time_between_updates;
      ns->net_trans[0] = (ns->trans - last_trans) / time_between_updates;
    }

    unsigned int curtmp1 = 0;
    unsigned int curtmp2 = 0;
    /* get an average over the last speed samples */
    int samples = net_avg_samples.get(*state);
    /* is OpenMP actually useful here? How large is samples? > 1000 ? */
#ifdef HAVE_OPENMP
#pragma omp parallel for reduction(+ : curtmp1, curtmp2) schedule(dynamic, 10)
#endif /* HAVE_OPENMP */
    for (int j = 0; j < samples; j++) {
      curtmp1 = curtmp1 + ns->net_rec[j];
      curtmp2 = curtmp2 + ns->net_trans[j];
    }
    ns->recv_speed = curtmp1 / (double)samples;
    ns->trans_speed = curtmp2 / (double)samples;
    if (samples > 1) {
#ifdef HAVE_OPENMP
#pragma omp parallel for schedule(dynamic, 10)
#endif /* HAVE_OPENMP */
      for (int j = samples; j > 1; j--) {
        ns->net_rec[j - 1] = ns->net_rec[j - 2];
        ns->net_trans[j - 1] = ns->net_trans[j - 2];
      }
    }

#ifdef BUILD_WLAN
    /* update wireless info */
    winfo = (struct wireless_info *)malloc(sizeof(struct wireless_info));
    memset(winfo, 0, sizeof(struct wireless_info));

    int skfd = iw_sockets_open();
    if (iw_get_basic_config(skfd, s, &(winfo->b)) > -1) {
      // set present winfo variables
      if (iw_get_range_info(skfd, s, &(winfo->range)) >= 0) {
        winfo->has_range = 1;
      }
      if (iw_get_stats(skfd, s, &(winfo->stats), &winfo->range,
                       winfo->has_range) >= 0) {
        winfo->has_stats = 1;
      }
      if (iw_get_ext(skfd, s, SIOCGIWAP, &wrq) >= 0) {
        winfo->has_ap_addr = 1;
        memcpy(&(winfo->ap_addr), &(wrq.u.ap_addr), sizeof(sockaddr));
      }

      // get bitrate
      if (iw_get_ext(skfd, s, SIOCGIWRATE, &wrq) >= 0) {
        memcpy(&(winfo->bitrate), &(wrq.u.bitrate), sizeof(iwparam));
        iw_print_bitrate(ns->bitrate, 16, winfo->bitrate.value);
      }

      // get link quality
      if (winfo->has_range && winfo->has_stats) {
        bool has_qual_level = (winfo->stats.qual.level != 0) ||
                              (winfo->stats.qual.updated & IW_QUAL_DBM);

        if (has_qual_level &&
            !(winfo->stats.qual.updated & IW_QUAL_QUAL_INVALID)) {
          ns->link_qual = winfo->stats.qual.qual;

          if (winfo->range.max_qual.qual > 0) {
            ns->link_qual_max = winfo->range.max_qual.qual;
          }
        }
      }

      // get ap mac
      if (winfo->has_ap_addr) { iw_sawap_ntop(&winfo->ap_addr, ns->ap); }

      // get essid
      if (winfo->b.has_essid) {
        if (winfo->b.essid_on) {
          snprintf(ns->essid, 34, "%s", winfo->b.essid);
        } else {
          snprintf(ns->essid, 34, "%s", "off/any");
        }
      }

      // get channel and freq
      if (winfo->b.has_freq) {
        if (winfo->has_range == 1) {
          ns->channel = iw_freq_to_channel(winfo->b.freq, &(winfo->range));
          iw_print_freq_value(ns->freq, 16, winfo->b.freq);
        } else {
          ns->channel = 0;
          ns->freq[0] = 0;
        }
      }

      snprintf(ns->mode, 16, "%s", iw_operation_mode[winfo->b.mode]);
    }

    iw_sockets_close(skfd);
    free(winfo);
#endif
  }
}

#ifdef BUILD_IPV6
void update_ipv6_net_stats() {
  FILE *file;
  char v6addr[33];
  char devname[21];
  unsigned int netmask, scope;
  struct net_stat *ns;
  struct v6addr *lastv6;

  // remove the old v6 addresses otherwise they are listed multiple times
  for (unsigned int i = 0; i < MAX_NET_INTERFACES; i++) {
    ns = &netstats[i];
    while (ns->v6addrs != nullptr) {
      lastv6 = ns->v6addrs;
      ns->v6addrs = ns->v6addrs->next;
      free(lastv6);
    }
  }

  if ((file = fopen(PROCDIR "/net/if_inet6", "r")) == nullptr) { return; }

  while (fscanf(file, "%32s %*02x %02x %02x %*02x %20s\n", v6addr, &netmask,
                &scope, devname) != EOF) {
    ns = get_net_stat(devname, nullptr, NULL);

    if (ns->v6addrs == nullptr) {
      lastv6 = (struct v6addr *)malloc(sizeof(struct v6addr));
      ns->v6addrs = lastv6;
    } else {
      lastv6 = ns->v6addrs;
      while (lastv6->next) lastv6 = lastv6->next;
      lastv6->next = (struct v6addr *)malloc(sizeof(struct v6addr));
      lastv6 = lastv6->next;
    }

    for (int i = 0; i < 16; i++)
      sscanf(v6addr + 2 * i, "%2hhx", &(lastv6->addr.s6_addr[i]));

    lastv6->netmask = netmask;

    switch (scope) {
      case 0:  // global
        lastv6->scope = 'G';
        break;
      case 16:  // host-local
        lastv6->scope = 'H';
        break;
      case 32:  // link-local
        lastv6->scope = 'L';
        break;
      case 64:  // site-local
        lastv6->scope = 'S';
        break;
      case 128:  // compat
        lastv6->scope = 'C';
        break;
      default:
        lastv6->scope = '?';
    }

    lastv6->next = nullptr;
  }

  fclose(file);
}
#endif /* BUILD_IPV6 */

/**
 * Parses information from /proc/net/dev and stores them in ???
 *
 * For the output format of /proc/net/dev @see http://linux.die.net/man/5/proc
 *
 * @return always returns 0. May change in the future, e.g. returning non zero
 * if some error happened
 **/
int update_net_stats(void) {
  update_gateway_info();
  update_gateway_info2();
  FILE *net_dev_fp;
  static int reported = 0;
  /* variable to notify the parts averaging the download speed, that this
   * is the first call ever to this function. This variable can't be used
   * to decide if this is the first time an interface was parsed as there
   * are many interfaces, which can be activated and deactivated at arbitrary
   * times */
  static bool is_first_update = true;

  // FIXME: arbitrary size chosen to keep code simple.
  char buf[256];
  double time_between_updates;

  /* get delta */
  time_between_updates = current_update_time - last_update_time;
  if (time_between_updates <= 0.0001) { return 0; }

  /* open file /proc/net/dev. If not something went wrong, clear all
   * network statistics */
  if (!(net_dev_fp = open_file("/proc/net/dev", &reported))) {
    clear_net_stats();
    return 0;
  }
  /* ignore first two header lines in file /proc/net/dev. If somethings
   * goes wrong, e.g. end of file reached, quit.
   * (Why isn't clear_net_stats called for this case ??? */
  char *one = fgets(buf, 255, net_dev_fp);
  char *two = fgets(buf, 255, net_dev_fp);
  if (!one || /* garbage */
      !two) { /* garbage (field names) */
    fclose(net_dev_fp);
    return 0;
  }

  update_net_interfaces(net_dev_fp, is_first_update, time_between_updates);

#ifdef BUILD_IPV6
  update_ipv6_net_stats();
#endif /* BUILD_IPV6 */

  is_first_update = false;

  fclose(net_dev_fp);
  return 0;
}

int result;

int update_total_processes(void) {
  DIR *dir;
  struct dirent *entry;
  int ignore1;
  char ignore2;

  info.procs = 0;
  dir = opendir("/proc");
  if (dir) {
    while ((entry = readdir(dir))) {
      if (sscanf(entry->d_name, "%d%c", &ignore1, &ignore2) == 1) {
        info.procs++;
      }
    }
    closedir(dir);
  }
  return 0;
}

int update_threads(void) {
#ifdef HAVE_SYSINFO
  if (!prefer_proc) {
    struct sysinfo s_info;

    sysinfo(&s_info);
    info.threads = s_info.procs;
  } else
#endif
  {
    static int reported = 0;
    FILE *fp;

    if (!(fp = open_file("/proc/loadavg", &reported))) {
      info.threads = 0;
      return 0;
    }
    if (fscanf(fp, "%*f %*f %*f %*d/%hu", &info.threads) <= 0) info.threads = 0;
    fclose(fp);
  }
  return 0;
}

#define CPU_SAMPLE_COUNT 15
struct cpu_info {
  unsigned long long cpu_user;
  unsigned long long cpu_system;
  unsigned long long cpu_nice;
  unsigned long long cpu_idle;
  unsigned long long cpu_iowait;
  unsigned long long cpu_irq;
  unsigned long long cpu_softirq;
  unsigned long long cpu_steal;
  unsigned long long cpu_total;
  unsigned long long cpu_active_total;
  unsigned long long cpu_last_total;
  unsigned long long cpu_last_active_total;
  double cpu_val[CPU_SAMPLE_COUNT];
};
static short cpu_setup = 0;

/* Determine if this kernel gives us "extended" statistics information in
 * /proc/stat.
 * Kernels around 2.5 and earlier only reported user, system, nice, and
 * idle values in proc stat.
 * Kernels around 2.6 and greater report these PLUS iowait, irq, softirq,
 * and steal */
void determine_longstat(char *buf) {
  unsigned long long iowait = 0;

  KFLAG_SETOFF(KFLAG_IS_LONGSTAT);
  /* scanf will either return -1 or 1 because there is only 1 assignment */
  if (sscanf(buf, "%*s %*d %*d %*d %*d %llu", &iowait) > 0) {
    KFLAG_SETON(KFLAG_IS_LONGSTAT);
  }
}

void determine_longstat_file(void) {
#define MAX_PROCSTAT_LINELEN 255
  FILE *stat_fp;
  static int reported = 0;
  char buf[MAX_PROCSTAT_LINELEN + 1];
  static int stat_initialized = 0;

  /* only execute once */
  if (stat_initialized) return;

  if (!(stat_fp = open_file("/proc/stat", &reported))) return;
  while (!feof(stat_fp) &&
         fgets(buf, MAX_PROCSTAT_LINELEN, stat_fp) != nullptr) {
    if (strncmp(buf, "cpu", 3) == 0) {
      determine_longstat(buf);
      break;
    }
  }
  fclose(stat_fp);
  stat_initialized = 1;
}

// Kernel CPU numbers that are present (populated), in ascending order as
// reported by /sys/devices/system/cpu/present. Used to map a "cpuN" line from
// /proc/stat to its slot in the cpu[]/info.cpu_usage arrays (slot 0 is the
// aggregate, present cores occupy slots 1..cpu_count in this order).
static std::vector<int> presented_cpus;

int cpu_present_slot(const std::vector<int> &present, int cpu_number) {
  auto it = std::lower_bound(present.begin(), present.end(), cpu_number);
  if (it == present.end() || *it != cpu_number) { return -1; }
  return static_cast<int>(it - present.begin()) + 1;
}

void get_cpu_count(void) {
  FILE *stat_fp;
  static int reported = 0;
  char buf[256];
  char *str1, *str2, *token, *subtoken;
  char *saveptr1, *saveptr2;
  int subtoken1 = -1;
  int subtoken2 = -1;

  if (info.cpu_usage) { return; }

  if (!(stat_fp = open_file("/sys/devices/system/cpu/present", &reported))) {
    return;
  }

  presented_cpus.clear();

  while (!feof(stat_fp)) {
    if (fgets(buf, 255, stat_fp) == nullptr) { break; }

    // Do some parsing here to handle skipped cpu numbers.  For example,
    // for an AMD FX(tm)-6350 Six-Core Processor /sys/.../present reports
    // "0,3-7".  I assume that chip is really an 8-core die with two cores
    // disabled...  Presumably you could also get "0,3-4,6", and other
    // combos too...
    for (str1 = buf;; str1 = nullptr) {
      token = strtok_r(str1, ",", &saveptr1);
      if (token == nullptr) break;

      subtoken1 = -1;
      subtoken2 = -1;
      for (str2 = token;; str2 = nullptr) {
        subtoken = strtok_r(str2, "-", &saveptr2);
        if (subtoken == nullptr) break;
        if (subtoken1 < 0)
          subtoken1 = strtol(subtoken, nullptr, 10);
        else
          subtoken2 = strtol(subtoken, nullptr, 10);
      }
      if (subtoken1 < 0) continue;
      if (subtoken2 < 0) subtoken2 = subtoken1;
      for (int core = subtoken1; core <= subtoken2; core++) {
        presented_cpus.push_back(core);
      }
    }
  }
  info.cpu_count = static_cast<unsigned int>(presented_cpus.size());
  info.cpu_usage = (float *)malloc((info.cpu_count + 1) * sizeof(float));

  fclose(stat_fp);
}

#define TMPL_LONGSTAT "%*s %llu %llu %llu %llu %llu %llu %llu %llu"
#define TMPL_SHORTSTAT "%*s %llu %llu %llu %llu"

int update_stat(void) {
  FILE *stat_fp;
  static int reported = 0;
  struct cpu_info *cpu = nullptr;
  char buf[256];
  int i;
  unsigned int idx;
  double curtmp;
  const char *stat_template = nullptr;
  unsigned int malloc_cpu_size = 0;
  extern void *global_cpu;

  static pthread_mutex_t last_stat_update_mutex = PTHREAD_MUTEX_INITIALIZER;
  static double last_stat_update = 0.0;
  float cur_total = 0.0;

  /* since we use wrappers for this function, the update machinery
   * can't eliminate double invocations of this function. Check for
   * them here, otherwise cpu_usage counters are freaking out. */
  pthread_mutex_lock(&last_stat_update_mutex);
  if (last_stat_update == current_update_time) {
    pthread_mutex_unlock(&last_stat_update_mutex);
    return 0;
  }
  last_stat_update = current_update_time;
  pthread_mutex_unlock(&last_stat_update_mutex);

  /* add check for !info.cpu_usage since that mem is freed on a SIGUSR1 */
  if (!cpu_setup || !info.cpu_usage) {
    get_cpu_count();
    cpu_setup = 1;
  }

  if (!stat_template) {
    determine_longstat_file();
    stat_template =
        KFLAG_ISSET(KFLAG_IS_LONGSTAT) ? TMPL_LONGSTAT : TMPL_SHORTSTAT;
  }

  if (global_cpu) {
    cpu = reinterpret_cast<struct cpu_info *>(global_cpu);
  } else {
    malloc_cpu_size = (info.cpu_count + 1) * sizeof(struct cpu_info);
    cpu = (struct cpu_info *)malloc(malloc_cpu_size);
    memset(cpu, 0, malloc_cpu_size);
    global_cpu = cpu;
  }

  if (!(stat_fp = open_file("/proc/stat", &reported))) {
    info.run_threads = 0;
    if (info.cpu_usage) {
      memset(info.cpu_usage, 0, info.cpu_count * sizeof(float));
    }
    return 0;
  }

  /* Reset per-core usage so that cores which are currently offline (and thus
   * absent from /proc/stat) report 0 instead of their last, now-stale value.
   * The aggregate (slot 0) is always present and recomputed below. */
  for (unsigned int c = 1; c <= info.cpu_count; c++) {
    info.cpu_usage[c] = 0.0;
  }

  idx = 0;
  while (!feof(stat_fp)) {
    if (fgets(buf, 255, stat_fp) == nullptr) { break; }

    if (strncmp(buf, "procs_running ", 14) == 0) {
      sscanf(buf, "%*s %hu", &info.run_threads);
    } else if (strncmp(buf, "cpu", 3) == 0) {
      double delta;
      if (isdigit((unsigned char)buf[3])) {
        // Index by the kernel CPU number's position in the present set rather
        // than a running counter, so offline cores (missing from /proc/stat)
        // don't shift the remaining cores into the wrong slots.
        int slot = cpu_present_slot(presented_cpus, atoi(buf + 3));
        if (slot < 0) { continue; }
        idx = static_cast<unsigned int>(slot);
      } else {
        idx = 0;
      }
      if (idx > info.cpu_count) { continue; }
      sscanf(buf, stat_template, &(cpu[idx].cpu_user), &(cpu[idx].cpu_nice),
             &(cpu[idx].cpu_system), &(cpu[idx].cpu_idle),
             &(cpu[idx].cpu_iowait), &(cpu[idx].cpu_irq),
             &(cpu[idx].cpu_softirq), &(cpu[idx].cpu_steal));

      cpu[idx].cpu_total = cpu[idx].cpu_user + cpu[idx].cpu_nice +
                           cpu[idx].cpu_system + cpu[idx].cpu_idle +
                           cpu[idx].cpu_iowait + cpu[idx].cpu_irq +
                           cpu[idx].cpu_softirq + cpu[idx].cpu_steal;

      cpu[idx].cpu_active_total =
          cpu[idx].cpu_total - (cpu[idx].cpu_idle + cpu[idx].cpu_iowait);

      delta = current_update_time - last_update_time;

      if (delta <= 0.001) { break; }

      cur_total = (float)(cpu[idx].cpu_total - cpu[idx].cpu_last_total);
      if (cur_total == 0.0) {
        cpu[idx].cpu_val[0] = 1.0;
      } else {
        cpu[idx].cpu_val[0] =
            (cpu[idx].cpu_active_total - cpu[idx].cpu_last_active_total) /
            cur_total;
      }
      curtmp = 0;

      int samples = std::min(cpu_avg_samples.get(*state), CPU_SAMPLE_COUNT);
      for (i = 0; i < samples; i++) { curtmp = curtmp + cpu[idx].cpu_val[i]; }
      info.cpu_usage[idx] = curtmp / samples;

      cpu[idx].cpu_last_total = cpu[idx].cpu_total;
      cpu[idx].cpu_last_active_total = cpu[idx].cpu_active_total;
      for (i = samples - 1; i > 0 && i < CPU_SAMPLE_COUNT; i--) {
        cpu[idx].cpu_val[i] = cpu[idx].cpu_val[i - 1];
      }
    }
  }
  fclose(stat_fp);
  return 0;
}

int update_running_processes(void) {
  update_stat();
  return 0;
}

int update_cpu_usage(void) {
  update_stat();
  return 0;
}

void free_cpu(struct text_object *) { /* no-op */ }

// fscanf() that reads floats with points even if you are using a locale where
// floats are with commas
int fscanf_no_i18n(FILE *stream, const char *format, ...) {
  int returncode;
  va_list ap;

#ifdef BUILD_I18N
  char *oldlocale = strdup(setlocale(LC_NUMERIC, nullptr));

  setlocale(LC_NUMERIC, "C");
#endif
  va_start(ap, format);
  returncode = vfscanf(stream, format, ap);
  va_end(ap);
#ifdef BUILD_I18N
  setlocale(LC_NUMERIC, oldlocale);
  free(oldlocale);
#endif
  return returncode;
}

int update_load_average(void) {
#ifdef HAVE_GETLOADAVG
  if (!prefer_proc) {
    double v[3];

    getloadavg(v, 3);
    info.loadavg[0] = (float)v[0];
    info.loadavg[1] = (float)v[1];
    info.loadavg[2] = (float)v[2];
  } else
#endif
  {
    static int reported = 0;
    FILE *fp;

    if (!(fp = open_file("/proc/loadavg", &reported))) {
      info.loadavg[0] = info.loadavg[1] = info.loadavg[2] = 0.0;
      return 0;
    }
    if (fscanf_no_i18n(fp, "%f %f %f", &info.loadavg[0], &info.loadavg[1],
                       &info.loadavg[2]) < 0)
      info.loadavg[0] = info.loadavg[1] = info.loadavg[2] = 0.0;
    fclose(fp);
  }
  return 0;
}

/***********************************************************/
/***********************************************************/
/***********************************************************/

static int no_dots(const struct dirent *d) {
  if (d->d_name[0] == '.') { return 0; }
  return 1;
}

static int get_first_file_in_a_directory(const char *dir, char *s,
                                         int *reported) {
  struct dirent **namelist;
  int i, n;

  n = scandir(dir, &namelist, no_dots, alphasort);
  if (n < 0) {
    if (!reported || !*reported) {
      LOG_ERROR("scandir for '{}': {}", dir, strerror(errno));
      if (reported) { *reported = 1; }
    }
    return 0;
  } else {
    if (n == 0) { return 0; }

    strncpy(s, namelist[0]->d_name, 255);
    s[255] = '\0';

    for (i = 0; i < n; i++) { free(namelist[i]); }
    free(namelist);

    return 1;
  }
}

/*
 * Convert @dev "0" (hwmon number) or "k10temp" (hwmon name) to "hwmon2/device"
 */
static void get_dev_path(const char *dir, const char *dev, char *out_buf) {
  struct dirent **namelist;
  char path[512] = {'\0'};
  char name[512] = {'\0'};
  bool found = false;
  size_t size;
  int name_fd;
  int i;
  int n;
  int ret;

  /* "0" numbered case */
  ret = sscanf(dev, "%d", &n);
  if (ret == 1) {
    snprintf(out_buf, 256, "hwmon%d/device", n);
    return;
  }

  /* "k10temp" name case, need to search hwmon*->name to find a match */
  n = scandir(dir, &namelist, no_dots, alphasort);
  if (n < 0) {
    LOG_ERROR("scandir for '{}': {}", dir, strerror(errno));
    goto not_found;
  }
  if (n == 0) goto not_found;

  /* Search each hwmon%s/name */
  for (i = 0; i < n; i++) {
    if (found) continue;

    snprintf(path, 512, "%s%s/name", dir, namelist[i]->d_name);
    name_fd = open(path, O_RDONLY);
    if (name_fd < 0) {
      snprintf(path, 512, "%s%s/device/name", dir, namelist[i]->d_name);
      name_fd = open(path, O_RDONLY);
      if (name_fd < 0) continue;
    }
    size = read(name_fd, name, strlen(dev));
    if (size < strlen(dev)) {
      close(name_fd);
      continue;
    }
    ret = strncmp(dev, name, strlen(dev));
    if (!ret) {
      found = true;
      snprintf(out_buf, 512, "%s/device", namelist[i]->d_name);
    }
    close(name_fd);
  }

  /* cleanup */
  for (i = 0; i < n; i++) free(namelist[i]);
  free(namelist);
  if (found) return;

not_found:
  out_buf[0] = '\0';
  return;
}

static int open_sysfs_sensor(const char *dir, const char *dev, const char *type,
                             int n, int *divisor, char *devtype) {
  char path[256];
  char buf[512];
  int fd;
  int divfd;

  memset(buf, 0, sizeof(buf));

  /* if device is nullptr or *, get first */
  if (dev == nullptr || strcmp(dev, "*") == 0) {
    static int reported = 0;

    if (!get_first_file_in_a_directory(dir, buf, &reported)) { return -1; }
    dev = buf;
  }

  if (strcmp(dir, "/sys/class/hwmon/") == 0) {
    if (*buf) {
      /* buf holds result from get_first_file_in_a_directory() above,
       * e.g. "hwmon0" -- append "/device" */
      strncat(buf, "/device", 255 - strnlen(buf, 255));
    } else {
      /*
       * @dev holds device number N or hwmon name as a string,
       * convert them as:
       * "0" -> "hwmon0/device"
       * "k10temp" -> "hwmon2/device", where hwmon2/name is "k10temp"
       */
      get_dev_path(dir, dev, buf);
      /* Not found */
      if (buf[0] == '\0') {
        LOG_ERROR("can't parse device \"{}\"", dev);
        return -1;
      }
      dev = buf;
    }
  }

  /* change vol to in, tempf to temp */
  if (strcmp(type, "vol") == 0) {
    type = "in";
  } else if (strcmp(type, "tempf") == 0) {
    type = "temp";
  } else if (strcmp(type, "temp2") == 0) {
    type = "temp";
  }

  LOG_DEBUG("open_sysfs_sensor: dir={} dev={} type={} n={}", dir, dev, type, n);
  /* construct path */
  snprintf(path, 255, "%s%s/%s%d_input", dir, dev, type, n);

  /* first, attempt to open file in /device */
  fd = open(path, O_RDONLY);
  if (fd < 0) {
    /* if it fails, strip the /device from dev and attempt again */
    size_t len_to_trunc = std::max((size_t)7, strnlen(buf, 255)) - 7;
    buf[len_to_trunc] = 0;
    snprintf(path, 255, "%s%s/%s%d_input", dir, dev, type, n);
    fd = open(path, O_RDONLY);
    if (fd < 0) {
      LOG_ERROR(
          "can't open '{}': {}\nplease check your device or remove this "
          "var from " PACKAGE_NAME,
          path, strerror(errno));
    }
  }

  strncpy(devtype, path, 255);

  if (strcmp(type, "in") == 0 || strcmp(type, "temp") == 0 ||
      strcmp(type, "tempf") == 0) {
    *divisor = 1;
  } else {
    *divisor = 0;
  }
  /* fan does not use *_div as a read divisor */
  if (strcmp("fan", type) == 0) { return fd; }

  /* test if *_div file exist, open it and use it as divisor */
  if (strcmp(type, "tempf") == 0) {
    snprintf(path, 255, "%s%s/%s%d_div", dir, "one", "two", n);
  } else {
    snprintf(path, 255, "%s%s/%s%d_div", dir, dev, type, n);
  }

  divfd = open(path, O_RDONLY);
  if (divfd > 0) {
    /* read integer */
    char divbuf[64];
    int divn;

    divn = read(divfd, divbuf, 63);
    /* should read until n == 0 but I doubt that kernel will give these
     * in multiple pieces. :) */
    if (divn < 0) {
      LOG_ERROR("can't read sysfs sensor divisor from {}", path);
    } else {
      divbuf[divn] = '\0';
      *divisor = strtol(divbuf, nullptr, 10);
    }
    close(divfd);
  }

  return fd;
}

static double get_sysfs_info(int *fd, int divisor, char *devtype, char *type) {
  int val = 0;

  if (*fd <= 0) { return 0; }

  lseek(*fd, 0, SEEK_SET);

  /* read integer */
  {
    char buf[64];
    int n;
    n = read(*fd, buf, 63);
    /* should read until n == 0 but I doubt that kernel will give these
     * in multiple pieces. :) */
    if (n < 0) {
      LOG_ERROR("can't read sysfs '{}': {}", devtype, strerror(errno));
    } else {
      buf[n] = '\0';
      val = strtol(buf, nullptr, 10);
    }
  }

  close(*fd);
  /* open file */
  *fd = open(devtype, O_RDONLY);
  if (*fd < 0) { LOG_ERROR("can't open '{}': {}", devtype, strerror(errno)); }

  /* My dirty hack for computing CPU value
   * Filedil, from forums.gentoo.org */
  /* if (strstr(devtype, "temp1_input") != nullptr) {
    return -15.096 + 1.4893 * (val / 1000.0);
  } */

  /* divide voltage and temperature by 1000 */
  /* or if any other divisor is given, use that */
  if (0 == (strcmp(type, "temp2"))) {
    temp2 = "temp2";
  } else {
    temp2 = "empty";
  }
  if (strcmp(type, "tempf") == 0) {
    if (divisor > 1) {
      return ((val / divisor + 40) * 9.0 / 5) - 40;
    } else if (divisor) {
      return ((val / 1000.0 + 40) * 9.0 / 5) - 40;
    } else {
      return ((val + 40) * 9.0 / 5) - 40;
    }
  } else {
    if (divisor > 1) {
      return val / divisor;
    } else if (divisor) {
      return val / 1000.0;
    } else {
      return val;
    }
  }
}

#define HWMON_RESET() \
  {                   \
    buf1[0] = 0;      \
    factor = 1.0;     \
    offset = 0.0;     \
  }

static void parse_sysfs_sensor(struct text_object *obj, const char *arg,
                               const char *path, const char *type) {
  char buf1[64], buf2[64];
  float factor, offset;
  int n, found = 0;
  struct sysfs *sf;
  memset(buf1, 0, 64);
  memset(buf2, 0, 64);

  if (sscanf(arg, "%63s %d %f %f", buf2, &n, &factor, &offset) == 4)
    found = 1;
  else
    HWMON_RESET();
  if (!found &&
      sscanf(arg, "%63s %63s %d %f %f", buf1, buf2, &n, &factor, &offset) == 5)
    found = 1;
  else if (!found)
    HWMON_RESET();
  if (!found && sscanf(arg, "%63s %63s %d", buf1, buf2, &n) == 3)
    found = 1;
  else if (!found)
    HWMON_RESET();
  if (!found && sscanf(arg, "%63s %d", buf2, &n) == 2)
    found = 1;
  else if (!found)
    HWMON_RESET();

  if (!found) {
    obj_be_plain_text(obj, "fail");
    return;
  }
  LOG_DEBUG("parsed {} args: '{}' '{}' {} {} {}", type, buf1, buf2, n, factor,
            offset);
  sf = (struct sysfs *)malloc(sizeof(struct sysfs));
  memset(sf, 0, sizeof(struct sysfs));
  sf->fd = open_sysfs_sensor(path, (*buf1) ? buf1 : 0, buf2, n, &sf->arg,
                             sf->devtype);
  strncpy(sf->type, buf2, 63);
  sf->factor = factor;
  sf->offset = offset;
  obj->data.opaque = sf;
}

static bool is_sysfs_sensor_type(const char *type) {
  return strcmp(type, "fan") == 0 || strcmp(type, "in") == 0 ||
         strcmp(type, "temp") == 0 || strcmp(type, "temp2") == 0 ||
         strcmp(type, "tempf") == 0 || strcmp(type, "vol") == 0;
}

static const char *scan_sysfs_bar(struct text_object *obj, const char *arg) {
  char maybe_dev[64], maybe_type[64];

  if (arg != nullptr && sscanf(arg, " %63s %63s", maybe_dev, maybe_type) == 2 &&
      is_sysfs_sensor_type(maybe_type)) {
    scan_bar(obj, nullptr, 100);
    return arg;
  }

  return scan_bar(obj, arg, 100);
}

#define PARSER_GENERATOR(name, path)                                     \
  void parse_##name##_sensor(struct text_object *obj, const char *arg) { \
    parse_sysfs_sensor(obj, arg, path, #name);                           \
  }                                                                      \
  void parse_##name##_bar(struct text_object *obj, const char *arg) {    \
    arg = scan_sysfs_bar(obj, arg);                                      \
    parse_sysfs_sensor(obj, arg, path, #name);                           \
  }

PARSER_GENERATOR(i2c, "/sys/bus/i2c/devices/")
PARSER_GENERATOR(hwmon, "/sys/class/hwmon/")
PARSER_GENERATOR(platform, "/sys/bus/platform/devices/")

void print_sysfs_sensor(struct text_object *obj, char *p,
                        unsigned int p_max_size) {
  double r;
  struct sysfs *sf = (struct sysfs *)obj->data.opaque;

  if (!sf || sf->fd < 0) return;

  r = get_sysfs_info(&sf->fd, sf->arg, sf->devtype, sf->type);

  r = r * sf->factor + sf->offset;

  if (0 == (strcmp(temp2, "temp2"))) {
    temp_print(p, p_max_size, r, TEMP_CELSIUS, 0);
  } else if (!strncmp(sf->type, "temp", 4)) {
    temp_print(p, p_max_size, r, TEMP_CELSIUS, 1);
  } else if (r >= 100.0 || r == 0) {
    snprintf(p, p_max_size, "%d", (int)r);
  } else {
    snprintf(p, p_max_size, "%.1f", r);
  }
}

double sysfs_sensor_barval(struct text_object *obj) {
  double r;
  struct sysfs *sf = (struct sysfs *)obj->data.opaque;

  if (!sf || sf->fd < 0) return 0.0;

  r = get_sysfs_info(&sf->fd, sf->arg, sf->devtype, sf->type);

  r = r * sf->factor + sf->offset;

  if (r < 0.0 || r > 100.0) {
    LOG_WARNING("sysfs bar value {} out of [0, 100]; adjust factor/offset", r);
  }
  return std::clamp(r, 0.0, 100.0);
}

void free_sysfs_sensor(struct text_object *obj) {
  struct sysfs *sf = (struct sysfs *)obj->data.opaque;

  if (!sf) return;

  if (sf->fd >= 0) close(sf->fd);
  free_and_zero(obj->data.opaque);
}

#define CPUFREQ_PREFIX "/sys/devices/system/cpu"
#define CPUFREQ_POSTFIX "cpufreq/scaling_cur_freq"

/* return system frequency in MHz (use divisor=1) or GHz (use divisor=1000) */
char get_freq(char *p_client_buffer, size_t client_buffer_size,
              const char *p_format, int divisor, unsigned int cpu) {
  FILE *f;
  static int reported = 0;
  char frequency[32];
  char s[256];
  double freq = 0;

  if (!p_client_buffer || client_buffer_size <= 0 || !p_format ||
      divisor <= 0) {
    return 0;
  }

  if (!prefer_proc) {
    char current_freq_file[128];

    snprintf(current_freq_file, 127, "%s/cpu%d/%s", CPUFREQ_PREFIX, cpu - 1,
             CPUFREQ_POSTFIX);
    f = fopen(current_freq_file, "r");
    if (f) {
      /* if there's a cpufreq /sys node, read the current frequency from
       * this node and divide by 1000 to get Mhz. */
      if (fgets(s, sizeof(s), f)) {
        s[strlen(s) - 1] = '\0';
        freq = strtod(s, nullptr);
      }
      fclose(f);
      snprintf(p_client_buffer, client_buffer_size, p_format,
               (freq / 1000) / divisor);
      return 1;
    }
  }

  // open the CPU information file
  f = open_file("/proc/cpuinfo", &reported);
  if (!f) {
    LOG_ERROR("failed to access '/proc/cpuinfo' at get_freq: {}",
              strerror(errno));
    return 0;
  }

  // read the file
  while (fgets(s, sizeof(s), f) != nullptr) {
#if defined(__i386) || defined(__x86_64)
    // and search for the cpu mhz
    if (strncmp(s, "cpu MHz", 7) == 0 && cpu == 0) {
#else
#if defined(__alpha)
    // different on alpha
    if (strncmp(s, "cycle frequency [Hz]", 20) == 0 && cpu == 0) {
#else
    // this is different on ppc for some reason
    if (strncmp(s, "clock", 5) == 0 && cpu == 0) {
#endif  // defined(__alpha)
#endif  // defined(__i386) || defined(__x86_64)

      // copy just the number
      strncpy(frequency, strchr(s, ':') + 2, 32);
#if defined(__alpha)
      // strip " est.\n"
      frequency[strlen(frequency) - 6] = '\0';
      // kernel reports in Hz
      freq = strtod(frequency, nullptr) / 1000000;
#else
      // strip \n
      frequency[strlen(frequency) - 1] = '\0';
      freq = strtod(frequency, nullptr);
#endif
      break;
    }
    if (strncmp(s, "processor", 9) == 0) {
      cpu--;
      continue;
    }
  }

  fclose(f);
  snprintf(p_client_buffer, client_buffer_size, p_format,
           (float)freq / divisor);
  return 1;
}

#define CPUFREQ_GOVERNOR "cpufreq/scaling_governor"

/* print the CPU scaling governor */
void print_cpugovernor(struct text_object *obj, char *p,
                       unsigned int p_max_size) {
  FILE *fp;
  char buf[64];
  unsigned int cpu = obj->data.i;

  cpu--;
  snprintf(buf, 63, "%s/cpu%d/%s", CPUFREQ_PREFIX, cpu, CPUFREQ_GOVERNOR);
  if ((fp = fopen(buf, "r")) != nullptr) {
    while (fscanf(fp, "%63s", buf) == 1) {
      snprintf(p, p_max_size, "%s", buf);
      fclose(fp);
      return;
    }
  }
}

#define CPUFREQ_VOLTAGE "cpufreq/scaling_voltages"

/* /sys/devices/system/cpu/cpu0/cpufreq/scaling_voltages looks something
 * like this:
# frequency voltage
1800000 1340
1600000 1292
1400000 1100
1200000 988
1000000 1116
800000 1004
600000 988
 * Peter Tarjan (ptarjan@citromail.hu) */

/* return cpu voltage in mV (use divisor=1) or V (use divisor=1000) */
static char get_voltage(char *p_client_buffer, size_t client_buffer_size,
                        const char *p_format, int divisor, unsigned int cpu) {
  FILE *f;
  char s[256];
  int freq = 0;
  int voltage = 0;
  char current_freq_file[128];
  int freq_comp = 0;

  /* build the voltage file name */
  cpu--;
  snprintf(current_freq_file, 127, "%s/cpu%d/%s", CPUFREQ_PREFIX, cpu,
           CPUFREQ_POSTFIX);

  if (!p_client_buffer || client_buffer_size <= 0 || !p_format ||
      divisor <= 0) {
    return 0;
  }

  /* read the current cpu frequency from the /sys node */
  f = fopen(current_freq_file, "r");
  if (f) {
    if (fgets(s, sizeof(s), f)) {
      s[strlen(s) - 1] = '\0';
      freq = strtod(s, nullptr);
    }
    fclose(f);
  } else {
    LOG_ERROR("failed to access '{}' at get_voltage: {}", current_freq_file,
              strerror(errno));
    return 0;
  }

  snprintf(current_freq_file, 127, "%s/cpu%d/%s", CPUFREQ_PREFIX, cpu,
           CPUFREQ_VOLTAGE);

  /* use the current cpu frequency to find the corresponding voltage */
  f = fopen(current_freq_file, "r");

  if (f) {
    while (!feof(f)) {
      char line[256];

      if (fgets(line, 255, f) == nullptr) { break; }
      sscanf(line, "%d %d", &freq_comp, &voltage);
      if (freq_comp == freq) { break; }
    }
    fclose(f);
  } else {
    LOG_ERROR("failed to access '{}' at get_voltage: {}", current_freq_file,
              strerror(errno));
    return 0;
  }
  snprintf(p_client_buffer, client_buffer_size, p_format,
           (float)voltage / divisor);
  return 1;
}

void print_voltage_mv(struct text_object *obj, char *p,
                      unsigned int p_max_size) {
  static int ok = 1;
  if (ok) { ok = get_voltage(p, p_max_size, "%.0f", 1, obj->data.i); }
}

void print_voltage_v(struct text_object *obj, char *p,
                     unsigned int p_max_size) {
  static int ok = 1;
  if (ok) { ok = get_voltage(p, p_max_size, "%'.3f", 1000, obj->data.i); }
}

#define ACPI_FAN_DIR "/proc/acpi/fan/"

void get_acpi_fan(char *p_client_buffer, size_t client_buffer_size) {
  static int reported = 0;
  char buf[512];
  char buf2[512];
  FILE *fp;

  if (!p_client_buffer || client_buffer_size <= 0) { return; }

  /* yeah, slow... :/ */
  if (!get_first_file_in_a_directory(ACPI_FAN_DIR, buf, &reported)) {
    snprintf(p_client_buffer, client_buffer_size, "%s", "no fans?");
    return;
  }

  snprintf(buf2, sizeof(buf2), "%s%.256s/state", ACPI_FAN_DIR, buf);

  fp = open_file(buf2, &reported);
  if (!fp) {
    snprintf(p_client_buffer, client_buffer_size, "%s",
             "can't open fan's state file");
    return;
  }
  memset(buf, 0, sizeof(buf));
  if (fscanf(fp, "%*s %99s", buf) <= 0)
    LOG_ERROR("fscanf: {}", strerror(errno));
  fclose(fp);

  snprintf(p_client_buffer, client_buffer_size, "%s", buf);
}

#define SYSFS_AC_ADAPTER_DIR "/sys/class/power_supply"
#define ACPI_AC_ADAPTER_DIR "/proc/acpi/ac_adapter/"
/* Linux 2.6.25 onwards ac adapter info is in
   /sys/class/power_supply/AC/
   On my system I get the following.
     /sys/class/power_supply/AC/uevent:
     PHYSDEVPATH=/devices/LNXSYSTM:00/device:00/PNP0A08:00/device:01/PNP0C09:00/ACPI0003:00
     PHYSDEVBUS=acpi
     PHYSDEVDRIVER=ac
     POWER_SUPPLY_NAME=AC
     POWER_SUPPLY_TYPE=Mains
     POWER_SUPPLY_ONLINE=1

   Update: it seems the folder name is hardware-dependent. We add an additional
   adapter argument, specifying the folder name.

   Update: on some systems it's /sys/class/power_supply/ADP1 instead of
   /sys/class/power_supply/AC
*/

void get_acpi_ac_adapter(char *p_client_buffer, size_t client_buffer_size,
                         const char *adapter) {
  static int reported = 0;

  char buf[512];
  char buf2[512];
  struct stat sb;
  FILE *fp;

  if (!p_client_buffer || client_buffer_size <= 0) { return; }

  if (adapter)
    snprintf(buf2, sizeof(buf2), "%s/%s/uevent", SYSFS_AC_ADAPTER_DIR, adapter);
  else {
    snprintf(buf2, sizeof(buf2), "%s/AC/uevent", SYSFS_AC_ADAPTER_DIR);
    if (stat(buf2, &sb) == -1)
      snprintf(buf2, sizeof(buf2), "%s/ADP1/uevent", SYSFS_AC_ADAPTER_DIR);
  }
  if (stat(buf2, &sb) == 0)
    fp = open_file(buf2, &reported);
  else
    fp = 0;
  if (fp) {
    /* sysfs processing */
    while (!feof(fp)) {
      if (fgets(buf, sizeof(buf), fp) == nullptr) break;

      if (strncmp(buf, "POWER_SUPPLY_ONLINE=", 20) == 0) {
        int online = 0;
        sscanf(buf, "POWER_SUPPLY_ONLINE=%d", &online);
        snprintf(p_client_buffer, client_buffer_size, "%s-line",
                 (online ? "on" : "off"));
        break;
      }
    }
    fclose(fp);
  } else {
    /* yeah, slow... :/ */
    if (!get_first_file_in_a_directory(ACPI_AC_ADAPTER_DIR, buf, &reported)) {
      snprintf(p_client_buffer, client_buffer_size, "%s", "no ac_adapters?");
      return;
    }

    snprintf(buf2, sizeof(buf2), "%s%.256s/state", ACPI_AC_ADAPTER_DIR, buf);

    fp = open_file(buf2, &reported);
    if (!fp) {
      snprintf(p_client_buffer, client_buffer_size, "%s",
               "No ac adapter found.... where is it?");
      return;
    }
    memset(buf, 0, sizeof(buf));
    if (fscanf(fp, "%*s %99s", buf) <= 0)
      LOG_ERROR("fscanf: {}", strerror(errno));
    fclose(fp);

    snprintf(p_client_buffer, client_buffer_size, "%s", buf);
  }
}

/*
/proc/acpi/thermal_zone/THRM/cooling_mode
cooling mode:            active
/proc/acpi/thermal_zone/THRM/polling_frequency
<polling disabled>
/proc/acpi/thermal_zone/THRM/state
state:                   ok
/proc/acpi/thermal_zone/THRM/temperature
temperature:             45 C
/proc/acpi/thermal_zone/THRM/trip_points
critical (S5):           73 C
passive:                 73 C: tc1=4 tc2=3 tsp=40 devices=0xcdf6e6c0
*/

#define ACPI_THERMAL_ZONE_DEFAULT "thermal_zone0"
#define ACPI_THERMAL_FORMAT "/sys/class/thermal/%s/temp"

int open_acpi_temperature(const char *name) {
  char path[256];
  int fd;

  if (name == nullptr || strcmp(name, "*") == 0) {
    snprintf(path, 255, ACPI_THERMAL_FORMAT, ACPI_THERMAL_ZONE_DEFAULT);
  } else {
    snprintf(path, 255, ACPI_THERMAL_FORMAT, name);
  }

  fd = open(path, O_RDONLY);
  if (fd < 0) { LOG_ERROR("can't open '{}': {}", path, strerror(errno)); }

  return fd;
}

static double last_acpi_temp;
static double last_acpi_temp_time;

// the maximum length of the string inside a ACPI_THERMAL_FORMAT file including
// the ending 0
#define MAXTHERMZONELEN 7

double get_acpi_temperature(int fd) {
  if (fd <= 0) { return 0; }

  /* don't update acpi temperature too often */
  if (current_update_time - last_acpi_temp_time < 11.32) {
    return last_acpi_temp;
  }
  last_acpi_temp_time = current_update_time;

  /* seek to beginning */
  lseek(fd, 0, SEEK_SET);

  /* read */
  {
    char buf[MAXTHERMZONELEN];
    int n;

    n = read(fd, buf, MAXTHERMZONELEN - 1);
    if (n < 0) {
      LOG_ERROR("can't read fd {}: {}", fd, strerror(errno));
    } else {
      buf[n] = '\0';
      sscanf(buf, "%lf", &last_acpi_temp);
      last_acpi_temp /= 1000;
    }
  }

  return last_acpi_temp;
}

/*
hipo@lepakko hipo $ cat /proc/acpi/battery/BAT1/info
present:                 yes
design capacity:         4400 mAh
last full capacity:      4064 mAh
battery technology:      rechargeable
design voltage:          14800 mV
design capacity warning: 300 mAh
design capacity low:     200 mAh
capacity granularity 1:  32 mAh
capacity granularity 2:  32 mAh
model number:            02KT
serial number:           16922
battery type:            LION
OEM info:                SANYO
*/

/*
hipo@lepakko conky $ cat /proc/acpi/battery/BAT1/state
present:                 yes
capacity state:          ok
charging state:          unknown
present rate:            0 mA
remaining capacity:      4064 mAh
present voltage:         16608 mV
*/

/*
2213<@jupetkellari> jupet@lagi-unstable:~$ cat /proc/apm
2213<@jupetkellari> 1.16 1.2 0x03 0x01 0xff 0x10 -1% -1 ?
2213<@jupetkellari> (-1 ollee ei akkua kiinni, koska akku on pydll)
2214<@jupetkellari> jupet@lagi-unstable:~$ cat /proc/apm
2214<@jupetkellari> 1.16 1.2 0x03 0x01 0x03 0x09 98% -1 ?

2238<@jupetkellari> 1.16 1.2 0x03 0x00 0x00 0x01 100% -1 ? ilman verkkovirtaa
2239<@jupetkellari> 1.16 1.2 0x03 0x01 0x00 0x01 99% -1 ? verkkovirralla

2240<@jupetkellari> 1.16 1.2 0x03 0x01 0x03 0x09 100% -1 ? verkkovirralla ja
monitori pll 2241<@jupetkellari> 1.16 1.2 0x03 0x00 0x00 0x01 99% -1 ?
monitori pll mutta ilman verkkovirtaa
*/

/* Kapil Hari Paranjape <kapil@imsc.res.in>
  Linux 2.6.24 onwards battery info is in
  /sys/class/power_supply/BAT0/
  On my system I get the following.
  /sys/class/power_supply/BAT0/uevent:
  PHYSDEVPATH=/devices/LNXSYSTM:00/device:00/PNP0A03:00/device:01/PNP0C09:00/PNP0C0A:00
  PHYSDEVBUS=acpi
  PHYSDEVDRIVER=battery
  POWER_SUPPLY_NAME=BAT0
  POWER_SUPPLY_TYPE=Battery
  POWER_SUPPLY_STATUS=Discharging
  POWER_SUPPLY_PRESENT=1
  POWER_SUPPLY_TECHNOLOGY=Li-ion
  POWER_SUPPLY_VOLTAGE_MIN_DESIGN=10800000
  POWER_SUPPLY_VOLTAGE_NOW=10780000
  POWER_SUPPLY_CURRENT_NOW=13970000
  POWER_SUPPLY_ENERGY_FULL_DESIGN=47510000
  POWER_SUPPLY_ENERGY_FULL=27370000
  POWER_SUPPLY_ENERGY_NOW=11810000
  POWER_SUPPLY_MODEL_NAME=IBM-92P1060
  POWER_SUPPLY_MANUFACTURER=Panasonic
  On some systems POWER_SUPPLY_ENERGY_* is replaced by POWER_SUPPLY_CHARGE_*
*/

/* Tiago Marques Vale <tiagomarquesvale@gmail.com>
  Regarding the comment above, since kernel 2.6.36.1 I have
  POWER_SUPPLY_POWER_NOW instead of POWER_SUPPLY_CURRENT_NOW
  See http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=532000
*/

#define SYSFS_BATTERY_BASE_PATH "/sys/class/power_supply"
#define ACPI_BATTERY_BASE_PATH "/proc/acpi/battery"
#define APM_PATH "/proc/apm"
#define MAX_BATTERY_COUNT 4

static FILE *sysfs_bat_fp[MAX_BATTERY_COUNT] = {nullptr, NULL, NULL, NULL};
static FILE *acpi_bat_fp[MAX_BATTERY_COUNT] = {nullptr, NULL, NULL, NULL};
static FILE *apm_bat_fp[MAX_BATTERY_COUNT] = {nullptr, NULL, NULL, NULL};

static int batteries_initialized = 0;
static char batteries[MAX_BATTERY_COUNT][32];

static int acpi_last_full[MAX_BATTERY_COUNT];
static int acpi_design_capacity[MAX_BATTERY_COUNT];

/* e.g. "charging 75%" */
static char last_battery_str[MAX_BATTERY_COUNT][64];
/* e.g. "3h 15m" */
static char last_battery_time_str[MAX_BATTERY_COUNT][64];

static double last_battery_time[MAX_BATTERY_COUNT];

static int last_battery_perct[MAX_BATTERY_COUNT];
static double last_battery_perct_time[MAX_BATTERY_COUNT];

void init_batteries(void) {
  int idx;

  if (batteries_initialized) { return; }
#ifdef HAVE_OPENMP
#pragma omp parallel for schedule(dynamic, 10)
#endif /* HAVE_OPENMP */
  for (idx = 0; idx < MAX_BATTERY_COUNT; idx++) { batteries[idx][0] = '\0'; }
  batteries_initialized = 1;
}

int get_battery_idx(const char *bat) {
  int idx;

  for (idx = 0; idx < MAX_BATTERY_COUNT; idx++) {
    if (!strlen(batteries[idx]) || !strcmp(batteries[idx], bat)) { break; }
  }

  /* if not found, enter a new entry */
  if (!strlen(batteries[idx])) { snprintf(batteries[idx], 31, "%s", bat); }

  return idx;
}

void set_return_value(char *buffer, unsigned int n, int item, int idx);

void get_battery_stuff(char *buffer, unsigned int n, const char *bat,
                       int item) {
  static int idx, rep = 0, rep1 = 0, rep2 = 0;
  char acpi_path[128];
  char sysfs_path[128];

  snprintf(acpi_path, 127, ACPI_BATTERY_BASE_PATH "/%s/state", bat);
  snprintf(sysfs_path, 127, SYSFS_BATTERY_BASE_PATH "/%s/uevent", bat);

  init_batteries();

  idx = get_battery_idx(bat);

  /* don't update battery too often */
  if (current_update_time - last_battery_time[idx] < 29.5) {
    set_return_value(buffer, n, item, idx);
    return;
  }

  last_battery_time[idx] = current_update_time;

  memset(last_battery_str[idx], 0, sizeof(last_battery_str[idx]));
  memset(last_battery_time_str[idx], 0, sizeof(last_battery_time_str[idx]));

  /* first try SYSFS if that fails try ACPI */

  if (sysfs_bat_fp[idx] == nullptr && acpi_bat_fp[idx] == NULL &&
      apm_bat_fp[idx] == nullptr) {
    sysfs_bat_fp[idx] = open_file(sysfs_path, &rep);
  }

  if (sysfs_bat_fp[idx] == nullptr && acpi_bat_fp[idx] == NULL &&
      apm_bat_fp[idx] == nullptr) {
    acpi_bat_fp[idx] = open_file(acpi_path, &rep1);
  }

  if (sysfs_bat_fp[idx] != nullptr) {
    /* SYSFS */
    int present_rate = -1;
    int remaining_capacity = -1;
    char charging_state[64];
    char present[4];

    strncpy(charging_state, "unknown", 64);

    while (!feof(sysfs_bat_fp[idx])) {
      char buf[256];
      if (fgets(buf, 256, sysfs_bat_fp[idx]) == nullptr) break;

      /* let's just hope units are ok */
      if (strncmp(buf, "POWER_SUPPLY_PRESENT=1", 22) == 0)
        strncpy(present, "yes", 4);
      else if (strncmp(buf, "POWER_SUPPLY_PRESENT=0", 22) == 0)
        strncpy(present, "no", 4);
      else if (strncmp(buf, "POWER_SUPPLY_STATUS=", 20) == 0)
        sscanf(buf, "POWER_SUPPLY_STATUS=%63s", charging_state);
      /* present_rate is not the same as the current flowing now but it
       * is the same value which was used in the past. so we continue the
       * tradition! */
      else if (strncmp(buf, "POWER_SUPPLY_CURRENT_NOW=", 25) == 0)
        sscanf(buf, "POWER_SUPPLY_CURRENT_NOW=%d", &present_rate);
      else if (strncmp(buf, "POWER_SUPPLY_POWER_NOW=", 23) == 0)
        sscanf(buf, "POWER_SUPPLY_POWER_NOW=%d", &present_rate);
      else if (strncmp(buf, "POWER_SUPPLY_ENERGY_NOW=", 24) == 0)
        sscanf(buf, "POWER_SUPPLY_ENERGY_NOW=%d", &remaining_capacity);
      else if (strncmp(buf, "POWER_SUPPLY_ENERGY_FULL=", 25) == 0)
        sscanf(buf, "POWER_SUPPLY_ENERGY_FULL=%d", &acpi_last_full[idx]);
      else if (strncmp(buf, "POWER_SUPPLY_CHARGE_NOW=", 24) == 0)
        sscanf(buf, "POWER_SUPPLY_CHARGE_NOW=%d", &remaining_capacity);
      else if (strncmp(buf, "POWER_SUPPLY_CHARGE_FULL=", 25) == 0)
        sscanf(buf, "POWER_SUPPLY_CHARGE_FULL=%d", &acpi_last_full[idx]);
    }

    fclose(sysfs_bat_fp[idx]);
    sysfs_bat_fp[idx] = nullptr;

    /* Hellf[i]re notes that remaining capacity can exceed acpi_last_full */
    if (remaining_capacity > acpi_last_full[idx])
      acpi_last_full[idx] = remaining_capacity; /* normalize to 100% */

    /* not present */
    if (strcmp(present, "No") == 0) {
      strncpy(last_battery_str[idx], "not present", 64);
    }
    /* charging */
    else if (strcmp(charging_state, "Charging") == 0) {
      if (acpi_last_full[idx] != 0 && present_rate > 0) {
        /* e.g. charging 75% */
        snprintf(
            last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
            "charging %i%%",
            (int)(((float)remaining_capacity / acpi_last_full[idx]) * 100));
        /* e.g. 2h 37m */
        format_seconds(
            last_battery_time_str[idx], sizeof(last_battery_time_str[idx]) - 1,
            (long)(((float)(acpi_last_full[idx] - remaining_capacity) /
                    present_rate) *
                   3600));
      } else if (acpi_last_full[idx] != 0 && present_rate <= 0) {
        snprintf(
            last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
            "charging %d%%",
            (int)(((float)remaining_capacity / acpi_last_full[idx]) * 100));
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      } else {
        strncpy(last_battery_str[idx], "charging",
                sizeof(last_battery_str[idx]) - 1);
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      }
    }
    /* discharging */
    else if (strncmp(charging_state, "Discharging", 64) == 0) {
      if (present_rate > 0) {
        /* e.g. discharging 35% */
        snprintf(
            last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
            "discharging %i%%",
            (int)(((float)remaining_capacity / acpi_last_full[idx]) * 100));
        /* e.g. 1h 12m */
        format_seconds(
            last_battery_time_str[idx], sizeof(last_battery_time_str[idx]) - 1,
            (long)(((float)remaining_capacity / present_rate) * 3600));
      } else if (present_rate == 0) { /* Thanks to Nexox for this one */
        snprintf(last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
                 "full");
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      } else {
        snprintf(
            last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
            "discharging %d%%",
            (int)(((float)remaining_capacity / acpi_last_full[idx]) * 100));
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      }
    }
    /* charged */
    /* thanks to Lukas Zapletal <lzap@seznam.cz> */
    else if (strncmp(charging_state, "Charged", 64) == 0 ||
             strncmp(charging_state, "Full", 64) == 0) {
      /* Below happens with the second battery on my X40,
       * when the second one is empty and the first one
       * being charged. */
      if (remaining_capacity == 0)
        strncpy(last_battery_str[idx], "empty", 64);
      else
        strncpy(last_battery_str[idx], "charged", 64);
    }
    /* unknown, probably full / AC */
    else {
      if (acpi_last_full[idx] != 0 && remaining_capacity != acpi_last_full[idx])
        snprintf(
            last_battery_str[idx], 64, "unknown %d%%",
            (int)(((float)remaining_capacity / acpi_last_full[idx]) * 100));
      else
        strncpy(last_battery_str[idx], "not present", 64);
    }
  } else if (acpi_bat_fp[idx] != nullptr) {
    /* ACPI */
    int present_rate = -1;
    int remaining_capacity = -1;
    char charging_state[64];
    char present[5];

    /* read last full capacity if it's zero */
    if (acpi_last_full[idx] == 0) {
      static int rep3 = 0;
      char path[128];
      FILE *fp;

      snprintf(path, 127, ACPI_BATTERY_BASE_PATH "/%s/info", bat);
      fp = open_file(path, &rep3);
      if (fp != nullptr) {
        while (!feof(fp)) {
          char b[256];

          if (fgets(b, 256, fp) == nullptr) { break; }
          if (sscanf(b, "last full capacity: %d", &acpi_last_full[idx]) != 0) {
            break;
          }
        }

        fclose(fp);
      }
    }

    fseek(acpi_bat_fp[idx], 0, SEEK_SET);

    strncpy(charging_state, "unknown", 8);

    while (!feof(acpi_bat_fp[idx])) {
      char buf[256];

      if (fgets(buf, 256, acpi_bat_fp[idx]) == nullptr) { break; }

      /* let's just hope units are ok */
      if (strncmp(buf, "present:", 8) == 0) {
        sscanf(buf, "present: %4s", present);
      } else if (strncmp(buf, "charging state:", 15) == 0) {
        sscanf(buf, "charging state: %63s", charging_state);
      } else if (strncmp(buf, "present rate:", 13) == 0) {
        sscanf(buf, "present rate: %d", &present_rate);
      } else if (strncmp(buf, "remaining capacity:", 19) == 0) {
        sscanf(buf, "remaining capacity: %d", &remaining_capacity);
      }
    }
    /* Hellf[i]re notes that remaining capacity can exceed acpi_last_full */
    if (remaining_capacity > acpi_last_full[idx]) {
      /* normalize to 100% */
      acpi_last_full[idx] = remaining_capacity;
    }

    /* not present */
    if (strcmp(present, "no") == 0) {
      strncpy(last_battery_str[idx], "not present", 64);
      /* charging */
    } else if (strcmp(charging_state, "charging") == 0) {
      if (acpi_last_full[idx] != 0 && present_rate > 0) {
        /* e.g. charging 75% */
        snprintf(last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
                 "charging %i%%",
                 (int)((remaining_capacity * 100) / acpi_last_full[idx]));
        /* e.g. 2h 37m */
        format_seconds(
            last_battery_time_str[idx], sizeof(last_battery_time_str[idx]) - 1,
            (long)(((acpi_last_full[idx] - remaining_capacity) * 3600) /
                   present_rate));
      } else if (acpi_last_full[idx] != 0 && present_rate <= 0) {
        snprintf(last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
                 "charging %d%%",
                 (int)((remaining_capacity * 100) / acpi_last_full[idx]));
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      } else {
        strncpy(last_battery_str[idx], "charging",
                sizeof(last_battery_str[idx]) - 1);
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      }
      /* discharging */
    } else if (strncmp(charging_state, "discharging", 64) == 0) {
      if (present_rate > 0) {
        /* e.g. discharging 35% */
        snprintf(last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
                 "discharging %i%%",
                 (int)((remaining_capacity * 100) / acpi_last_full[idx]));
        /* e.g. 1h 12m */
        format_seconds(last_battery_time_str[idx],
                       sizeof(last_battery_time_str[idx]) - 1,
                       (long)((remaining_capacity * 3600) / present_rate));
      } else if (present_rate == 0) { /* Thanks to Nexox for this one */
        snprintf(last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
                 "charged");
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      } else {
        snprintf(last_battery_str[idx], sizeof(last_battery_str[idx]) - 1,
                 "discharging %d%%",
                 (int)((remaining_capacity * 100) / acpi_last_full[idx]));
        snprintf(last_battery_time_str[idx],
                 sizeof(last_battery_time_str[idx]) - 1, "%s", "unknown");
      }
      /* charged */
    } else if (strncmp(charging_state, "charged", 64) == 0) {
      /* thanks to Lukas Zapletal <lzap@seznam.cz> */
      /* Below happens with the second battery on my X40,
       * when the second one is empty and the first one being charged. */
      if (remaining_capacity == 0) {
        strncpy(last_battery_str[idx], "empty", 6);
      } else {
        strncpy(last_battery_str[idx], "charged", 8);
      }
      /* unknown, probably full / AC */
    } else {
      if (strncmp(charging_state, "Full", 64) == 0) {
        strncpy(last_battery_str[idx], "charged", 64);
      } else if (acpi_last_full[idx] != 0 &&
                 remaining_capacity != acpi_last_full[idx]) {
        snprintf(last_battery_str[idx], 64, "unknown %d%%",
                 (int)((remaining_capacity * 100) / acpi_last_full[idx]));
      } else {
        strncpy(last_battery_str[idx], "not present", 64);
      }
    }
    fclose(acpi_bat_fp[idx]);
    acpi_bat_fp[idx] = nullptr;
  } else {
    /* APM */
    if (apm_bat_fp[idx] == nullptr) {
      apm_bat_fp[idx] = open_file(APM_PATH, &rep2);
    }

    if (apm_bat_fp[idx] != nullptr) {
      unsigned int ac, status, flag;
      int life;

      if (fscanf(apm_bat_fp[idx], "%*s %*s %*x %x   %x       %x     %d%%", &ac,
                 &status, &flag, &life) <= 0)
        goto read_bat_fp_end;

      if (life == -1) {
        /* could check now that there is ac */
        snprintf(last_battery_str[idx], 64, "%s", "not present");

        /* could check that status == 3 here? */
      } else if (ac && life != 100) {
        snprintf(last_battery_str[idx], 64, "charging %d%%", life);
      } else {
        snprintf(last_battery_str[idx], 64, "%d%%", life);
      }

    read_bat_fp_end:
      /* it seemed to buffer it so file must be closed (or could use
       * syscalls directly but I don't feel like coding it now) */
      fclose(apm_bat_fp[idx]);
      apm_bat_fp[idx] = nullptr;
    }
  }
  set_return_value(buffer, n, item, idx);
}

void set_return_value(char *buffer, unsigned int n, int item, int idx) {
  switch (item) {
    case BATTERY_STATUS:
      snprintf(buffer, n, "%s", last_battery_str[idx]);
      break;
    case BATTERY_TIME:
      snprintf(buffer, n, "%s", last_battery_time_str[idx]);
      break;
    default:
      break;
  }
}

void get_battery_short_status(char *buffer, unsigned int n, const char *bat) {
  get_battery_stuff(buffer, n, bat, BATTERY_STATUS);
  if (0 == strncmp("charging", buffer, 8)) {
    buffer[0] = 'C';
    memmove(buffer + 1, buffer + 8, n - 8);
  } else if (0 == strncmp("discharging", buffer, 11)) {
    buffer[0] = 'D';
    memmove(buffer + 1, buffer + 11, n - 11);
  } else if (0 == strncmp("charged", buffer, 7)) {
    buffer[0] = 'F';
    memmove(buffer + 1, buffer + 7, n - 7);
  } else if (0 == strncmp("not present", buffer, 11)) {
    buffer[0] = 'N';
    memmove(buffer + 1, buffer + 11, n - 11);
  } else if (0 == strncmp("empty", buffer, 5)) {
    buffer[0] = 'E';
    memmove(buffer + 1, buffer + 5, n - 5);
  } else if (0 == strncmp("unknown", buffer, 7)) {
    buffer[0] = 'U';
    memmove(buffer + 1, buffer + 7, n - 7);
  }
  // Otherwise, don't shorten.
}

void get_battery_power_draw(char *buffer, unsigned int n, const char *bat) {
  static int reported_power = 0;
  static int reported_other = 0;
  char path[256];
  char value[256];
  FILE *fp;
  char *ret;

  snprintf(path, 255, SYSFS_BATTERY_BASE_PATH "/%s/power_now", bat);
  fp = open_file(path, &reported_power);
  if (fp != nullptr) {
    ret = fgets(value, 256, fp);
    fclose(fp);

    if (ret != nullptr) {
      double result = strtol(value, NULL, 10) * 1e-6;
      snprintf(buffer, n, "%.1f", result);
      return;
    }
  }

  snprintf(path, 255, SYSFS_BATTERY_BASE_PATH "/%s/current_now", bat);
  fp = open_file(path, &reported_other);
  if (fp == nullptr) return;
  ret = fgets(value, 256, fp);
  fclose(fp);

  if (ret == nullptr) return;
  double result = strtol(value, NULL, 10) * 1e-6;

  snprintf(path, 255, SYSFS_BATTERY_BASE_PATH "/%s/voltage_now", bat);
  fp = open_file(path, &reported_other);
  if (fp == nullptr) return;
  ret = fgets(value, 256, fp);
  fclose(fp);

  if (fp == nullptr) return;
  result *= strtol(value, NULL, 10) * 1e-6;
  snprintf(buffer, n, "%.1f", result);
}

int _get_battery_perct(const char *bat) {
  static int reported = 0;
  int idx;
  char acpi_path[128];
  char sysfs_path[128];
  int remaining_capacity = -1;

  snprintf(acpi_path, 127, ACPI_BATTERY_BASE_PATH "/%s/state", bat);
  snprintf(sysfs_path, 127, SYSFS_BATTERY_BASE_PATH "/%s/uevent", bat);

  idx = get_battery_idx(bat);

  /* don't update battery too often */
  if (current_update_time - last_battery_perct_time[idx] < 30) {
    return last_battery_perct[idx];
  }
  last_battery_perct_time[idx] = current_update_time;

  /* Only check for SYSFS or ACPI */

  if (sysfs_bat_fp[idx] == nullptr && acpi_bat_fp[idx] == NULL &&
      apm_bat_fp[idx] == nullptr) {
    sysfs_bat_fp[idx] = open_file(sysfs_path, &reported);
    reported = 0;
  }

  if (sysfs_bat_fp[idx] == nullptr && acpi_bat_fp[idx] == NULL &&
      apm_bat_fp[idx] == nullptr) {
    acpi_bat_fp[idx] = open_file(acpi_path, &reported);
  }

  if (sysfs_bat_fp[idx] != nullptr) {
    /* SYSFS */
    while (!feof(sysfs_bat_fp[idx])) {
      char buf[256];
      if (fgets(buf, 256, sysfs_bat_fp[idx]) == nullptr) break;

      if (strncmp(buf, "POWER_SUPPLY_CHARGE_NOW=", 24) == 0) {
        sscanf(buf, "POWER_SUPPLY_CHARGE_NOW=%d", &remaining_capacity);
      } else if (strncmp(buf, "POWER_SUPPLY_CHARGE_FULL=", 25) == 0) {
        sscanf(buf, "POWER_SUPPLY_CHARGE_FULL=%d", &acpi_design_capacity[idx]);
      } else if (strncmp(buf, "POWER_SUPPLY_ENERGY_NOW=", 24) == 0) {
        sscanf(buf, "POWER_SUPPLY_ENERGY_NOW=%d", &remaining_capacity);
      } else if (strncmp(buf, "POWER_SUPPLY_ENERGY_FULL=", 25) == 0) {
        sscanf(buf, "POWER_SUPPLY_ENERGY_FULL=%d", &acpi_design_capacity[idx]);
      }
    }

    fclose(sysfs_bat_fp[idx]);
    sysfs_bat_fp[idx] = nullptr;

  } else if (acpi_bat_fp[idx] != nullptr) {
    /* ACPI */
    /* read last full capacity if it's zero */
    if (acpi_design_capacity[idx] == 0) {
      static int rep2;
      char path[128];
      FILE *fp;

      snprintf(path, 127, ACPI_BATTERY_BASE_PATH "/%s/info", bat);
      fp = open_file(path, &rep2);
      if (fp != nullptr) {
        while (!feof(fp)) {
          char b[256];

          if (fgets(b, 256, fp) == nullptr) { break; }
          if (sscanf(b, "last full capacity: %d", &acpi_design_capacity[idx]) !=
              0) {
            break;
          }
        }
        fclose(fp);
      }
    }

    fseek(acpi_bat_fp[idx], 0, SEEK_SET);

    while (!feof(acpi_bat_fp[idx])) {
      char buf[256];

      if (fgets(buf, 256, acpi_bat_fp[idx]) == nullptr) { break; }

      if (buf[0] == 'r') {
        sscanf(buf, "remaining capacity: %d", &remaining_capacity);
      }
    }
  }
  if (remaining_capacity < 0) { return 0; }
  /* compute the battery percentage */
  last_battery_perct[idx] =
      (int)(((float)remaining_capacity / acpi_design_capacity[idx]) * 100);
  if (last_battery_perct[idx] > 100) last_battery_perct[idx] = 100;
  return last_battery_perct[idx];
}

int get_battery_perct(const char *bat) {
  int idx, n = 0, total_capacity = 0, remaining_capacity;
  ;
#define BATTERY_LEN 8
  char battery[BATTERY_LEN];

  init_batteries();

  /* Check if user asked for the mean percentage of all batteries. */
  if (!strcmp(bat, "all")) {
    for (idx = 0; idx < MAX_BATTERY_COUNT; idx++) {
      snprintf(battery, BATTERY_LEN - 1, "BAT%d", idx);
#undef BATTERY_LEN
      remaining_capacity = _get_battery_perct(battery);
      if (remaining_capacity > 0) {
        total_capacity += remaining_capacity;
        n++;
      }
    }

    if (n == 0)
      return 0;
    else
      return total_capacity / n;
  } else {
    return _get_battery_perct(bat);
  }
}

double get_battery_perct_bar(struct text_object *obj) {
  int idx;

  get_battery_perct(obj->data.s);
  idx = get_battery_idx(obj->data.s);
  return last_battery_perct[idx];
}

/* On Apple powerbook and ibook:
$ cat /proc/pmu/battery_0
flags      : 00000013
charge     : 3623
max_charge : 3720
current    : 388
voltage    : 16787
time rem.  : 900
$ cat /proc/pmu/info
PMU driver version     : 2
PMU firmware version   : 0c
AC Power               : 1
Battery count          : 1
*/

/* defines as in <linux/pmu.h> */
#define PMU_BATT_PRESENT 0x00000001
#define PMU_BATT_CHARGING 0x00000002

static FILE *pmu_battery_fp;
static FILE *pmu_info_fp;
static char pb_battery_info[3][32];
static double pb_battery_info_update;

void powerbook_update_status(unsigned int flags, int ac);
void powerbook_update_percentage(long timeval, unsigned int flags, int ac,
                                 int charge, int max_charge);
void powerbook_update_time(long timeval);

#define PMU_PATH "/proc/pmu"
void get_powerbook_batt_info(struct text_object *obj, char *buffer,
                             unsigned int n) {
  static int reported = 0;
  const char *batt_path = PMU_PATH "/battery_0";
  const char *info_path = PMU_PATH "/info";
  unsigned int flags = 0;
  int charge = 0;
  int max_charge = 1;
  int ac = -1;
  long timeval = -1;

  /* don't update battery too often */
  if (current_update_time - pb_battery_info_update < 29.5) {
    snprintf(buffer, n, "%s", pb_battery_info[obj->data.i]);
    return;
  }
  pb_battery_info_update = current_update_time;

  if (pmu_battery_fp == nullptr) {
    pmu_battery_fp = open_file(batt_path, &reported);
    if (pmu_battery_fp == nullptr) { return; }
  }

  rewind(pmu_battery_fp);
  while (!feof(pmu_battery_fp)) {
    char buf[32];

    if (fgets(buf, sizeof(buf), pmu_battery_fp) == nullptr) { break; }

    if (buf[0] == 'f') {
      sscanf(buf, "flags      : %8x", &flags);
    } else if (buf[0] == 'c' && buf[1] == 'h') {
      sscanf(buf, "charge     : %d", &charge);
    } else if (buf[0] == 'm') {
      sscanf(buf, "max_charge : %d", &max_charge);
    } else if (buf[0] == 't') {
      sscanf(buf, "time rem.  : %ld", &timeval);
    }
  }
  pmu_info_fp = open_file(info_path, &reported);
  if (pmu_info_fp == nullptr) { return; }

  rewind(pmu_info_fp);
  while (!feof(pmu_info_fp)) {
    char buf[32];

    if (fgets(buf, sizeof(buf), pmu_info_fp) == nullptr) { break; }
    if (buf[0] == 'A') { sscanf(buf, "AC Power               : %d", &ac); }
  }

  powerbook_update_status(flags, ac);
  powerbook_update_percentage(timeval, flags, ac, charge, max_charge);
  powerbook_update_time(timeval);

  snprintf(buffer, n, "%s", pb_battery_info[obj->data.i]);
}

void powerbook_update_status(unsigned int flags, int ac) {
  /* update status string */
  if ((ac && !(flags & PMU_BATT_PRESENT))) {
    strncpy(pb_battery_info[PB_BATT_STATUS], "AC",
            sizeof(pb_battery_info[PB_BATT_STATUS]));
  } else if (ac && (flags & PMU_BATT_PRESENT) && !(flags & PMU_BATT_CHARGING)) {
    strncpy(pb_battery_info[PB_BATT_STATUS], "charged",
            sizeof(pb_battery_info[PB_BATT_STATUS]));
  } else if ((flags & PMU_BATT_PRESENT) && (flags & PMU_BATT_CHARGING)) {
    strncpy(pb_battery_info[PB_BATT_STATUS], "charging",
            sizeof(pb_battery_info[PB_BATT_STATUS]));
  } else {
    strncpy(pb_battery_info[PB_BATT_STATUS], "discharging",
            sizeof(pb_battery_info[PB_BATT_STATUS]));
  }
}

void powerbook_update_percentage(long timeval, unsigned int flags, int ac,
                                 int charge, int max_charge) {
  /* update percentage string */
  if (timeval == 0 && ac && (flags & PMU_BATT_PRESENT) &&
      !(flags & PMU_BATT_CHARGING)) {
    snprintf(pb_battery_info[PB_BATT_PERCENT],
             sizeof(pb_battery_info[PB_BATT_PERCENT]), "%s", "100%%");
  } else if (timeval == 0) {
    snprintf(pb_battery_info[PB_BATT_PERCENT],
             sizeof(pb_battery_info[PB_BATT_PERCENT]), "%s", "unknown");
  } else {
    snprintf(pb_battery_info[PB_BATT_PERCENT],
             sizeof(pb_battery_info[PB_BATT_PERCENT]), "%d%%",
             (charge * 100) / max_charge);
  }
}

void powerbook_update_time(long timeval) {
  /* update time string */
  if (timeval == 0) { /* fully charged or battery not present */
    snprintf(pb_battery_info[PB_BATT_TIME],
             sizeof(pb_battery_info[PB_BATT_TIME]), "%s", "unknown");
  } else if (timeval < 60 * 60) { /* don't show secs */
    format_seconds_short(pb_battery_info[PB_BATT_TIME],
                         sizeof(pb_battery_info[PB_BATT_TIME]), timeval);
  } else {
    format_seconds(pb_battery_info[PB_BATT_TIME],
                   sizeof(pb_battery_info[PB_BATT_TIME]), timeval);
  }
}

#define ENTROPY_AVAIL_PATH "/proc/sys/kernel/random/entropy_avail"

int get_entropy_avail(unsigned int *val) {
  static int reported = 0;
  FILE *fp;

  if (!(fp = open_file(ENTROPY_AVAIL_PATH, &reported))) return 1;

  if (fscanf(fp, "%u", val) != 1) return 1;

  fclose(fp);
  return 0;
}

#define ENTROPY_POOLSIZE_PATH "/proc/sys/kernel/random/poolsize"

int get_entropy_poolsize(unsigned int *val) {
  static int reported = 0;
  FILE *fp;

  if (!(fp = open_file(ENTROPY_POOLSIZE_PATH, &reported))) return 1;

  if (fscanf(fp, "%u", val) != 1) return 1;

  fclose(fp);
  return 0;
}

void print_disk_protect_queue(struct text_object *obj, char *p,
                              unsigned int p_max_size) {
  FILE *fp;
  char path[128];
  int state;

  snprintf(path, 127, "/sys/block/%s/device/unload_heads", obj->data.s);
  if (access(path, F_OK)) {
    snprintf(path, 127, "/sys/block/%s/queue/protect", obj->data.s);
  }
  if ((fp = fopen(path, "r")) == nullptr) {
    snprintf(p, p_max_size, "%s", "n/a   ");
    return;
  }
  if (fscanf(fp, "%d\n", &state) != 1) {
    fclose(fp);
    snprintf(p, p_max_size, "%s", "failed");
    return;
  }
  fclose(fp);
  snprintf(p, p_max_size, "%s", (state > 0) ? "frozen" : "free  ");
}

std::unordered_map<std::string, bool> dev_list;

/* Same as sf #2942117 but memoized using a linked list */
int is_disk(char *dev) {
  std::string orig(dev);
  std::string syspath("/sys/block/");
  char *slash;

  auto i = dev_list.find(orig);
  if (i != dev_list.end()) return i->second;

  while ((slash = strchr(dev, '/'))) *slash = '!';
  syspath += dev;

  return dev_list[orig] = !(access(syspath.c_str(), F_OK));
}

int update_diskio(void) {
  FILE *fp;
  static int reported = 0;
  char buf[512], devbuf[64];
  unsigned int major, minor;
  int col_count = 0;
  struct diskio_stat *cur;
  unsigned int reads, writes;
  unsigned int total_reads = 0, total_writes = 0;

  stats.current = 0;
  stats.current_read = 0;
  stats.current_write = 0;

  if (!(fp = open_file("/proc/diskstats", &reported))) { return 0; }

  /* read reads and writes from all disks (minor = 0), including cd-roms
   * and floppies, and sum them up */
  while (fgets(buf, 512, fp)) {
    col_count = sscanf(buf, "%u %u %s %*u %*u %u %*u %*u %*u %u", &major,
                       &minor, devbuf, &reads, &writes);
    /* ignore subdevices (they have only 3 matching entries in their line)
     * and virtual devices (LVM, network block devices, RAM disks, Loopback)
     *
     * XXX: ignore devices which are part of a SW RAID (MD_MAJOR) */
    if (col_count == 5 && major != LVM_BLK_MAJOR && major != NBD_MAJOR &&
        major != RAMDISK_MAJOR && major != LOOP_MAJOR && major != DM_MAJOR) {
      /* check needed for kernel >= 2.6.31, see sf #2942117 */
      if (is_disk(devbuf)) {
        total_reads += reads;
        total_writes += writes;
      }
    } else {
      col_count = sscanf(buf, "%u %u %s %*u %u %*u %u", &major, &minor, devbuf,
                         &reads, &writes);
      if (col_count != 5) { continue; }
    }
    cur = stats.next;
    while (cur && strcmp(devbuf, cur->dev)) cur = cur->next;

    if (cur) update_diskio_values(cur, reads, writes);
  }
  update_diskio_values(&stats, total_reads, total_writes);
  fclose(fp);
  return 0;
}

std::optional<std::string> get_kv_field(std::istream &in,
                                        std::string_view key) {
  std::string line;
  while (std::getline(in, line)) {
    // Match "<key>=" at the start of the line.
    if (line.rfind(key, 0) != 0 || line.size() <= key.size() ||
        line[key.size()] != '=') {
      continue;
    }
    std::string value = line.substr(key.size() + 1);

    // Values may be quoted or unquoted; strip surrounding quotes when present.
    if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
      value = value.substr(1, value.size() - 2);
    }

    if (!value.empty()) { return value; }
  }
  return std::nullopt;
}

std::optional<std::string> parse_distribution_from_proc_version(
    const std::string &version) {
  for (size_t from = 1; from < version.size(); ++from) {
    // First braced uppercase text is usually the distribution name:
    if (version[from - 1] == '(' && std::isupper(version[from])) {
      // Capture braced content until version number or closing brace:
      size_t to = from + 1;
      for (; to < version.size() - 1; to++) {
        if (version[to] == ' ' &&
            (std::isdigit(version[to + 1]) || version[to + 1] == ')')) {
          break;
        }
      }
      if (to >= version.size() - 1) {
        // No ending delimiter found.
        break;
      }
      return version.substr(from, to - from);
    }
  }
  return std::nullopt;
}

void print_distribution(struct text_object * /*obj*/, char *p,
                        unsigned int p_max_size) {
  if (p_max_size == 0) return;  // can't do nothing

  const auto set_result = [&](std::string_view value) {
    auto len = std::min<std::size_t>(p_max_size - 1, value.length());
    std::memcpy(p, value.data(), len);
    p[len] = '\0';
  };

  // 1) Try /etc/os-release
  // This file is provided even in some non-systemd distros like Void and
  // Devuan- Minimal distros like "Linux From Scratch" won't have it. But it can
  // be provided to ensure proper output (though it's mostly aesthetic). See:
  // https://www.freedesktop.org/software/systemd/man/latest/os-release.html
  std::ifstream os_rel("/etc/os-release");
  if (!os_rel) {
    // attempt alternative path
    os_rel.open("/usr/lib/os-release");
  }
  if (os_rel) {
    // NAME is "A string identifying the operating system, without a version
    // component, and suitable for presentation to the user."
    if (auto name = get_kv_field(os_rel, "NAME")) {
      set_result(*name);
      return;
    }
  }

  // Alternatives that are already handled by above file:
  // /etc/lsb-release - Debian/Ubuntu only, LSB spec
  // /etc/redhat-release - single string
  // /etc/centos-release - single string e.g. "CentOS Linux release 7.9.2009
  // (Core)" /etc/fedora-release - single string /etc/debian_version - version
  // only /etc/alpine-release - version only /etc/arch-release - single string:
  // "Arch Linux"

  // 2) Fallback: parse /proc/version
  // This file doesn't necessarily have distribution name in it (e.g. on Arch).
  // Though on popular distros it could. This isn't a good fallback.
  std::ifstream proc_version("/proc/version");
  if (proc_version) {
    std::string buff;
    std::getline(proc_version, buff);
    if (auto name = parse_distribution_from_proc_version(buff)) {
      set_result(*name);
      return;
    }
  }

  set_result("Linux");
}

/******************************************
 * Calculate cpu total					  *
 ******************************************/
#define TMPL_SHORTPROC "%*s %llu %llu %llu %llu"
#define TMPL_LONGPROC "%*s %llu %llu %llu %llu %llu %llu %llu %llu"

static unsigned long long calc_cpu_total(void) {
  static unsigned long long previous_total = 0;
  unsigned long long total = 0;
  unsigned long long t = 0;
  int rc;
  int ps;
  char line[BUFFER_LEN] = {0};
  unsigned long long cpu = 0;
  unsigned long long niceval = 0;
  unsigned long long systemval = 0;
  unsigned long long idle = 0;
  unsigned long long iowait = 0;
  unsigned long long irq = 0;
  unsigned long long softirq = 0;
  unsigned long long steal = 0;
  const char *template_ =
      KFLAG_ISSET(KFLAG_IS_LONGSTAT) ? TMPL_LONGPROC : TMPL_SHORTPROC;

  ps = open("/proc/stat", O_RDONLY);
  rc = read(ps, line, BUFFER_LEN - 1);
  close(ps);
  if (rc < 0) { return 0; }

  sscanf(line, template_, &cpu, &niceval, &systemval, &idle, &iowait, &irq,
         &softirq, &steal);
  total = cpu + niceval + systemval + idle + iowait + irq + softirq + steal;

  t = total - previous_total;
  previous_total = total;

  return t;
}

/******************************************
 * Calculate each processes cpu			  *
 ******************************************/

inline static void calc_cpu_each(unsigned long long total) {
  float mul = 100.0;
  if (top_cpu_separate.get(*state)) mul *= info.cpu_count;

  for (struct process *p = first_process; p; p = p->next)
    p->amount = mul * (p->user_time + p->kernel_time) / (float)total;
}

#ifdef BUILD_IOSTATS
static void calc_io_each(void) {
  struct process *p;
  unsigned long long sum = 0;

  for (p = first_process; p; p = p->next) sum += p->read_bytes + p->write_bytes;

  if (sum == 0) sum = 1; /* to avoid having NANs if no I/O occurred */
  for (p = first_process; p; p = p->next)
    p->io_perc = 100.0 * (p->read_bytes + p->write_bytes) / (float)sum;
}
#endif /* BUILD_IOSTATS */

/******************************************
 * Extract information from /proc		  *
 ******************************************/

#define PROCFS_TEMPLATE "/proc/%d/stat"
#define PROCFS_CMDLINE_TEMPLATE "/proc/%d/cmdline"

/* These are the guts that extract information out of /proc.
 * Anyone hoping to port wmtop should look here first. */
static void process_parse_stat(struct process *process) {
  char line[BUFFER_LEN] = {0}, filename[BUFFER_LEN], procname[BUFFER_LEN];
  char cmdline[BUFFER_LEN] = {0}, cmdline_filename[BUFFER_LEN],
       cmdline_procname[BUFFER_LEN];
  char basename[BUFFER_LEN] = {0};
  char tmpstr[BUFFER_LEN] = {0};
  char state[4];
  int ps, cmdline_ps;
  unsigned long user_time = 0;
  unsigned long kernel_time = 0;
  int rc;
  int endl;
  int nice_val;
  char *lparen, *rparen;
  struct stat process_stat;

  snprintf(filename, sizeof(filename), PROCFS_TEMPLATE, process->pid);
  snprintf(cmdline_filename, sizeof(cmdline_filename), PROCFS_CMDLINE_TEMPLATE,
           process->pid);

  ps = open(filename, O_RDONLY);
  if (ps == -1) {
    /* The process must have finished in the last few jiffies! */
    return;
  }

  if (fstat(ps, &process_stat) != 0) {
    close(ps);
    return;
  }
  process->uid = process_stat.st_uid;

  /* Mark process as up-to-date. */
  process->time_stamp = g_time;

  rc = read(ps, line, BUFFER_LEN - 1);
  close(ps);
  if (rc < 0) { return; }

  /* Read /proc/<pid>/cmdline */
  cmdline_ps = open(cmdline_filename, O_RDONLY);
  if (cmdline_ps < 0) {
    /* The process must have finished in the last few jiffies! */
    return;
  }

  endl = read(cmdline_ps, cmdline, BUFFER_LEN - 1);
  close(cmdline_ps);
  if (endl < 0) { return; }

  /* Some processes have null-separated arguments (see proc(5)); let's fix it */
  int i = endl;
  while (i && cmdline[i - 1] == 0) {
    /* Skip past any trailing null characters */
    --i;
  }
  while (i--) {
    /* Replace null character between arguments with a space */
    if (cmdline[i] == 0) { cmdline[i] = ' '; }
  }

  cmdline[endl] = 0;

  /* We want to transform for example "/usr/bin/python program.py" to "python
   * program.py"
   * 1. search for first space
   * 2. search for last / before first space
   * 3. copy string from its position
   */
  char *space_ptr = strchr(cmdline, ' ');
  if (space_ptr == nullptr) {
    strncpy(tmpstr, cmdline, BUFFER_LEN);
  } else {
    long int space_pos = space_ptr - cmdline;
    strncpy(tmpstr, cmdline, space_pos);
    tmpstr[space_pos] = 0;
  }

  char *slash_ptr = strrchr(tmpstr, '/');
  if (slash_ptr == nullptr) {
    strncpy(cmdline_procname, cmdline, BUFFER_LEN);
  } else {
    long int slash_pos = slash_ptr - tmpstr;
    strncpy(cmdline_procname, cmdline + slash_pos + 1,
            BUFFER_LEN - slash_pos - 1);
    cmdline_procname[BUFFER_LEN - slash_pos - 1] = 0;
  }

  /* Extract cpu times from data in /proc filesystem */
  lparen = strchr(line, '(');
  rparen = strrchr(line, ')');
  if (!lparen || !rparen || rparen < lparen) return;  // this should not happen

  rc = MIN((unsigned)(rparen - lparen - 1), sizeof(procname) - 1);
  strncpy(procname, lparen + 1, rc);
  procname[rc] = '\0';
  strncpy(basename, procname, strlen(procname) + 1);

  if (strlen(procname) < strlen(cmdline_procname))
    strncpy(procname, cmdline_procname, strlen(cmdline_procname) + 1);

  rc = sscanf(rparen + 1,
              "%3s %*s %*s %*s %*s %*s %*s %*s %*s %*s %*s %lu "
              "%lu %*s %*s %*s %d %*s %*s %*s %llu %llu",
              state, &process->user_time, &process->kernel_time, &nice_val,
              &process->vsize, &process->rss);
  if (rc < 6) {
    LOG_ERROR("scanning data for {} failed, got only {} fields", procname, rc);
    return;
  }

  if (state[0] == 'R') ++info.run_procs;

  free_and_zero(process->name);
  free_and_zero(process->basename);
  process->name = strndup(procname, text_buffer_size.get(*::state));
  process->basename = strndup(basename, text_buffer_size.get(*::state));
  process->rss *= getpagesize();

  process->total_cpu_time = process->user_time + process->kernel_time;
  if (process->previous_user_time == ULONG_MAX) {
    process->previous_user_time = process->user_time;
  }
  if (process->previous_kernel_time == ULONG_MAX) {
    process->previous_kernel_time = process->kernel_time;
  }

  /* strangely, the values aren't monotonous */
  if (process->previous_user_time > process->user_time)
    process->previous_user_time = process->user_time;

  if (process->previous_kernel_time > process->kernel_time)
    process->previous_kernel_time = process->kernel_time;

  /* store the difference of the user_time */
  user_time = process->user_time - process->previous_user_time;
  kernel_time = process->kernel_time - process->previous_kernel_time;

  /* backup the process->user_time for next time around */
  process->previous_user_time = process->user_time;
  process->previous_kernel_time = process->kernel_time;

  /* store only the difference of the user_time here... */
  process->user_time = user_time;
  process->kernel_time = kernel_time;
}

#ifdef BUILD_IOSTATS
#define PROCFS_TEMPLATE_IO "/proc/%d/io"
static void process_parse_io(struct process *process) {
  static const char *read_bytes_str = "read_bytes:";
  static const char *write_bytes_str = "write_bytes:";

  char line[BUFFER_LEN] = {0}, filename[BUFFER_LEN];
  int ps;
  int rc;
  char *pos, *endpos;
  unsigned long long read_bytes, write_bytes;

  snprintf(filename, sizeof(filename), PROCFS_TEMPLATE_IO, process->pid);

  ps = open(filename, O_RDONLY);
  if (ps < 0) {
    /* The process must have finished in the last few jiffies!
     * Or, the kernel doesn't support I/O accounting.
     */
    return;
  }

  rc = read(ps, line, BUFFER_LEN - 1);
  close(ps);
  if (rc < 0) { return; }

  pos = strstr(line, read_bytes_str);
  if (pos == nullptr) {
    /* these should not happen (unless the format of the file changes) */
    return;
  }
  pos += strlen(read_bytes_str);
  process->read_bytes = strtoull(pos, &endpos, 10);
  if (endpos == pos) { return; }

  pos = strstr(line, write_bytes_str);
  if (pos == nullptr) { return; }
  pos += strlen(write_bytes_str);
  process->write_bytes = strtoull(pos, &endpos, 10);
  if (endpos == pos) { return; }

  if (process->previous_read_bytes == ULLONG_MAX) {
    process->previous_read_bytes = process->read_bytes;
  }
  if (process->previous_write_bytes == ULLONG_MAX) {
    process->previous_write_bytes = process->write_bytes;
  }

  /* store the difference of the byte counts */
  read_bytes = process->read_bytes - process->previous_read_bytes;
  write_bytes = process->write_bytes - process->previous_write_bytes;

  /* backup the counts for next time around */
  process->previous_read_bytes = process->read_bytes;
  process->previous_write_bytes = process->write_bytes;

  /* store only the difference here... */
  process->read_bytes = read_bytes;
  process->write_bytes = write_bytes;
}
#endif /* BUILD_IOSTATS */

/******************************************
 * Get process structure for process pid  *
 ******************************************/

/* This function seems to hog all of the CPU time.
 * I can't figure out why - it doesn't do much. */
static void calculate_stats(struct process *process) {
  /* compute each process cpu usage by reading /proc/<proc#>/stat */
  process_parse_stat(process);

#ifdef BUILD_IOSTATS
  process_parse_io(process);
#endif /* BUILD_IOSTATS */

  /*
   * Check name against the exclusion list
   */
  /* if (process->counted && exclusion_expression &&
   * !regexec(exclusion_expression, process->name, 0, 0, 0))
   * process->counted = 0; */
}

/******************************************
 * Update process table					  *
 ******************************************/

static void update_process_table(void) {
  DIR *dir;
  struct dirent *entry;

  if (!(dir = opendir("/proc"))) { return; }

  info.run_procs = 0;

  /* Get list of processes from /proc directory */
  while ((entry = readdir(dir))) {
    pid_t pid;

    if (sscanf(entry->d_name, "%d", &pid) > 0) {
      /* compute each process cpu usage */
      calculate_stats(get_process(pid));
    }
  }

  closedir(dir);
}

void get_top_info(void) {
  unsigned long long total = 0;

  total = calc_cpu_total(); /* calculate the total of the processor */
  update_process_table();   /* update the table with process list */
  calc_cpu_each(total);     /* and then the percentage for each task */
#ifdef BUILD_IOSTATS
  calc_io_each(); /* percentage of I/O for each task */
#endif            /* BUILD_IOSTATS */
}

/******************************************
 * Check if more than one conky process   *
 * is running                             *
 ******************************************/

bool is_conky_already_running(void) {
  DIR *dir;
  struct dirent *ent;
  char buf[512];
  int instances = 0;

  if (!(dir = opendir("/proc"))) {
    LOG_ERROR("can't open /proc: {}", strerror(errno));
    return false;
  }

  while ((ent = readdir(dir)) != NULL) {
    char *endptr = ent->d_name;
    long lpid = strtol(ent->d_name, &endptr, 10);
    if (*endptr != '\0') { continue; }

    snprintf(buf, sizeof(buf), "/proc/%ld/stat", lpid);
    FILE *fp = fopen(buf, "r");
    if (!fp) { continue; }

    if (fgets(buf, sizeof(buf), fp) != NULL) {
      char *conky = strstr(buf, "(conky)");
      if (conky) { instances++; }
    }
    fclose(fp);
  }

  closedir(dir);
  return instances > 1;
}
