blob: 4c2261f15ffdbd91f6fe71327deb66fffd88688e [file] [log] [blame]
/*****************************************************************************\
* kill_tree.c - Kill process tree based upon process IDs
* Used primarily for MPICH-GM
*****************************************************************************
* Copyright (C) 2004 The Regents of the University of California.
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
* Written by Takao Hatazaki <takao.hatazaki@hp.com>
* CODE-OCEC-09-009. All rights reserved.
*
* This file is part of Slurm, a resource management program.
* For details, see <https://slurm.schedmd.com/>.
* Please also read the included file: DISCLAIMER.
*
* Slurm 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.
*
* In addition, as a special exception, the copyright holders give permission
* to link the code of portions of this program with the OpenSSL library under
* certain conditions as described in each individual source file, and
* distribute linked combinations including the two. You must obey the GNU
* General Public License in all respects for all of the code used other than
* OpenSSL. If you modify file(s) with this exception, you may extend this
* exception to your version of the file(s), but you are not obligated to do
* so. If you do not wish to do so, delete this exception statement from your
* version. If you delete this exception statement from all source files in
* the program, then also delete it here.
*
* Slurm 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 Slurm; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\*****************************************************************************/
#include <dirent.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "slurm/slurm.h"
#include "slurm/slurm_errno.h"
#include "src/common/log.h"
#include "src/common/xmalloc.h"
#include "src/common/xstring.h"
#include "kill_tree.h"
typedef struct xpid_s {
pid_t pid;
int is_usercmd;
char *cmd;
struct xpid_s *next;
} xpid_t;
typedef struct xppid_s {
pid_t ppid;
xpid_t *list;
struct xppid_s *next;
} xppid_t;
#define HASH_LEN 64
#define GET_HASH_IDX(ppid) ((ppid)%HASH_LEN)
static xpid_t *_alloc_pid(pid_t pid, int is_usercmd, char *cmd, xpid_t *next)
{
xpid_t *new;
new = xmalloc(sizeof(*new));
new->pid = pid;
new->is_usercmd = is_usercmd;
new->cmd = xstrdup(cmd);
new->next = next;
return new;
}
static xppid_t *_alloc_ppid(pid_t ppid, pid_t pid, int is_usercmd, char *cmd,
xppid_t *next)
{
xppid_t *new;
new = xmalloc(sizeof(*new));
new->ppid = ppid;
new->list = _alloc_pid(pid, is_usercmd, cmd, NULL);
new->next = next;
return new;
}
static void _push_to_hashtbl(pid_t ppid, pid_t pid,
int is_usercmd, char *cmd, xppid_t **hashtbl)
{
int idx;
xppid_t *ppids, *newppid;
xpid_t *newpid;
idx = GET_HASH_IDX(ppid);
ppids = hashtbl[idx];
while (ppids) {
if (ppids->ppid == ppid) {
newpid = _alloc_pid(pid, is_usercmd, cmd, ppids->list);
ppids->list = newpid;
return;
}
ppids = ppids->next;
}
newppid = _alloc_ppid(ppid, pid, is_usercmd, cmd, hashtbl[idx]);
hashtbl[idx] = newppid;
}
static int _get_myname(char *s)
{
char path[PATH_MAX], *rbuf;
ssize_t buf_used;
int fd;
snprintf(path, PATH_MAX, "/proc/%ld/stat", (long)getpid());
if ((fd = open(path, O_RDONLY)) < 0) {
error("Cannot open /proc/getpid()/stat");
return -1;
}
rbuf = xmalloc(4096);
buf_used = read(fd, rbuf, 4096);
if ((buf_used <= 0) || (buf_used >= 4096)) {
error("Cannot read /proc/getpid()/stat");
xfree(rbuf);
close(fd);
return -1;
}
close(fd);
if (sscanf(rbuf, "%*d %s ", s) != 1) {
error("Cannot get the command name from /proc/getpid()/stat");
xfree(rbuf);
return -1;
}
xfree(rbuf);
return 0;
}
static xppid_t **_build_hashtbl(void)
{
DIR *dir;
struct dirent *de;
char path[PATH_MAX], *endptr, *num, *rbuf;
ssize_t buf_used;
char myname[1024], cmd[1024];
char state;
int fd;
long pid, ppid, ret_l;
xppid_t **hashtbl;
if ((dir = opendir("/proc")) == NULL) {
error("opendir(/proc): %m");
return NULL;
}
if (_get_myname(myname) < 0)
return NULL;
debug3("Myname in build_hashtbl: %s", myname);
hashtbl = xcalloc(HASH_LEN, sizeof(xppid_t *));
errno = 0;
rbuf = xmalloc(4096);
while ((de = readdir(dir)) != NULL) {
num = de->d_name;
if ((num[0] < '0') || (num[0] > '9'))
continue;
ret_l = strtol(num, &endptr, 10);
if ((ret_l == LONG_MIN) || (ret_l == LONG_MAX)) {
error("couldn't do a strtol on str %s(%ld): %m",
num, ret_l);
continue;
}
if (endptr == NULL || *endptr != 0)
continue;
snprintf(path, PATH_MAX, "/proc/%s/stat", num);
if ((fd = open(path, O_RDONLY)) < 0) {
continue;
}
buf_used = read(fd, rbuf, 4096);
if ((buf_used <= 0) || (buf_used >= 4096)) {
close(fd);
continue;
}
close(fd);
if (sscanf(rbuf, "%ld %s %c %ld", &pid, cmd, &state, &ppid)
!= 4) {
continue;
}
if (state == 'Z') {
debug3("Defunct process skipped: command=%s state=%c "
"pid=%ld ppid=%ld", cmd, state, pid, ppid);
continue; /* Defunct, don't try to kill */
}
/* Record cmd for debugging purpose */
_push_to_hashtbl((pid_t)ppid, (pid_t)pid,
xstrcmp(myname, cmd), cmd, hashtbl);
}
xfree(rbuf);
closedir(dir);
return hashtbl;
}
static void _destroy_list(xpid_t *list)
{
xpid_t *tmp;
while (list) {
tmp = list->next;
xfree(list->cmd);
xfree(list);
list = tmp;
}
}
static void _destroy_hashtbl(xppid_t **hashtbl)
{
int i;
xppid_t *ppid, *tmp;
for (i=0; i<HASH_LEN; i++) {
ppid = hashtbl[i];
while (ppid) {
_destroy_list(ppid->list);
tmp = ppid->next;
xfree(ppid);
ppid = tmp;
}
}
xfree(hashtbl);
}
static xpid_t *_get_list(int top, xpid_t *list, xppid_t **hashtbl)
{
xppid_t *ppid;
xpid_t *children;
ppid = hashtbl[GET_HASH_IDX(top)];
while (ppid) {
if (ppid->ppid == top) {
children = ppid->list;
while (children) {
list = _alloc_pid(children->pid,
children->is_usercmd,
children->cmd,
list);
children = children->next;
}
children = ppid->list;
while (children) {
list = _get_list(children->pid, list, hashtbl);
children = children->next;
}
break;
}
ppid = ppid->next;
}
return list;
}
static int _kill_proclist(xpid_t *list, int sig)
{
int rc;
rc = 0;
while (list) {
if (list->pid > 1) {
if (! list->is_usercmd) {
debug2("%ld %s is not a user command. "
"Skipped sending signal %d",
(long)list->pid, list->cmd, sig);
} else {
verbose("Sending signal %d to pid %d %s",
sig, list->pid, list->cmd);
if (kill(list->pid, sig))
rc = errno; /* save the last error */
}
}
list = list->next;
}
return rc;
}
/*
* Some of processes may not be in the same process group
* (e.g. GMPI processes). So, find out the process tree,
* then kill all that subtree.
*/
extern int kill_proc_tree(pid_t top, int sig)
{
xpid_t *list;
int rc = -1;
xppid_t **hashtbl;
if ((hashtbl = _build_hashtbl()) == NULL)
return -1;
list = _get_list(top, NULL, hashtbl);
rc = _kill_proclist(list, sig);
_destroy_hashtbl(hashtbl);
_destroy_list(list);
return rc;
}
/*
* Return the pid of the process named "process_name"
* which is the ancestor of "process".
*/
extern pid_t find_ancestor(pid_t process, char *process_name)
{
char path[PATH_MAX], *rbuf;
ssize_t buf_used;
int fd, len;
long pid, ppid;
len = strlen(process_name);
rbuf = xmalloc_nz(4097);
pid = ppid = (long)process;
while (1) {
if (ppid <= 1) {
pid = 0;
break;
}
snprintf(path, PATH_MAX, "/proc/%ld/stat", ppid);
if ((fd = open(path, O_RDONLY)) < 0) {
pid = 0;
break;
}
buf_used = read(fd, rbuf, 4096);
if (buf_used >= 0)
rbuf[buf_used] = '\0';
else
rbuf[0] = '\0';
if ((buf_used <= 0) || (buf_used >= 4096)) {
close(fd);
pid = 0;
break;
}
close(fd);
if (sscanf(rbuf, "%ld %*s %*s %ld", &pid, &ppid) != 2) {
pid = 0;
break;
}
snprintf(path, PATH_MAX, "/proc/%ld/cmdline", pid);
if ((fd = open(path, O_RDONLY)) < 0) {
continue;
}
buf_used = read(fd, rbuf, 4096);
if (buf_used >= 0)
rbuf[buf_used] = '\0';
else
rbuf[0] = '\0';
if ((buf_used <= 0) || (buf_used >= 4096)) {
close(fd);
continue;
}
close(fd);
if (strncmp(rbuf, process_name, len) == 0)
break;
}
xfree(rbuf);
return pid;
}
/* The returned "pids" array does NOT include the slurmstepd */
extern int proctrack_linuxproc_get_pids(pid_t top, pid_t **pids, int *npids)
{
xppid_t **hashtbl;
xpid_t *list, *ptr;
pid_t *p;
int i, len = 32, rc;
if ((hashtbl = _build_hashtbl()) == NULL)
return SLURM_ERROR;
list = _get_list(top, NULL, hashtbl);
if (list == NULL) {
*pids = NULL;
*npids = 0;
_destroy_hashtbl(hashtbl);
return SLURM_ERROR;
}
p = xcalloc(len, sizeof(pid_t));
ptr = list;
i = 0;
while (ptr != NULL) {
if (ptr->is_usercmd) { /* don't include the slurmstepd */
if (i >= len - 1) {
len *= 2;
xrealloc(p, (sizeof(pid_t) * len));
}
p[i] = ptr->pid;
i++;
}
ptr = ptr->next;
}
if (i == 0) {
xfree(p);
*pids = NULL;
*npids = 0;
rc = SLURM_ERROR;
} else {
*pids = p;
*npids = i;
rc = SLURM_SUCCESS;
}
_destroy_hashtbl(hashtbl);
_destroy_list(list);
return rc;
}