diff options
Diffstat (limited to 'procfs/rootdir.c')
-rw-r--r-- | procfs/rootdir.c | 661 |
1 files changed, 661 insertions, 0 deletions
diff --git a/procfs/rootdir.c b/procfs/rootdir.c new file mode 100644 index 00000000..0b131192 --- /dev/null +++ b/procfs/rootdir.c @@ -0,0 +1,661 @@ +/* Hurd /proc filesystem, permanent files of the root directory. + Copyright (C) 2010,13 Free Software Foundation, Inc. + + This file is part of the GNU Hurd. + + The GNU Hurd 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 2, or (at + your option) any later version. + + The GNU Hurd 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, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ + +#include <mach/gnumach.h> +#include <mach/vm_param.h> +#include <mach/vm_statistics.h> +#include <mach/vm_cache_statistics.h> +#include <mach/default_pager.h> +#include <mach_debug/mach_debug_types.h> +#include <hurd/paths.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/time.h> +#include <sys/utsname.h> +#include <sys/stat.h> +#include <argz.h> +#include <ps.h> +#include "procfs.h" +#include "procfs_dir.h" +#include "main.h" + +#include "mach_debug_U.h" + +/* This implements a directory node with the static files in /proc. + NB: the libps functions for host information return static storage; + using them would require locking and as a consequence it would be + more complicated, not simpler. */ + + +/* Helper functions */ + +/* We get the boot time by using that of the kernel process. */ +static error_t +get_boottime (struct ps_context *pc, struct timeval *tv) +{ + struct proc_stat *ps; + error_t err; + + err = _proc_stat_create (opt_kernel_pid, pc, &ps); + if (err) + return err; + + err = proc_stat_set_flags (ps, PSTAT_TASK_BASIC); + if (err || !(proc_stat_flags (ps) & PSTAT_TASK_BASIC)) + err = EIO; + + if (! err) + { + task_basic_info_t tbi = proc_stat_task_basic_info (ps); + tv->tv_sec = tbi->creation_time.seconds; + tv->tv_usec = tbi->creation_time.microseconds; + } + + _proc_stat_free (ps); + return err; +} + +/* We get the idle time by querying the kernel's idle thread. */ +static error_t +get_idletime (struct ps_context *pc, struct timeval *tv) +{ + struct proc_stat *ps, *pst; + thread_basic_info_t tbi; + error_t err; + int i; + + err = _proc_stat_create (opt_kernel_pid, pc, &ps); + if (err) + return err; + + pst = NULL, tbi = NULL; + + err = proc_stat_set_flags (ps, PSTAT_NUM_THREADS); + if (err || !(proc_stat_flags (ps) & PSTAT_NUM_THREADS)) + { + err = EIO; + goto out; + } + + /* Look for the idle thread */ + for (i=0; !tbi || !(tbi->flags & TH_FLAGS_IDLE); i++) + { + if (pst) + _proc_stat_free (pst); + + pst = NULL, tbi = NULL; + if (i >= proc_stat_num_threads (ps)) + { + err = ESRCH; + goto out; + } + + err = proc_stat_thread_create (ps, i, &pst); + if (err) + continue; + + err = proc_stat_set_flags (pst, PSTAT_THREAD_BASIC); + if (err || ! (proc_stat_flags (pst) & PSTAT_THREAD_BASIC)) + continue; + + tbi = proc_stat_thread_basic_info (pst); + } + + /* We found it! */ + tv->tv_sec = tbi->system_time.seconds; + tv->tv_usec = tbi->system_time.microseconds; + err = 0; + +out: + if (pst) _proc_stat_free (pst); + _proc_stat_free (ps); + return err; +} + +static error_t +get_swapinfo (default_pager_info_t *info) +{ + mach_port_t defpager; + error_t err; + + defpager = file_name_lookup (_SERVERS_DEFPAGER, O_READ, 0); + if (defpager == MACH_PORT_NULL) + return errno; + + err = default_pager_info (defpager, info); + mach_port_deallocate (mach_task_self (), defpager); + + return err; +} + + +/* Content generators */ + +static error_t +rootdir_gc_version (void *hook, char **contents, ssize_t *contents_len) +{ + struct utsname uts; + int r; + + r = uname (&uts); + if (r < 0) + return errno; + + *contents_len = asprintf (contents, + "Linux version 2.6.1 (%s %s %s %s)\n", + uts.sysname, uts.release, uts.version, uts.machine); + + return 0; +} + +static error_t +rootdir_gc_uptime (void *hook, char **contents, ssize_t *contents_len) +{ + struct timeval time, boottime, idletime; + double up_secs, idle_secs; + error_t err; + + err = gettimeofday (&time, NULL); + if (err < 0) + return errno; + + err = get_boottime (hook, &boottime); + if (err) + return err; + + err = get_idletime (hook, &idletime); + if (err) + return err; + + timersub (&time, &boottime, &time); + up_secs = (time.tv_sec * 1000000. + time.tv_usec) / 1000000.; + idle_secs = (idletime.tv_sec * 1000000. + idletime.tv_usec) / 1000000.; + + /* The second field is the total idle time. As far as I know we don't + keep track of it. However, procps uses it to compute "USER_HZ", and + proc(5) specifies that it should be equal to USER_HZ times the idle value + in ticks from /proc/stat. So we assume a completely idle system both here + and there to make that work. */ + *contents_len = asprintf (contents, "%.2lf %.2lf\n", up_secs, idle_secs); + + return 0; +} + +static error_t +rootdir_gc_stat (void *hook, char **contents, ssize_t *contents_len) +{ + struct timeval boottime, time, idletime; + struct vm_statistics vmstats; + unsigned long up_ticks, idle_ticks; + error_t err; + + err = gettimeofday (&time, NULL); + if (err < 0) + return errno; + + err = get_boottime (hook, &boottime); + if (err) + return err; + + err = get_idletime (hook, &idletime); + if (err) + return err; + + err = vm_statistics (mach_task_self (), &vmstats); + if (err) + return EIO; + + timersub (&time, &boottime, &time); + up_ticks = opt_clk_tck * (time.tv_sec * 1000000. + time.tv_usec) / 1000000.; + idle_ticks = opt_clk_tck * (idletime.tv_sec * 1000000. + idletime.tv_usec) / 1000000.; + + *contents_len = asprintf (contents, + "cpu %lu 0 0 %lu 0 0 0 0 0\n" + "cpu0 %lu 0 0 %lu 0 0 0 0 0\n" + "intr 0\n" + "page %d %d\n" + "btime %lu\n", + up_ticks - idle_ticks, idle_ticks, + up_ticks - idle_ticks, idle_ticks, + vmstats.pageins, vmstats.pageouts, + boottime.tv_sec); + + return 0; +} + +static error_t +rootdir_gc_loadavg (void *hook, char **contents, ssize_t *contents_len) +{ + host_load_info_data_t hli; + mach_msg_type_number_t cnt; + error_t err; + + cnt = HOST_LOAD_INFO_COUNT; + err = host_info (mach_host_self (), HOST_LOAD_INFO, (host_info_t) &hli, &cnt); + if (err) + return err; + + assert (cnt == HOST_LOAD_INFO_COUNT); + *contents_len = asprintf (contents, + "%.2f %.2f %.2f 1/0 0\n", + hli.avenrun[0] / (double) LOAD_SCALE, + hli.avenrun[1] / (double) LOAD_SCALE, + hli.avenrun[2] / (double) LOAD_SCALE); + + return 0; +} + +static error_t +rootdir_gc_meminfo (void *hook, char **contents, ssize_t *contents_len) +{ + host_basic_info_data_t hbi; + mach_msg_type_number_t cnt; + struct vm_statistics vmstats; + struct vm_cache_statistics cache_stats; + default_pager_info_t swap; + error_t err; + + err = vm_statistics (mach_task_self (), &vmstats); + if (err) + return EIO; + + err = vm_cache_statistics (mach_task_self (), &cache_stats); + if (err) + return EIO; + + cnt = HOST_BASIC_INFO_COUNT; + err = host_info (mach_host_self (), HOST_BASIC_INFO, (host_info_t) &hbi, &cnt); + if (err) + return err; + + err = get_swapinfo (&swap); + if (err) + return err; + + assert (cnt == HOST_BASIC_INFO_COUNT); + *contents_len = asprintf (contents, + "MemTotal: %14lu kB\n" + "MemFree: %14lu kB\n" + "Buffers: %14lu kB\n" + "Cached: %14lu kB\n" + "Active: %14lu kB\n" + "Inactive: %14lu kB\n" + "Mlocked: %14lu kB\n" + "SwapTotal:%14lu kB\n" + "SwapFree: %14lu kB\n" + , + (long unsigned) hbi.memory_size / 1024, + (long unsigned) vmstats.free_count * PAGE_SIZE / 1024, + 0UL, + (long unsigned) cache_stats.cache_count * PAGE_SIZE / 1024, + (long unsigned) vmstats.active_count * PAGE_SIZE / 1024, + (long unsigned) vmstats.inactive_count * PAGE_SIZE / 1024, + (long unsigned) vmstats.wire_count * PAGE_SIZE / 1024, + (long unsigned) swap.dpi_total_space / 1024, + (long unsigned) swap.dpi_free_space / 1024); + + return 0; +} + +static error_t +rootdir_gc_vmstat (void *hook, char **contents, ssize_t *contents_len) +{ + host_basic_info_data_t hbi; + mach_msg_type_number_t cnt; + struct vm_statistics vmstats; + error_t err; + + err = vm_statistics (mach_task_self (), &vmstats); + if (err) + return EIO; + + cnt = HOST_BASIC_INFO_COUNT; + err = host_info (mach_host_self (), HOST_BASIC_INFO, (host_info_t) &hbi, &cnt); + if (err) + return err; + + assert (cnt == HOST_BASIC_INFO_COUNT); + *contents_len = asprintf (contents, + "nr_free_pages %lu\n" + "nr_inactive_anon %lu\n" + "nr_active_anon %lu\n" + "nr_inactive_file %lu\n" + "nr_active_file %lu\n" + "nr_unevictable %lu\n" + "nr_mlock %lu\n" + "pgpgin %lu\n" + "pgpgout %lu\n" + "pgfault %lu\n", + (long unsigned) vmstats.free_count, + /* FIXME: how can we distinguish the anon/file pages? Maybe we can + ask the default pager how many it manages? */ + (long unsigned) vmstats.inactive_count, + (long unsigned) vmstats.active_count, + (long unsigned) 0, + (long unsigned) 0, + (long unsigned) vmstats.wire_count, + (long unsigned) vmstats.wire_count, + (long unsigned) vmstats.pageins, + (long unsigned) vmstats.pageouts, + (long unsigned) vmstats.faults); + + return 0; +} + +static error_t +rootdir_gc_cmdline (void *hook, char **contents, ssize_t *contents_len) +{ + struct ps_context *pc = hook; + struct proc_stat *ps; + error_t err; + + err = _proc_stat_create (opt_kernel_pid, pc, &ps); + if (err) + return EIO; + + err = proc_stat_set_flags (ps, PSTAT_ARGS); + if (err || ! (proc_stat_flags (ps) & PSTAT_ARGS)) + { + err = EIO; + goto out; + } + + *contents_len = proc_stat_args_len (ps); + *contents = malloc (*contents_len); + if (! *contents) + { + err = ENOMEM; + goto out; + } + + memcpy (*contents, proc_stat_args (ps), *contents_len); + argz_stringify (*contents, *contents_len, ' '); + (*contents)[*contents_len - 1] = '\n'; + +out: + _proc_stat_free (ps); + return err; +} + +static int +rootdir_fakeself_exists (void *dir_hook, const void *entry_hook) +{ + return opt_fake_self >= 0; +} + +static error_t +rootdir_gc_fakeself (void *hook, char **contents, ssize_t *contents_len) +{ + *contents_len = asprintf (contents, "%d", opt_fake_self); + return 0; +} + +/* The mtab translator to use by default for the "mounts" node. */ +#define MTAB_TRANSLATOR "/hurd/mtab" + +static struct node *rootdir_mounts_node; +static pthread_spinlock_t rootdir_mounts_node_lock = + PTHREAD_SPINLOCK_INITIALIZER; + +static struct node * +rootdir_mounts_make_node (void *dir_hook, const void *entry_hook) +{ + struct node *np, *prev; + + pthread_spin_lock (&rootdir_mounts_node_lock); + np = rootdir_mounts_node; + pthread_spin_unlock (&rootdir_mounts_node_lock); + + if (np != NULL) + { + netfs_nref (np); + return np; + } + + np = procfs_make_node (entry_hook, dir_hook); + if (np == NULL) + return NULL; + + procfs_node_chtype (np, S_IFREG | S_IPTRANS); + procfs_node_chmod (np, 0444); + + pthread_spin_lock (&rootdir_mounts_node_lock); + prev = rootdir_mounts_node; + if (rootdir_mounts_node == NULL) + rootdir_mounts_node = np; + pthread_spin_unlock (&rootdir_mounts_node_lock); + + if (prev != NULL) + { + procfs_cleanup (np); + np = prev; + } + + return np; +} + +static error_t +rootdir_mounts_get_translator (void *hook, char **argz, size_t *argz_len) +{ + static const char const mtab_argz[] = MTAB_TRANSLATOR "\0/"; + + *argz = malloc (sizeof mtab_argz); + if (! *argz) + return ENOMEM; + + memcpy (*argz, mtab_argz, sizeof mtab_argz); + *argz_len = sizeof mtab_argz; + return 0; +} + +static int +rootdir_mounts_exists (void *dir_hook, const void *entry_hook) +{ + static int translator_exists = -1; + if (translator_exists == -1) + translator_exists = access (MTAB_TRANSLATOR, F_OK|X_OK) == 0; + return translator_exists; +} + +static error_t +rootdir_gc_slabinfo (void *hook, char **contents, ssize_t *contents_len) +{ + error_t err; + FILE *m; + const char header[] = + "cache obj slab bufs objs bufs" + " total reclaimable\n" + "name flags size size /slab usage count" + " memory memory\n"; + cache_info_array_t cache_info; + size_t mem_usage, mem_reclaimable, mem_total, mem_total_reclaimable; + mach_msg_type_number_t cache_info_count; + int i; + + cache_info = NULL; + cache_info_count = 0; + + err = host_slab_info (mach_host_self(), &cache_info, &cache_info_count); + if (err) + return err; + + m = open_memstream (contents, contents_len); + if (m == NULL) + { + err = ENOMEM; + goto out; + } + + fprintf (m, "%s", header); + + mem_total = 0; + mem_total_reclaimable = 0; + + for (i = 0; i < cache_info_count; i++) + { + mem_usage = (cache_info[i].nr_slabs * cache_info[i].slab_size) + >> 10; + mem_total += mem_usage; + mem_reclaimable = (cache_info[i].flags & CACHE_FLAGS_NO_RECLAIM) + ? 0 : (cache_info[i].nr_free_slabs + * cache_info[i].slab_size) >> 10; + mem_total_reclaimable += mem_reclaimable; + fprintf (m, + "%-21s %04x %7zu %3zuk %4lu %6lu %6lu %7zuk %10zuk\n", + cache_info[i].name, cache_info[i].flags, + cache_info[i].obj_size, cache_info[i].slab_size >> 10, + cache_info[i].bufs_per_slab, cache_info[i].nr_objs, + cache_info[i].nr_bufs, mem_usage, mem_reclaimable); + } + + fprintf (m, "total: %zuk, reclaimable: %zuk\n", + mem_total, mem_total_reclaimable); + + fclose (m); + + out: + vm_deallocate (mach_task_self (), + cache_info, cache_info_count * sizeof *cache_info); + return err; +} + +/* Glue logic and entries table */ + +static struct node * +rootdir_file_make_node (void *dir_hook, const void *entry_hook) +{ + /* The entry hook we use is actually a procfs_node_ops for the file to be + created. The hook associated to these newly created files (and passed + to the generators above as a consequence) is always the same global + ps_context, which we get from rootdir_make_node as the directory hook. */ + return procfs_make_node (entry_hook, dir_hook); +} + +static struct node * +rootdir_symlink_make_node (void *dir_hook, const void *entry_hook) +{ + struct node *np = procfs_make_node (entry_hook, dir_hook); + if (np) + procfs_node_chtype (np, S_IFLNK); + return np; +} + +static const struct procfs_dir_entry rootdir_entries[] = { + { + .name = "self", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_fakeself, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + .ops = { + .make_node = rootdir_symlink_make_node, + .exists = rootdir_fakeself_exists, + } + }, + { + .name = "version", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_version, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "uptime", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_uptime, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "stat", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_stat, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "loadavg", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_loadavg, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "meminfo", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_meminfo, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "vmstat", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_vmstat, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "cmdline", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_cmdline, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, + { + .name = "mounts", + .hook = & (struct procfs_node_ops) { + .get_translator = rootdir_mounts_get_translator, + }, + .ops = { + .make_node = rootdir_mounts_make_node, + .exists = rootdir_mounts_exists, + } + }, + { + .name = "slabinfo", + .hook = & (struct procfs_node_ops) { + .get_contents = rootdir_gc_slabinfo, + .cleanup_contents = procfs_cleanup_contents_with_free, + }, + }, +#ifdef PROFILE + /* In order to get a usable gmon.out file, we must apparently use exit(). */ + { + .name = "exit", + .ops = { + .make_node = exit, + }, + }, +#endif + {} +}; + +struct node +*rootdir_make_node (struct ps_context *pc) +{ + static const struct procfs_dir_ops ops = { + .entries = rootdir_entries, + .entry_ops = { + .make_node = rootdir_file_make_node, + }, + }; + return procfs_dir_make_node (&ops, pc); +} + |