/* Calculate the size of physical memory.

   Copyright (C) 2000-2001, 2003, 2005-2006, 2009-2016 Free Software
   Foundation, Inc.

   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/>.  */

/* Written by Paul Eggert.  */

#include <config.h>

#include "physmem.h"

#include <unistd.h>

#if HAVE_SYS_PSTAT_H
# include <sys/pstat.h>
#endif

#if HAVE_SYS_SYSMP_H
# include <sys/sysmp.h>
#endif

#if HAVE_SYS_SYSINFO_H
# include <sys/sysinfo.h>
#endif

#if HAVE_MACHINE_HAL_SYSINFO_H
# include <machine/hal_sysinfo.h>
#endif

#if HAVE_SYS_TABLE_H
# include <sys/table.h>
#endif

#include <sys/types.h>

#if HAVE_SYS_PARAM_H
# include <sys/param.h>
#endif

#if HAVE_SYS_SYSCTL_H
# include <sys/sysctl.h>
#endif

#if HAVE_SYS_SYSTEMCFG_H
# include <sys/systemcfg.h>
#endif

#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
/*  MEMORYSTATUSEX is missing from older windows headers, so define
    a local replacement.  */
typedef struct
{
  DWORD dwLength;
  DWORD dwMemoryLoad;
  DWORDLONG ullTotalPhys;
  DWORDLONG ullAvailPhys;
  DWORDLONG ullTotalPageFile;
  DWORDLONG ullAvailPageFile;
  DWORDLONG ullTotalVirtual;
  DWORDLONG ullAvailVirtual;
  DWORDLONG ullAvailExtendedVirtual;
} lMEMORYSTATUSEX;
typedef WINBOOL (WINAPI *PFN_MS_EX) (lMEMORYSTATUSEX*);
#endif

#define ARRAY_SIZE(a) (sizeof (a) / sizeof ((a)[0]))

/* Return the total amount of physical memory.  */
double
physmem_total (void)
{
#if defined _SC_PHYS_PAGES && defined _SC_PAGESIZE
  { /* This works on linux-gnu, solaris2 and cygwin.  */
    double pages = sysconf (_SC_PHYS_PAGES);
    double pagesize = sysconf (_SC_PAGESIZE);
    if (0 <= pages && 0 <= pagesize)
      return pages * pagesize;
  }
#endif

#if HAVE_SYSINFO && HAVE_STRUCT_SYSINFO_MEM_UNIT
  { /* This works on linux.  */
    struct sysinfo si;
    if (sysinfo(&si) == 0)
      return (double) si.totalram * si.mem_unit;
  }
#endif

#if HAVE_PSTAT_GETSTATIC
  { /* This works on hpux11.  */
    struct pst_static pss;
    if (0 <= pstat_getstatic (&pss, sizeof pss, 1, 0))
      {
        double pages = pss.physical_memory;
        double pagesize = pss.page_size;
        if (0 <= pages && 0 <= pagesize)
          return pages * pagesize;
      }
  }
#endif

#if HAVE_SYSMP && defined MP_SAGET && defined MPSA_RMINFO && defined _SC_PAGESIZE
  { /* This works on irix6. */
    struct rminfo realmem;
    if (sysmp (MP_SAGET, MPSA_RMINFO, &realmem, sizeof realmem) == 0)
      {
        double pagesize = sysconf (_SC_PAGESIZE);
        double pages = realmem.physmem;
        if (0 <= pages && 0 <= pagesize)
          return pages * pagesize;
      }
  }
#endif

#if HAVE_GETSYSINFO && defined GSI_PHYSMEM
  { /* This works on Tru64 UNIX V4/5.  */
    int physmem;

    if (getsysinfo (GSI_PHYSMEM, (caddr_t) &physmem, sizeof (physmem),
                    NULL, NULL, NULL) == 1)
      {
        double kbytes = physmem;

        if (0 <= kbytes)
          return kbytes * 1024.0;
      }
  }
#endif

#if HAVE_SYSCTL && defined HW_PHYSMEM
  { /* This works on *bsd and darwin.  */
    unsigned int physmem;
    size_t len = sizeof physmem;
    static int mib[2] = { CTL_HW, HW_PHYSMEM };

    if (sysctl (mib, ARRAY_SIZE (mib), &physmem, &len, NULL, 0) == 0
        && len == sizeof (physmem))
      return (double) physmem;
  }
#endif

#if HAVE__SYSTEM_CONFIGURATION
  /* This works on AIX.  */
  return _system_configuration.physmem;
#endif

#if defined _WIN32
  { /* this works on windows */
    PFN_MS_EX pfnex;
    HMODULE h = GetModuleHandle ("kernel32.dll");

    if (!h)
      return 0.0;

    /*  Use GlobalMemoryStatusEx if available.  */
    if ((pfnex = (PFN_MS_EX) GetProcAddress (h, "GlobalMemoryStatusEx")))
      {
        lMEMORYSTATUSEX lms_ex;
        lms_ex.dwLength = sizeof lms_ex;
        if (!pfnex (&lms_ex))
          return 0.0;
        return (double) lms_ex.ullTotalPhys;
      }

    /*  Fall back to GlobalMemoryStatus which is always available.
        but returns wrong results for physical memory > 4GB.  */
    else
      {
        MEMORYSTATUS ms;
        GlobalMemoryStatus (&ms);
        return (double) ms.dwTotalPhys;
      }
  }
#endif

  /* Guess 64 MB.  It's probably an older host, so guess small.  */
  return 64 * 1024 * 1024;
}

/* Return the amount of physical memory available.  */
double
physmem_available (void)
{
#if defined _SC_AVPHYS_PAGES && defined _SC_PAGESIZE
  { /* This works on linux-gnu, solaris2 and cygwin.  */
    double pages = sysconf (_SC_AVPHYS_PAGES);
    double pagesize = sysconf (_SC_PAGESIZE);
    if (0 <= pages && 0 <= pagesize)
      return pages * pagesize;
  }
#endif

#if HAVE_SYSINFO && HAVE_STRUCT_SYSINFO_MEM_UNIT
  { /* This works on linux.  */
    struct sysinfo si;
    if (sysinfo(&si) == 0)
      return ((double) si.freeram + si.bufferram) * si.mem_unit;
  }
#endif

#if HAVE_PSTAT_GETSTATIC && HAVE_PSTAT_GETDYNAMIC
  { /* This works on hpux11.  */
    struct pst_static pss;
    struct pst_dynamic psd;
    if (0 <= pstat_getstatic (&pss, sizeof pss, 1, 0)
        && 0 <= pstat_getdynamic (&psd, sizeof psd, 1, 0))
      {
        double pages = psd.psd_free;
        double pagesize = pss.page_size;
        if (0 <= pages && 0 <= pagesize)
          return pages * pagesize;
      }
  }
#endif

#if HAVE_SYSMP && defined MP_SAGET && defined MPSA_RMINFO && defined _SC_PAGESIZE
  { /* This works on irix6. */
    struct rminfo realmem;
    if (sysmp (MP_SAGET, MPSA_RMINFO, &realmem, sizeof realmem) == 0)
      {
        double pagesize = sysconf (_SC_PAGESIZE);
        double pages = realmem.availrmem;
        if (0 <= pages && 0 <= pagesize)
          return pages * pagesize;
      }
  }
#endif

#if HAVE_TABLE && defined TBL_VMSTATS
  { /* This works on Tru64 UNIX V4/5.  */
    struct tbl_vmstats vmstats;

    if (table (TBL_VMSTATS, 0, &vmstats, 1, sizeof (vmstats)) == 1)
      {
        double pages = vmstats.free_count;
        double pagesize = vmstats.pagesize;

        if (0 <= pages && 0 <= pagesize)
          return pages * pagesize;
      }
  }
#endif

#if HAVE_SYSCTL && defined HW_USERMEM
  { /* This works on *bsd and darwin.  */
    unsigned int usermem;
    size_t len = sizeof usermem;
    static int mib[2] = { CTL_HW, HW_USERMEM };

    if (sysctl (mib, ARRAY_SIZE (mib), &usermem, &len, NULL, 0) == 0
        && len == sizeof (usermem))
      return (double) usermem;
  }
#endif

#if defined _WIN32
  { /* this works on windows */
    PFN_MS_EX pfnex;
    HMODULE h = GetModuleHandle ("kernel32.dll");

    if (!h)
      return 0.0;

    /*  Use GlobalMemoryStatusEx if available.  */
    if ((pfnex = (PFN_MS_EX) GetProcAddress (h, "GlobalMemoryStatusEx")))
      {
        lMEMORYSTATUSEX lms_ex;
        lms_ex.dwLength = sizeof lms_ex;
        if (!pfnex (&lms_ex))
          return 0.0;
        return (double) lms_ex.ullAvailPhys;
      }

    /*  Fall back to GlobalMemoryStatus which is always available.
        but returns wrong results for physical memory > 4GB  */
    else
      {
        MEMORYSTATUS ms;
        GlobalMemoryStatus (&ms);
        return (double) ms.dwAvailPhys;
      }
  }
#endif

  /* Guess 25% of physical memory.  */
  return physmem_total () / 4;
}


#if DEBUG

# include <stdio.h>
# include <stdlib.h>

int
main (void)
{
  printf ("%12.f %12.f\n", physmem_total (), physmem_available ());
  exit (0);
}

#endif /* DEBUG */

/*
Local Variables:
compile-command: "gcc -DDEBUG -g -O -Wall -W physmem.c"
End:
*/
