summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOleksii Shevchuk <alxchk@gmail.com>2017-05-21 22:27:38 +0300
committerGiampaolo Rodola <g.rodola@gmail.com>2017-05-21 21:27:38 +0200
commit68e04def9b1f04462fa8f2eefcc5287b1b943cfe (patch)
treea71b523b97b3ca3ee5621d34663f56a0bdfe4951
parenta92cfcd574e876189b64449c8e1eea505768a83e (diff)
downloadpsutil-68e04def9b1f04462fa8f2eefcc5287b1b943cfe.tar.gz
Add environment parsing (#1091)
* Add common functions to extract information from SunOS process address space * SunOS feature: Add .environ()
-rw-r--r--psutil/_pssunos.py4
-rw-r--r--psutil/_psutil_sunos.c76
-rw-r--r--psutil/arch/solaris/process_as_utils.c386
-rw-r--r--psutil/arch/solaris/process_as_utils.h21
-rwxr-xr-xsetup.py1
5 files changed, 488 insertions, 0 deletions
diff --git a/psutil/_pssunos.py b/psutil/_pssunos.py
index b1ba6b45..53821829 100644
--- a/psutil/_pssunos.py
+++ b/psutil/_pssunos.py
@@ -415,6 +415,10 @@ class Process(object):
return self._proc_name_and_args()[1].split(' ')
@wrap_exceptions
+ def environ(self):
+ return cext.proc_environ(self.pid, self._procfs_path)
+
+ @wrap_exceptions
def create_time(self):
return self._proc_basic_info()[proc_info_map['create_time']]
diff --git a/psutil/_psutil_sunos.c b/psutil/_psutil_sunos.c
index fcfbd1ac..0af8bc47 100644
--- a/psutil/_psutil_sunos.c
+++ b/psutil/_psutil_sunos.c
@@ -50,6 +50,8 @@
#include "_psutil_common.h"
#include "_psutil_posix.h"
+#include "arch/solaris/process_as_utils.h"
+
#define PSUTIL_TV2DOUBLE(t) (((t).tv_nsec * 0.000000001) + (t).tv_sec)
@@ -154,6 +156,78 @@ error:
return NULL;
}
+/*
+ * Return process environ block
+ */
+static PyObject *
+psutil_proc_environ(PyObject *self, PyObject *args) {
+ int pid;
+ char path[1000];
+ psinfo_t info;
+ const char *procfs_path;
+ char **env = NULL;
+ ssize_t env_count = -1;
+ char *dm;
+ int i = 0;
+ PyObject *py_retdict = NULL;
+ PyObject *py_envname = NULL;
+ PyObject *py_envval = NULL;
+
+ if (! PyArg_ParseTuple(args, "is", &pid, &procfs_path))
+ goto error;
+
+ sprintf(path, "%s/%i/psinfo", procfs_path, pid);
+ if (! psutil_file_to_struct(path, (void *)&info, sizeof(info)))
+ goto error;
+
+ env = psutil_read_raw_env(info, procfs_path, &env_count);
+ if (! env && env_count != 0)
+ goto error;
+
+ py_retdict = PyDict_New();
+ if (! py_retdict) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ for (i=0; i<env_count; i++) {
+ if (! env[i])
+ break;
+
+ /* Environment corrupted */
+ dm = strchr(env[i], '=');
+ if (! dm)
+ break;
+
+ *dm = '\0';
+
+ py_envname = PyUnicode_DecodeFSDefault(env[i]);
+ if (! py_envname)
+ goto error;
+
+ py_envval = PyUnicode_DecodeFSDefault(dm+1);
+ if (! py_envname)
+ goto error;
+
+ if (PyDict_SetItem(py_retdict, py_envname, py_envval) < 0)
+ goto error;
+
+ Py_DECREF(py_envname);
+ Py_DECREF(py_envval);
+ }
+
+ psutil_free_cstrings_array(env, env_count);
+ return py_retdict;
+
+ error:
+ if (env && env_count >= 0)
+ psutil_free_cstrings_array(env, env_count);
+
+ Py_XDECREF(py_envname);
+ Py_XDECREF(py_envval);
+ Py_XDECREF(py_retdict);
+ return NULL;
+}
/*
* Return process user and system CPU times as a Python tuple.
@@ -1475,6 +1549,8 @@ PsutilMethods[] = {
"Return process ppid, rss, vms, ctime, nice, nthreads, status and tty"},
{"proc_name_and_args", psutil_proc_name_and_args, METH_VARARGS,
"Return process name and args."},
+ {"proc_environ", psutil_proc_environ, METH_VARARGS,
+ "Return process environment."},
{"proc_cpu_times", psutil_proc_cpu_times, METH_VARARGS,
"Return process user and system CPU times."},
{"proc_cred", psutil_proc_cred, METH_VARARGS,
diff --git a/psutil/arch/solaris/process_as_utils.c b/psutil/arch/solaris/process_as_utils.c
new file mode 100644
index 00000000..6af8f04a
--- /dev/null
+++ b/psutil/arch/solaris/process_as_utils.c
@@ -0,0 +1,386 @@
+/*
+ * Copyright (c) 2017, Oleksii Shevchuk. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Functions specific to Sun OS Solaris platforms.
+ */
+
+#define _STRUCTURED_PROC 1
+
+#if !defined(_LP64) && _FILE_OFFSET_BITS == 64
+# undef _FILE_OFFSET_BITS
+# undef _LARGEFILE64_SOURCE
+#endif
+
+#include <Python.h>
+
+#include <sys/types.h>
+#include <sys/procfs.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "process_as_utils.h"
+
+/** Function opens address space of specified process and return file
+ * descriptor.
+ * @param pid a pid of process.
+ * @param procfs_path a path to mounted procfs filesystem.
+ * @return file descriptor or -1 in case of error.
+ */
+static int
+open_address_space(pid_t pid, const char *procfs_path) {
+ int fd;
+ char proc_path[PATH_MAX];
+
+ snprintf(proc_path, PATH_MAX, "%s/%i/as", procfs_path, pid);
+ fd = open(proc_path, O_RDONLY);
+ if (fd < 0)
+ PyErr_SetFromErrno(PyExc_OSError);
+
+ return fd;
+}
+
+/** Function reads chunk of data by offset to specified
+ * buffer of the same size.
+ * @param fd a file descriptor.
+ * @param offset an required offset in file.
+ * @param buf a buffer where to store result.
+ * @param buf_size a size of buffer where data will be stored.
+ * @return amount of bytes stored to the buffer or -1 in case of
+ * error.
+ */
+static int
+read_offt(int fd, off_t offset, char *buf, size_t buf_size) {
+ size_t to_read = buf_size;
+ size_t stored = 0;
+
+ while (to_read) {
+ int r = pread(fd, buf + stored, to_read, offset + stored);
+ if (r < 0)
+ goto error;
+ else if (r == 0)
+ break;
+
+ to_read -= r;
+ stored += r;
+ }
+
+ return stored;
+
+ error:
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+}
+
+#define STRING_SEARCH_BUF_SIZE 512
+
+/** Function reads null-terminated string from file descriptor starting from
+ * specified offset.
+ * @param fd a file descriptor of opened address space.
+ * @param offset an offset in specified file descriptor.
+ * @return allocated null-terminated string or NULL in case of error.
+*/
+static char *
+read_cstring_offt(int fd, off_t offset) {
+ int r;
+ int i = 0;
+ off_t end = offset;
+ size_t len;
+ char buf[STRING_SEARCH_BUF_SIZE];
+ char *result = NULL;
+
+ if (lseek(fd, offset, SEEK_SET) == (off_t)-1) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto error;
+ }
+
+ // Search end of string
+ for (;;) {
+ r = read(fd, buf, sizeof(buf));
+ if (r == -1) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto error;
+ }
+ else if (r == 0)
+ break;
+ else
+ for (i=0; i<r; i++)
+ if (! buf[i])
+ goto found;
+
+ end += r;
+ }
+
+ found:
+ len = end + i - offset;
+
+ result = malloc(len+1);
+ if (! result) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ if (len)
+ if (read_offt(fd, offset, result, len) < 0)
+ goto error;
+
+ result[len] = '\0';
+ return result;
+
+ error:
+ if (result)
+ free(result);
+
+ return NULL;
+}
+
+/** Function reads block of addresses by offset, dereference them one by one
+ * and create an array of null terminated C strings from them.
+ * @param fd a file descriptor of address space of interesting process.
+ * @param offset an offset of address block in address space.
+ * @param ptr_size a size of pointer. Only 4 or 8 are valid values.
+ * @param count amount of pointers in block.
+ * @return allocated array of strings dereferenced and read by offset. Number of
+ * elements in array are count. In case of error function returns NULL.
+ */
+static char **
+read_cstrings_block(int fd, off_t offset, size_t ptr_size, size_t count) {
+ char **result = NULL;
+ char *pblock = NULL;
+ size_t pblock_size;
+ int i;
+
+ assert(ptr_size == 4 || ptr_size == 8);
+
+ if (!count)
+ goto error;
+
+ pblock_size = ptr_size * count;
+
+ pblock = malloc(pblock_size);
+ if (! pblock) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ if (read_offt(fd, offset, pblock, pblock_size) != pblock_size)
+ goto error;
+
+ result = (char **) calloc(count, sizeof(char *));
+ if (! result) {
+ PyErr_NoMemory();
+ goto error;
+ }
+
+ for (i=0; i<count; i++) {
+ result[i] = read_cstring_offt(
+ fd, (ptr_size == 4?
+ ((uint32_t *) pblock)[i]:
+ ((uint64_t *) pblock)[i]));
+
+ if (!result[i])
+ goto error;
+ }
+
+ free(pblock);
+ return result;
+
+ error:
+ if (result)
+ psutil_free_cstrings_array(result, i);
+ if (pblock)
+ free(pblock);
+ return NULL;
+}
+
+/** Checks that caller process can extract proper values from psinfo_t structure.
+ * @param info a ponter to process info (psinfo_t) structure of the
+ * interesting process.
+ * @return 1 in case if caller process can extract proper values from psinfo_t
+ * structure, or 0 otherwise.
+ */
+static inline int
+is_ptr_dereference_possible(psinfo_t info) {
+#if !defined(_LP64)
+ return info.pr_dmodel == PR_MODEL_ILP32;
+#else
+ return 1;
+#endif
+}
+
+/** Return pointer size according to psinfo_t structure
+ * @param info a ponter to process info (psinfo_t) structure of the
+ * interesting process.
+ * @return pointer size (4 or 8).
+ */
+static inline int
+ptr_size_by_psinfo(psinfo_t info) {
+ return info.pr_dmodel == PR_MODEL_ILP32? 4 : 8;
+}
+
+/** Count amount of pointers in a block which ends with NULL.
+ * @param fd a discriptor of /proc/PID/as special file.
+ * @param offt an offset of block of pointers at the file.
+ * @param ptr_size a pointer size (allowed values: {4, 8}).
+ * @return amount of non-NULL pointers or -1 in case of error.
+ */
+static int
+search_pointers_vector_size_offt(int fd, off_t offt, size_t ptr_size) {
+ int count = 0;
+ int r;
+ char buf[8];
+ static const char zeros[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+
+ assert(ptr_size == 4 || ptr_size == 8);
+
+ if (lseek(fd, offt, SEEK_SET) == (off_t)-1)
+ goto error;
+
+ for (;; count ++) {
+ r = read(fd, buf, ptr_size);
+
+ if (r < 0)
+ goto error;
+
+ if (r == 0)
+ break;
+
+ if (r != ptr_size) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Pointer block is truncated");
+
+ return -1;
+ }
+
+ if (! memcmp(buf, zeros, ptr_size))
+ break;
+ }
+
+ return count;
+
+ error:
+ PyErr_SetFromErrno(PyExc_OSError);
+ return -1;
+}
+
+/** Derefence and read array of strings by psinfo_t.pr_argv pointer from remote process.
+ * @param info a ponter to process info (psinfo_t) structure of the
+ * interesting process
+ * @param procfs_path a cstring with path to mounted procfs filesystem.
+ * @param count a pointer to variable where to store amount of elements in
+ * returned array. In case of error value of variable will not be changed.
+ * @return allocated array of cstrings or NULL in case of error.
+ */
+char **
+psutil_read_raw_args(psinfo_t info, const char *procfs_path, size_t *count) {
+ int as;
+ char **result;
+
+ if (! is_ptr_dereference_possible(info)) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Dereferencing procfs pointers of "
+ "64bit process is not possible");
+
+ return NULL;
+ }
+
+ if (! (info.pr_argv && info.pr_argc)) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Process doesn't have arguments block");
+
+ return NULL;
+ }
+
+ as = open_address_space(info.pr_pid, procfs_path);
+ if (as < 0)
+ return NULL;
+
+ result = read_cstrings_block(
+ as, info.pr_argv, ptr_size_by_psinfo(info), info.pr_argc
+ );
+
+ if (result && count)
+ *count = info.pr_argc;
+
+ close(as);
+
+ return result;
+}
+
+/** Dereference and read array of strings by psinfo_t.pr_envp pointer from remote process.
+ * @param info a ponter to process info (psinfo_t) structure of the
+ * interesting process.
+ * @param procfs_path a cstring with path to mounted procfs filesystem.
+ * @param count a pointer to variable where to store amount of elements in
+ * returned array. In case of error value of variable will not be
+ * changed. To detect special case (described later) variable should be
+ * initialized by -1 or other negative value.
+ * @return allocated array of cstrings or NULL in case of error.
+ * Special case: count set to 0, return NULL.
+ * Special case means there is no error acquired, but no data retrieved.
+ * Special case exists because the nature of the process. From the
+ * beginning it's not clean how many pointers in envp array. Also
+ * situation when environment is empty is common for kernel processes.
+ */
+char **
+psutil_read_raw_env(psinfo_t info, const char *procfs_path, ssize_t *count) {
+ int as;
+ int env_count;
+ int ptr_size;
+ char **result = NULL;
+
+ if (! is_ptr_dereference_possible(info)) {
+ PyErr_SetString(
+ PyExc_RuntimeError, "Dereferencing procfs pointers of "
+ "64bit process is not possible");
+
+ return NULL;
+ }
+
+ if (! info.pr_envp) {
+ if (count)
+ *count = 0;
+
+ return NULL;
+ }
+
+ as = open_address_space(info.pr_pid, procfs_path);
+ if (as < 0)
+ return NULL;
+
+ ptr_size = ptr_size_by_psinfo(info);
+
+ env_count = search_pointers_vector_size_offt(
+ as, info.pr_envp, ptr_size);
+
+ if (env_count >= 0 && count)
+ *count = env_count;
+
+ if (env_count > 0)
+ result = read_cstrings_block(
+ as, info.pr_envp, ptr_size, env_count
+ );
+
+ close(as);
+ return result;
+}
+
+/** Free array of cstrings.
+ * @param array an array of cstrings returned by psutil_read_raw_env,
+ * psutil_read_raw_args or any other function.
+ * @param count a count of strings in the passed array
+ */
+void
+psutil_free_cstrings_array(char **array, size_t count) {
+ int i;
+
+ if (!array)
+ return;
+
+ for (i=0; i<count; i++)
+ if (array[i])
+ free(array[i]);
+
+ free(array);
+}
diff --git a/psutil/arch/solaris/process_as_utils.h b/psutil/arch/solaris/process_as_utils.h
new file mode 100644
index 00000000..f886767c
--- /dev/null
+++ b/psutil/arch/solaris/process_as_utils.h
@@ -0,0 +1,21 @@
+/*
+ * Copyright (c) 2017, Oleksii Shevchuk. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ *
+ * Functions specific to Sun OS Solaris platforms.
+ */
+
+#ifndef PROCESS_AS_UTILS_H
+#define PROCESS_AS_UTILS_H
+
+char **
+psutil_read_raw_args(psinfo_t info, const char *procfs_path, size_t *count);
+
+char **
+psutil_read_raw_env(psinfo_t info, const char *procfs_path, ssize_t *count);
+
+void
+psutil_free_cstrings_array(char **array, size_t count);
+
+#endif // PROCESS_AS_UTILS_H
diff --git a/setup.py b/setup.py
index cf3deb7f..f6acc149 100755
--- a/setup.py
+++ b/setup.py
@@ -238,6 +238,7 @@ elif SUNOS:
sources=sources + [
'psutil/_psutil_sunos.c',
'psutil/arch/solaris/v10/ifaddrs.c',
+ 'psutil/arch/solaris/process_as_utils.c'
],
define_macros=macros,
libraries=['kstat', 'nsl', 'socket'])