/*
     This file is part of GNUnet.
     Copyright (C) 2008--2013 GNUnet e.V.

     GNUnet is free software: you can redistribute it and/or modify it
     under the terms of the GNU Affero General Public License as published
     by the Free Software Foundation, either version 3 of the License,
     or (at your option) any later version.

     GNUnet 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
     Affero General Public License for more details.

     You should have received a copy of the GNU Affero General Public License
     along with this program.  If not, see <http://www.gnu.org/licenses/>.

     SPDX-License-Identifier: AGPL3.0-or-later
 */

#include "platform.h"
#include "gnunet_util_lib.h"

/*
 * File for parsing top-level /proc entities.
 * Copyright Copyright (C) 1992-1998 by Michael K. Johnson, johnsonm@redhat.com
 * Copyright 1998-2003 Albert Cahalan
 * June 2003, Fabian Frederick, disk and slab info
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */

#define BAD_OPEN_MESSAGE                                        \
  "Error: /proc must be mounted\n"                                \
  "  To mount /proc at boot you need an /etc/fstab line like:\n"  \
  "      proc   /proc   proc    defaults\n"                       \
  "  In the meantime, run \"mount proc /proc -t proc\"\n"

#define STAT_FILE    "/proc/stat"
// static int stat_fd = -1;
#define UPTIME_FILE  "/proc/uptime"
// static int uptime_fd = -1;
#define LOADAVG_FILE "/proc/loadavg"
// static int loadavg_fd = -1;
#define MEMINFO_FILE "/proc/meminfo"
static int meminfo_fd = -1;
#define VMINFO_FILE "/proc/vmstat"
// static int vminfo_fd = -1;

// As of 2.6.24 /proc/meminfo seems to need 888 on 64-bit,
// and would need 1258 if the obsolete fields were there.
static char buf[2048];

/* This macro opens filename only if necessary and seeks to 0 so
 * that successive calls to the functions are more efficient.
 * It also reads the current contents of the file into the global buf.
 */
#define FILE_TO_BUF(filename, fd) do {                           \
    static int local_n;                                         \
    if (fd == -1 && (fd = open (filename, O_RDONLY)) == -1) {    \
      fputs (BAD_OPEN_MESSAGE, stderr);                        \
      fflush (NULL);                                           \
      _exit (102);                                             \
    }                                                           \
    lseek (fd, 0L, SEEK_SET);                                    \
    if ((local_n = read (fd, buf, sizeof buf - 1)) < 0) {        \
      perror (filename);                                       \
      fflush (NULL);                                           \
      _exit (103);                                             \
    }                                                           \
    buf[local_n] = '\0';                                        \
} while (0)


/***********************************************************************/
/*
 * Copyright 1999 by Albert Cahalan; all rights reserved.
 * This file may be used subject to the terms and conditions of the
 * GNU Library General Public License Version 2, or any later version
 * at your option, as published by the Free Software Foundation.
 * 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 Library General Public License for more details.
 */

typedef struct mem_table_struct
{
  const char *name;     /* memory type name */
  unsigned long *slot; /* slot in return struct */
} mem_table_struct;

static int
compare_mem_table_structs (const void *a, const void *b)
{
  return strcmp (((const mem_table_struct *) a)->name, ((const
                                                         mem_table_struct *) b)
                 ->
                 name);
}


/* example data, following junk, with comments added:
 *
 * MemTotal:        61768 kB    old
 * MemFree:          1436 kB    old
 * MemShared:           0 kB    old (now always zero; not calculated)
 * Buffers:          1312 kB    old
 * Cached:          20932 kB    old
 * Active:          12464 kB    new
 * Inact_dirty:      7772 kB    new
 * Inact_clean:      2008 kB    new
 * Inact_target:        0 kB    new
 * Inact_laundry:       0 kB    new, and might be missing too
 * HighTotal:           0 kB
 * HighFree:            0 kB
 * LowTotal:        61768 kB
 * LowFree:          1436 kB
 * SwapTotal:      122580 kB    old
 * SwapFree:        60352 kB    old
 * Inactive:        20420 kB    2.5.41+
 * Dirty:               0 kB    2.5.41+
 * Writeback:           0 kB    2.5.41+
 * Mapped:           9792 kB    2.5.41+
 * Slab:             4564 kB    2.5.41+
 * Committed_AS:     8440 kB    2.5.41+
 * PageTables:        304 kB    2.5.41+
 * ReverseMaps:      5738       2.5.41+
 * SwapCached:          0 kB    2.5.??+
 * HugePages_Total:   220       2.5.??+
 * HugePages_Free:    138       2.5.??+
 * Hugepagesize:     4096 kB    2.5.??+
 */

/* obsolete */
unsigned long kb_main_shared;
/* old but still kicking -- the important stuff */
unsigned long kb_main_buffers;
unsigned long kb_main_cached;
unsigned long kb_main_free;
unsigned long kb_main_total;
unsigned long kb_swap_free;
unsigned long kb_swap_total;
/* recently introduced */
unsigned long kb_high_free;
unsigned long kb_high_total;
unsigned long kb_low_free;
unsigned long kb_low_total;
/* 2.4.xx era */
unsigned long kb_active;
unsigned long kb_inact_laundry;
unsigned long kb_inact_dirty;
unsigned long kb_inact_clean;
unsigned long kb_inact_target;
unsigned long kb_swap_cached;  /* late 2.4 and 2.6+ only */
/* derived values */
unsigned long kb_swap_used;
unsigned long kb_main_used;
/* 2.5.41+ */
unsigned long kb_writeback;
unsigned long kb_slab;
unsigned long nr_reversemaps;
unsigned long kb_committed_as;
unsigned long kb_dirty;
unsigned long kb_inactive;
unsigned long kb_mapped;
unsigned long kb_pagetables;
// seen on a 2.6.x kernel:
static unsigned long kb_vmalloc_chunk;
static unsigned long kb_vmalloc_total;
static unsigned long kb_vmalloc_used;
// seen on 2.6.24-rc6-git12
static unsigned long kb_anon_pages;
static unsigned long kb_bounce;
static unsigned long kb_commit_limit;
static unsigned long kb_nfs_unstable;
static unsigned long kb_swap_reclaimable;
static unsigned long kb_swap_unreclaimable;

void
meminfo (void)
{
  char namebuf[16]; /* big enough to hold any row name */
  mem_table_struct findme = { namebuf, NULL };
  mem_table_struct *found;
  char *head;
  char *tail;
  static const mem_table_struct mem_table[] = {
    { "Active", &kb_active },         // important
    { "AnonPages", &kb_anon_pages },
    { "Bounce", &kb_bounce },
    { "Buffers", &kb_main_buffers },  // important
    { "Cached", &kb_main_cached },    // important
    { "CommitLimit", &kb_commit_limit },
    { "Committed_AS", &kb_committed_as },
    { "Dirty", &kb_dirty },           // kB version of vmstat nr_dirty
    { "HighFree", &kb_high_free },
    { "HighTotal", &kb_high_total },
    { "Inact_clean", &kb_inact_clean },
    { "Inact_dirty", &kb_inact_dirty },
    { "Inact_laundry", &kb_inact_laundry },
    { "Inact_target", &kb_inact_target },
    { "Inactive", &kb_inactive },     // important
    { "LowFree", &kb_low_free },
    { "LowTotal", &kb_low_total },
    { "Mapped", &kb_mapped },         // kB version of vmstat nr_mapped
    { "MemFree", &kb_main_free },     // important
    { "MemShared", &kb_main_shared }, // important, but now gone!
    { "MemTotal", &kb_main_total },   // important
    { "NFS_Unstable", &kb_nfs_unstable },
    { "PageTables", &kb_pagetables }, // kB version of vmstat nr_page_table_pages
    { "ReverseMaps", &nr_reversemaps }, // same as vmstat nr_page_table_pages
    { "SReclaimable", &kb_swap_reclaimable }, // "swap reclaimable" (dentry and inode structures)
    { "SUnreclaim", &kb_swap_unreclaimable },
    { "Slab", &kb_slab },             // kB version of vmstat nr_slab
    { "SwapCached", &kb_swap_cached },
    { "SwapFree", &kb_swap_free },    // important
    { "SwapTotal", &kb_swap_total },  // important
    { "VmallocChunk", &kb_vmalloc_chunk },
    { "VmallocTotal", &kb_vmalloc_total },
    { "VmallocUsed", &kb_vmalloc_used },
    { "Writeback", &kb_writeback },   // kB version of vmstat nr_writeback
  };
  const int mem_table_count = sizeof(mem_table) / sizeof(mem_table_struct);

  FILE_TO_BUF (MEMINFO_FILE, meminfo_fd);

  kb_inactive = ~0UL;

  head = buf;
  for (;;)
  {
    tail = strchr (head, ':');
    if (! tail)
      break;
    *tail = '\0';
    if (strlen (head) >= sizeof(namebuf))
    {
      head = tail + 1;
      goto nextline;
    }
    strcpy (namebuf, head);
    found = bsearch (&findme, mem_table, mem_table_count,
                     sizeof(mem_table_struct), compare_mem_table_structs
                     );
    head = tail + 1;
    if (! found)
      goto nextline;
    *(found->slot) = (unsigned long) strtoull (head, &tail, 10);
nextline:
    tail = strchr (head, '\n');
    if (! tail)
      break;
    head = tail + 1;
  }
  if (! kb_low_total)   /* low==main except with large-memory support */
  {
    kb_low_total = kb_main_total;
    kb_low_free = kb_main_free;
  }
  if (kb_inactive == ~0UL)
  {
    kb_inactive = kb_inact_dirty + kb_inact_clean + kb_inact_laundry;
  }
  kb_swap_used = kb_swap_total - kb_swap_free;
  kb_main_used = kb_main_total - kb_main_free;
}
