Skip to content

Commit

Permalink
Add common functions to extract information from SunOS process addres…
Browse files Browse the repository at this point in the history
…s space
  • Loading branch information
alxchk committed May 21, 2017
1 parent d3fde86 commit daaf410
Show file tree
Hide file tree
Showing 3 changed files with 408 additions and 0 deletions.
386 changes: 386 additions & 0 deletions psutil/arch/solaris/process_as_utils.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,386 @@
/*
* Copyright (c) 2017, Oleksii Shevcuhk. 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 enomem;
}

if (read_offt(fd, offset, pblock, pblock_size) != pblock_size)
goto error;

result = (char **) calloc(count, sizeof(char *));
if (! result) {
PyErr_NoMemory();
goto enomem;
}

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);
}
Loading

0 comments on commit daaf410

Please sign in to comment.