/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
*
* Copyright (C) 2006 William Jon McCann <mccann@jhu.edu>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*
*/
#include "config.h"
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/vt.h>
#include <linux/tty.h>
#include <linux/kd.h>
#ifdef HAVE_PATHS_H
#include <paths.h>
#endif /* HAVE_PATHS_H */
#include "ck-sysdeps.h"
#ifndef ERROR
#define ERROR -1
#endif
/* adapted from procps */
struct _CkProcessStat
{
int pid;
int ppid; /* stat,status pid of parent process */
char state; /* stat,status single-char code for process state (S=sleeping) */
char cmd[16]; /* stat,status basename of executable file in call to exec(2) */
unsigned long long utime; /* stat user-mode CPU time accumulated by process */
unsigned long long stime; /* stat kernel-mode CPU time accumulated by process */
unsigned long long cutime; /* stat cumulative utime of process and reaped children */
unsigned long long cstime; /* stat cumulative stime of process and reaped children */
unsigned long long start_time; /* stat start time of process -- seconds since 1-1-70 */
unsigned long start_code; /* stat address of beginning of code segment */
unsigned long end_code; /* stat address of end of code segment */
unsigned long start_stack; /* stat address of the bottom of stack for the process */
unsigned long kstk_esp; /* stat kernel stack pointer */
unsigned long kstk_eip; /* stat kernel instruction pointer */
unsigned long wchan; /* stat (special) address of kernel wait channel proc is sleeping in */
long priority; /* stat kernel scheduling priority */
long nice; /* stat standard unix nice level of process */
long rss; /* stat resident set size from /proc/#/stat (pages) */
long alarm; /* stat ? */
unsigned long rtprio; /* stat real-time priority */
unsigned long sched; /* stat scheduling class */
unsigned long vsize; /* stat number of pages of virtual memory ... */
unsigned long rss_rlim; /* stat resident set size limit? */
unsigned long flags; /* stat kernel flags for the process */
unsigned long min_flt; /* stat number of minor page faults since process start */
unsigned long maj_flt; /* stat number of major page faults since process start */
unsigned long cmin_flt; /* stat cumulative min_flt of process and child processes */
unsigned long cmaj_flt; /* stat cumulative maj_flt of process and child processes */
int pgrp; /* stat process group id */
int session; /* stat session id */
int nlwp; /* stat number of threads, or 0 if no clue */
int tty; /* stat full device number of controlling terminal */
int tpgid; /* stat terminal process group id */
int exit_signal; /* stat might not be SIGCHLD */
int processor; /* stat current (or most recent?) CPU */
};
/* adapted from procps */
#define MAJOR_OF(d) ( ((unsigned)(d)>>8u) & 0xfffu )
#define MINOR_OF(d) ( ((unsigned)(d)&0xffu) | (((unsigned)(d)&0xfff00000u)>>12u) )
typedef struct tty_map_node {
struct tty_map_node *next;
guint major_number;
guint minor_first;
guint minor_last;
char name[16];
char devfs_type;
} tty_map_node;
static tty_map_node *tty_map = NULL;
/* adapted from procps */
/* Load /proc/tty/drivers for device name mapping use. */
static void
load_drivers (void)
{
char buf[10000];
char *p;
int fd;
int bytes;
fd = open ("/proc/tty/drivers", O_RDONLY);
if (fd == -1) {
goto fail;
}
bytes = read (fd, buf, sizeof (buf) - 1);
if (bytes == -1) {
goto fail;
}
buf[bytes] = '\0';
p = buf;
while ((p = strstr (p, " " _PATH_DEV))) {
tty_map_node *tmn;
int len;
char *end;
p += 6;
end = strchr (p, ' ');
if (! end) {
continue;
}
len = end - p;
tmn = calloc (1, sizeof (tty_map_node));
tmn->next = tty_map;
tty_map = tmn;
/* if we have a devfs type name such as /dev/tts/%d then strip the %d but
keep a flag. */
if (len >= 3 && !strncmp (end - 2, "%d", 2)) {
len -= 2;
tmn->devfs_type = 1;
}
strncpy (tmn->name, p, len);
p = end; /* set p to point past the %d as well if there is one */
while (*p == ' ') {
p++;
}
tmn->major_number = atoi (p);
p += strspn (p, "0123456789");
while (*p == ' ') {
p++;
}
switch (sscanf (p, "%u-%u", &tmn->minor_first, &tmn->minor_last)) {
default:
/* Can't finish parsing this line so we remove it from the list */
tty_map = tty_map->next;
free (tmn);
break;
case 1:
tmn->minor_last = tmn->minor_first;
break;
case 2:
break;
}
}
fail:
if (fd != -1) {
close (fd);
}
if(! tty_map) {
tty_map = (tty_map_node *)-1;
}
}
/* adapted from procps */
/* Try to guess the device name from /proc/tty/drivers info. */
static char *
driver_name (guint maj,
guint min)
{
struct stat sbuf;
tty_map_node *tmn;
char *tty;
if (! tty_map) {
load_drivers ();
}
if (tty_map == (tty_map_node *) - 1) {
return 0;
}
tmn = tty_map;
for (;;) {
if (! tmn) {
return 0;
}
if (tmn->major_number == maj && tmn->minor_first <= min && tmn->minor_last >= min) {
break;
}
tmn = tmn->next;
}
tty = g_strdup_printf (_PATH_DEV "%s%d", tmn->name, min); /* like "/dev/ttyZZ255" */
if (stat (tty, &sbuf) < 0){
g_free (tty);
if (tmn->devfs_type) {
return NULL;
}
tty = g_strdup_printf (_PATH_DEV "%s", tmn->name); /* like "/dev/ttyZZ255" */
if (stat (tty, &sbuf) < 0) {
g_free (tty);
return NULL;
}
}
if (min != MINOR_OF (sbuf.st_rdev)) {
g_free (tty);
return NULL;
}
if (maj != MAJOR_OF (sbuf.st_rdev)) {
g_free (tty);
return NULL;
}
return tty;
}
/* adapted from procps */
static char *
link_name (guint maj,
guint min,
int pid,
const char *name)
{
struct stat sbuf;
char *path;
char *tty;
path = g_strdup_printf ("/proc/%d/%s", pid, name);
tty = g_file_read_link (path, NULL);
g_free (path);
if (tty == NULL) {
goto out;
}
if (stat (tty, &sbuf) < 0) {
g_free (tty);
tty = NULL;
goto out;
}
if (min != MINOR_OF (sbuf.st_rdev)) {
g_free (tty);
tty = NULL;
goto out;
}
if (maj != MAJOR_OF (sbuf.st_rdev)) {
g_free (tty);
tty = NULL;
goto out;
}
out:
return tty;
}
pid_t
ck_process_stat_get_ppid (CkProcessStat *stat)
{
g_return_val_if_fail (stat != NULL, -1);
return stat->ppid;
}
char *
ck_process_stat_get_cmd (CkProcessStat *stat)
{
g_return_val_if_fail (stat != NULL, NULL);
return g_strdup (stat->cmd);
}
/* adapted from procps */
char *
ck_process_stat_get_tty (CkProcessStat *stat)
{
guint dev;
char *tty;
guint dev_maj;
guint dev_min;
pid_t pid;
g_return_val_if_fail (stat != NULL, NULL);
pid = stat->pid;
dev = stat->tty;
if (dev == 0u) {
return NULL;
}
dev_maj = MAJOR_OF (dev);
dev_min = MINOR_OF (dev);
tty = link_name (dev_maj, dev_min, pid, "tty");
if (tty != NULL) {
goto out;
}
tty = driver_name (dev_maj, dev_min);
if (tty != NULL) {
goto out;
}
tty = link_name (dev_maj, dev_min, pid, "fd/2");
if (tty != NULL) {
goto out;
}
tty = link_name (dev_maj, dev_min, pid, "fd/255");
if (tty != NULL) {
goto out;
}
out:
return tty;
}
#define KLF "l"
/* adapted from procps */
static void
stat2proc (const char *S,
CkProcessStat *P)
{
unsigned num;
char * tmp;
/* fill in default values for older kernels */
P->processor = 0;
P->rtprio = -1;
P->sched = -1;
P->nlwp = 0;
S = strchr (S, '(') + 1;
tmp = strrchr (S, ')');
num = tmp - S;
if (G_UNLIKELY (num >= sizeof P->cmd)) {
num = sizeof P->cmd - 1;
}
memcpy (P->cmd, S, num);
P->cmd[num] = '\0';
S = tmp + 2; /* skip ") " */
num = sscanf (S,
"%c "
"%d %d %d %d %d "
"%lu %lu %lu %lu %lu "
"%Lu %Lu %Lu %Lu " /* utime stime cutime cstime */
"%ld %ld "
"%d "
"%ld "
"%Lu " /* start_time */
"%lu "
"%ld "
"%lu %"KLF"u %"KLF"u %"KLF"u %"KLF"u %"KLF"u "
"%*s %*s %*s %*s " /* discard, no RT signals & Linux 2.1 used hex */
"%"KLF"u %*lu %*lu "
"%d %d "
"%lu %lu",
&P->state,
&P->ppid, &P->pgrp, &P->session, &P->tty, &P->tpgid,
&P->flags, &P->min_flt, &P->cmin_flt, &P->maj_flt, &P->cmaj_flt,
&P->utime, &P->stime, &P->cutime, &P->cstime,
&P->priority, &P->nice,
&P->nlwp,
&P->alarm,
&P->start_time,
&P->vsize,
&P->rss,
&P->rss_rlim, &P->start_code, &P->end_code, &P->start_stack, &P->kstk_esp, &P->kstk_eip,
/* P->signal, P->blocked, P->sigignore, P->sigcatch, */ /* can't use */
&P->wchan, /* &P->nswap, &P->cnswap, */ /* nswap and cnswap dead for 2.4.xx and up */
/* -- Linux 2.0.35 ends here -- */
&P->exit_signal, &P->processor, /* 2.2.1 ends with "exit_signal" */
/* -- Linux 2.2.8 to 2.5.17 end here -- */
&P->rtprio, &P->sched /* both added to 2.5.18 */
);
if (!P->nlwp){
P->nlwp = 1;
}
}
gboolean
ck_process_stat_new_for_unix_pid (pid_t pid,
CkProcessStat **stat,
GError **error)
{
char *path;
char *contents;
gsize length;
gboolean res;
GError *local_error;
CkProcessStat *proc;
g_return_val_if_fail (pid > 1, FALSE);
if (stat == NULL) {
return FALSE;
}
path = g_strdup_printf ("/proc/%d/stat", pid);
contents = NULL;
local_error = NULL;
res = g_file_get_contents (path,
&contents,
&length,
&local_error);
if (res) {
proc = g_new0 (CkProcessStat, 1);
proc->pid = pid;
stat2proc (contents, proc);
*stat = proc;
} else {
g_propagate_error (error, local_error);
*stat = NULL;
}
g_free (contents);
g_free (path);
return res;
}
void
ck_process_stat_free (CkProcessStat *stat)
{
g_free (stat);
}
GHashTable *
ck_unix_pid_get_env_hash (pid_t pid)
{
char *path;
gboolean res;
char *contents;
gsize length;
GError *error;
GHashTable *hash;
int i;
gboolean last_was_null;
g_return_val_if_fail (pid > 1, NULL);
contents = NULL;
hash = NULL;
path = g_strdup_printf ("/proc/%u/environ", (guint)pid);
error = NULL;
res = g_file_get_contents (path,
&contents,
&length,
&error);
if (! res) {
g_warning ("Couldn't read %s: %s", path, error->message);
g_error_free (error);
goto out;
}
hash = g_hash_table_new_full (g_str_hash,
g_str_equal,
g_free,
g_free);
last_was_null = TRUE;
for (i = 0; i < length; i++) {
if (contents[i] == '\0') {
last_was_null = TRUE;
continue;
}
if (last_was_null) {
char **vals;
vals = g_strsplit (contents + i, "=", 2);
if (vals != NULL) {
g_hash_table_insert (hash,
g_strdup (vals[0]),
g_strdup (vals[1]));
g_strfreev (vals);
}
}
last_was_null = FALSE;
}
out:
g_free (contents);
g_free (path);
return hash;
}
char *
ck_unix_pid_get_env (pid_t pid,
const char *var)
{
char *path;
gboolean res;
char *contents;
char *val;
gsize length;
GError *error;
int i;
char *prefix;
int prefix_len;
gboolean last_was_null;
g_return_val_if_fail (pid > 1, NULL);
val = NULL;
contents = NULL;
prefix = NULL;
path = g_strdup_printf ("/proc/%u/environ", (guint)pid);
error = NULL;
res = g_file_get_contents (path,
&contents,
&length,
&error);
if (! res) {
g_warning ("Couldn't read %s: %s", path, error->message);
g_error_free (error);
goto out;
}
prefix = g_strdup_printf ("%s=", var);
prefix_len = strlen (prefix);
/* FIXME: make more robust */
last_was_null = TRUE;
for (i = 0; i < length; i++) {
if (contents[i] == '\0') {
last_was_null = TRUE;
continue;
}
if (last_was_null && g_str_has_prefix (contents + i, prefix)) {
val = g_strdup (contents + i + prefix_len);
break;
}
last_was_null = FALSE;
}
out:
g_free (prefix);
g_free (contents);
g_free (path);
return val;
}
uid_t
ck_unix_pid_get_uid (pid_t pid)
{
struct stat st;
char *path;
int uid;
int res;
g_return_val_if_fail (pid > 1, 0);
uid = -1;
path = g_strdup_printf ("/proc/%u", (guint)pid);
res = stat (path, &st);
g_free (path);
if (res == 0) {
uid = st.st_uid;
}
return uid;
}
pid_t
ck_unix_pid_get_ppid (pid_t pid)
{
int ppid;
gboolean res;
CkProcessStat *stat;
g_return_val_if_fail (pid > 1, 0);
ppid = -1;
res = ck_process_stat_new_for_unix_pid (pid, &stat, NULL);
if (! res) {
goto out;
}
ppid = ck_process_stat_get_ppid (stat);
ck_process_stat_free (stat);
out:
return ppid;
}
gboolean
ck_get_max_num_consoles (guint *num)
{
if (num != NULL) {
*num = MAX_NR_CONSOLES;
}
return TRUE;
}
char *
ck_get_console_device_for_num (guint num)
{
char *device;
device = g_strdup_printf (_PATH_TTY "%u", num);
return device;
}
gboolean
ck_get_console_num_from_device (const char *device,
guint *num)
{
guint n;
gboolean ret;
n = 0;
ret = FALSE;
if (device == NULL) {
return FALSE;
}
if (sscanf (device, _PATH_TTY "%u", &n) == 1) {
ret = TRUE;
}
if (num != NULL) {
*num = n;
}
return ret;
}
gboolean
ck_get_active_console_num (int console_fd,
guint *num)
{
gboolean ret;
int res;
guint active;
struct vt_stat stat;
g_assert (console_fd != -1);
active = 0;
ret = FALSE;
res = ioctl (console_fd, VT_GETSTATE, &stat);
if (res == ERROR) {
perror ("ioctl VT_GETSTATE");
goto out;
}
{
int i;
g_debug ("Current VT: tty%d", stat.v_active);
for (i = 1; i <= 16; i++) {
gboolean is_on;
is_on = stat.v_state & (1 << i);
g_debug ("VT %d:%s", i, is_on ? "on" : "off");
}
}
active = stat.v_active;
ret = TRUE;
out:
if (num != NULL) {
*num = active;
}
return ret;
}
syntax highlighted by Code2HTML, v. 0.9.1