mirror of
https://github.com/checkpoint-restore/criu
synced 2025-08-22 09:58:09 +00:00
crtools: split cr-dump.c in two files
Signed-off-by: Andrey Vagin <avagin@openvz.org> Signed-off-by: Pavel Emelyanov <xemul@parallels.com>
This commit is contained in:
parent
3d1e8c6883
commit
2f172c8b24
@ -73,6 +73,7 @@ obj-y += sd-daemon.o
|
|||||||
obj-y += plugin.o
|
obj-y += plugin.o
|
||||||
obj-y += cr-errno.o
|
obj-y += cr-errno.o
|
||||||
obj-y += pie/pie-relocs.o
|
obj-y += pie/pie-relocs.o
|
||||||
|
obj-y += seize.o
|
||||||
|
|
||||||
ifneq ($(MAKECMDGOALS),clean)
|
ifneq ($(MAKECMDGOALS),clean)
|
||||||
incdeps := y
|
incdeps := y
|
||||||
|
380
cr-dump.c
380
cr-dump.c
@ -79,11 +79,10 @@
|
|||||||
#include "security.h"
|
#include "security.h"
|
||||||
#include "lsm.h"
|
#include "lsm.h"
|
||||||
#include "seccomp.h"
|
#include "seccomp.h"
|
||||||
|
#include "seize.h"
|
||||||
|
|
||||||
#include "asm/dump.h"
|
#include "asm/dump.h"
|
||||||
|
|
||||||
#define NR_ATTEMPTS 5
|
|
||||||
|
|
||||||
static char loc_buf[PAGE_SIZE];
|
static char loc_buf[PAGE_SIZE];
|
||||||
|
|
||||||
static void close_vma_file(struct vma_area *vma)
|
static void close_vma_file(struct vma_area *vma)
|
||||||
@ -713,353 +712,6 @@ err:
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int parse_children(pid_t pid, pid_t **_c, int *_n)
|
|
||||||
{
|
|
||||||
pid_t *ch = NULL;
|
|
||||||
int nr = 0;
|
|
||||||
DIR *dir;
|
|
||||||
struct dirent *de;
|
|
||||||
|
|
||||||
dir = opendir_proc(pid, "task");
|
|
||||||
if (dir == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
while ((de = readdir(dir))) {
|
|
||||||
int fd, len;
|
|
||||||
char *pos;
|
|
||||||
|
|
||||||
if (dir_dots(de))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
fd = open_proc(pid, "task/%s/children", de->d_name);
|
|
||||||
if (fd < 0)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
len = read(fd, loc_buf, sizeof(loc_buf) - 1);
|
|
||||||
close(fd);
|
|
||||||
if (len < 0)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
loc_buf[len] = '\0';
|
|
||||||
pos = loc_buf;
|
|
||||||
while (1) {
|
|
||||||
pid_t val, *tmp;
|
|
||||||
|
|
||||||
val = strtol(pos, &pos, 0);
|
|
||||||
if (!val) {
|
|
||||||
BUG_ON(*pos != '\0');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
tmp = xrealloc(ch, (nr + 1) * sizeof(pid_t));
|
|
||||||
if (!tmp)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
ch = tmp;
|
|
||||||
ch[nr] = val;
|
|
||||||
nr++;
|
|
||||||
pos++; /* space goes after each pid */
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
*_c = ch;
|
|
||||||
*_n = nr;
|
|
||||||
|
|
||||||
closedir(dir);
|
|
||||||
return 0;
|
|
||||||
err:
|
|
||||||
closedir(dir);
|
|
||||||
xfree(ch);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool child_collected(struct pstree_item *i, pid_t pid)
|
|
||||||
{
|
|
||||||
struct pstree_item *c;
|
|
||||||
|
|
||||||
list_for_each_entry(c, &i->children, sibling)
|
|
||||||
if (c->pid.real == pid)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int collect_task(struct pstree_item *item);
|
|
||||||
static int collect_children(struct pstree_item *item)
|
|
||||||
{
|
|
||||||
pid_t *ch;
|
|
||||||
int ret, i, nr_children, nr_inprogress;
|
|
||||||
|
|
||||||
ret = parse_children(item->pid.real, &ch, &nr_children);
|
|
||||||
if (ret < 0)
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
nr_inprogress = 0;
|
|
||||||
for (i = 0; i < nr_children; i++) {
|
|
||||||
struct pstree_item *c;
|
|
||||||
pid_t pid = ch[i];
|
|
||||||
|
|
||||||
/* Is it already frozen? */
|
|
||||||
if (child_collected(item, pid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
nr_inprogress++;
|
|
||||||
|
|
||||||
pr_info("Seized task %d, state %d\n", pid, ret);
|
|
||||||
|
|
||||||
c = alloc_pstree_item();
|
|
||||||
if (c == NULL) {
|
|
||||||
ret = -1;
|
|
||||||
goto free;
|
|
||||||
}
|
|
||||||
|
|
||||||
ret = seize_task(pid, item->pid.real, &dmpi(c)->pi_creds);
|
|
||||||
if (ret < 0) {
|
|
||||||
/*
|
|
||||||
* Here is a race window between parse_children() and seize(),
|
|
||||||
* so the task could die for these time.
|
|
||||||
* Don't worry, will try again on the next attempt. The number
|
|
||||||
* of attempts is restricted, so it will exit if something
|
|
||||||
* really wrong.
|
|
||||||
*/
|
|
||||||
ret = 0;
|
|
||||||
xfree(c);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
c->pid.real = pid;
|
|
||||||
c->parent = item;
|
|
||||||
c->state = ret;
|
|
||||||
list_add_tail(&c->sibling, &item->children);
|
|
||||||
|
|
||||||
/* Here is a recursive call (Depth-first search) */
|
|
||||||
ret = collect_task(c);
|
|
||||||
if (ret < 0)
|
|
||||||
goto free;
|
|
||||||
}
|
|
||||||
free:
|
|
||||||
xfree(ch);
|
|
||||||
return ret < 0 ? ret : nr_inprogress;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void unseize_task_and_threads(const struct pstree_item *item, int st)
|
|
||||||
{
|
|
||||||
int i;
|
|
||||||
|
|
||||||
if (item->state == TASK_DEAD)
|
|
||||||
return;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* The st is the state we want to switch tasks into,
|
|
||||||
* the item->state is the state task was in when we seized one.
|
|
||||||
*/
|
|
||||||
|
|
||||||
unseize_task(item->pid.real, item->state, st);
|
|
||||||
|
|
||||||
if (st == TASK_DEAD)
|
|
||||||
return;
|
|
||||||
|
|
||||||
for (i = 1; i < item->nr_threads; i++)
|
|
||||||
if (ptrace(PTRACE_DETACH, item->threads[i].real, NULL, NULL))
|
|
||||||
pr_perror("Unable to detach from %d", item->threads[i].real);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pstree_wait(struct pstree_item *root_item)
|
|
||||||
{
|
|
||||||
struct pstree_item *item = root_item;
|
|
||||||
int pid, status, i;
|
|
||||||
|
|
||||||
for_each_pstree_item(item) {
|
|
||||||
|
|
||||||
if (item->state == TASK_DEAD)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
for (i = 0; i < item->nr_threads; i++) {
|
|
||||||
pid = wait4(-1, &status, __WALL, NULL);
|
|
||||||
if (pid < 0) {
|
|
||||||
pr_perror("wait4 failed");
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
|
|
||||||
pr_err("Unexpected exit code %d of %d\n", status, pid);
|
|
||||||
BUG();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pid = wait4(-1, &status, __WALL, NULL);
|
|
||||||
if (pid > 0) {
|
|
||||||
pr_err("Unexpected child %d", pid);
|
|
||||||
BUG();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void pstree_switch_state(struct pstree_item *root_item, int st)
|
|
||||||
{
|
|
||||||
struct pstree_item *item = root_item;
|
|
||||||
|
|
||||||
pr_info("Unfreezing tasks into %d\n", st);
|
|
||||||
for_each_pstree_item(item)
|
|
||||||
unseize_task_and_threads(item, st);
|
|
||||||
|
|
||||||
if (st == TASK_DEAD)
|
|
||||||
pstree_wait(root_item);
|
|
||||||
}
|
|
||||||
|
|
||||||
static pid_t item_ppid(const struct pstree_item *item)
|
|
||||||
{
|
|
||||||
item = item->parent;
|
|
||||||
return item ? item->pid.real : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool thread_collected(struct pstree_item *i, pid_t tid)
|
|
||||||
{
|
|
||||||
int t;
|
|
||||||
|
|
||||||
if (i->pid.real == tid) /* thread leader is collected as task */
|
|
||||||
return true;
|
|
||||||
|
|
||||||
for (t = 0; t < i->nr_threads; t++)
|
|
||||||
if (tid == i->threads[t].real)
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int collect_threads(struct pstree_item *item)
|
|
||||||
{
|
|
||||||
struct pid *threads = NULL;
|
|
||||||
int nr_threads = 0, i = 0, ret, nr_inprogress, nr_stopped = 0;
|
|
||||||
|
|
||||||
ret = parse_threads(item->pid.real, &threads, &nr_threads);
|
|
||||||
if (ret < 0)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
if ((item->state == TASK_DEAD) && (nr_threads > 1)) {
|
|
||||||
pr_err("Zombies with threads are not supported\n");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* The number of threads can't be less than allready frozen */
|
|
||||||
item->threads = xrealloc(item->threads, nr_threads * sizeof(struct pid));
|
|
||||||
if (item->threads == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
if (item->nr_threads == 0) {
|
|
||||||
item->threads[0].real = item->pid.real;
|
|
||||||
item->nr_threads = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
nr_inprogress = 0;
|
|
||||||
for (i = 0; i < nr_threads; i++) {
|
|
||||||
pid_t pid = threads[i].real;
|
|
||||||
|
|
||||||
if (thread_collected(item, pid))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
nr_inprogress++;
|
|
||||||
|
|
||||||
pr_info("\tSeizing %d's %d thread\n",
|
|
||||||
item->pid.real, pid);
|
|
||||||
|
|
||||||
ret = seize_task(pid, item_ppid(item), &dmpi(item)->pi_creds);
|
|
||||||
if (ret < 0) {
|
|
||||||
/*
|
|
||||||
* Here is a race window between parse_threads() and seize(),
|
|
||||||
* so the task could die for these time.
|
|
||||||
* Don't worry, will try again on the next attempt. The number
|
|
||||||
* of attempts is restricted, so it will exit if something
|
|
||||||
* really wrong.
|
|
||||||
*/
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BUG_ON(item->nr_threads + 1 > nr_threads);
|
|
||||||
item->threads[item->nr_threads].real = pid;
|
|
||||||
item->nr_threads++;
|
|
||||||
|
|
||||||
if (ret == TASK_DEAD) {
|
|
||||||
pr_err("Zombie thread not supported\n");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ret == TASK_STOPPED) {
|
|
||||||
nr_stopped++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nr_stopped && nr_stopped != nr_inprogress) {
|
|
||||||
pr_err("Individually stopped threads not supported\n");
|
|
||||||
goto err;
|
|
||||||
}
|
|
||||||
|
|
||||||
xfree(threads);
|
|
||||||
return nr_inprogress;
|
|
||||||
|
|
||||||
err:
|
|
||||||
xfree(threads);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int collect_loop(struct pstree_item *item,
|
|
||||||
int (*collect)(struct pstree_item *))
|
|
||||||
{
|
|
||||||
int attempts = NR_ATTEMPTS, nr_inprogress = 1;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* While we scan the proc and seize the children/threads
|
|
||||||
* new ones can appear (with clone(CLONE_PARENT) or with
|
|
||||||
* pthread_create). Thus, after one go, we need to repeat
|
|
||||||
* the scan-and-freeze again collecting new arrivals. As
|
|
||||||
* new guys may appear again we do NR_ATTEMPTS passes and
|
|
||||||
* fail to seize the item if new tasks/threads still
|
|
||||||
* appear.
|
|
||||||
*/
|
|
||||||
|
|
||||||
while (nr_inprogress > 0 && attempts) {
|
|
||||||
attempts--;
|
|
||||||
nr_inprogress = collect(item);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* We may fail to collect items or run out of attempts.
|
|
||||||
* In the former case nr_inprogress will be negative, in
|
|
||||||
* the latter -- positive. Thus it's enough just to check
|
|
||||||
* for "no more new stuff" and say "we're OK" if so.
|
|
||||||
*/
|
|
||||||
|
|
||||||
return (nr_inprogress == 0) ? 0 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int collect_task(struct pstree_item *item)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
ret = collect_loop(item, collect_threads);
|
|
||||||
if (ret < 0)
|
|
||||||
goto err_close;
|
|
||||||
|
|
||||||
/* Depth-first search (DFS) is used for traversing a process tree. */
|
|
||||||
ret = collect_loop(item, collect_children);
|
|
||||||
if (ret < 0)
|
|
||||||
goto err_close;
|
|
||||||
|
|
||||||
if ((item->state == TASK_DEAD) && !list_empty(&item->children)) {
|
|
||||||
pr_err("Zombie with children?! O_o Run, run, run!\n");
|
|
||||||
goto err_close;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pstree_alloc_cores(item))
|
|
||||||
goto err_close;
|
|
||||||
|
|
||||||
pr_info("Collected %d in %d state\n", item->pid.real, item->state);
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_close:
|
|
||||||
close_pid_proc();
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int collect_pstree_ids_predump(void)
|
static int collect_pstree_ids_predump(void)
|
||||||
{
|
{
|
||||||
struct pstree_item *item;
|
struct pstree_item *item;
|
||||||
@ -1101,36 +753,6 @@ int collect_pstree_ids(void)
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int collect_pstree(pid_t pid)
|
|
||||||
{
|
|
||||||
int ret;
|
|
||||||
|
|
||||||
timing_start(TIME_FREEZING);
|
|
||||||
|
|
||||||
root_item = alloc_pstree_item();
|
|
||||||
if (root_item == NULL)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
root_item->pid.real = pid;
|
|
||||||
ret = seize_task(pid, -1, &dmpi(root_item)->pi_creds);
|
|
||||||
if (ret < 0)
|
|
||||||
goto err;
|
|
||||||
pr_info("Seized task %d, state %d\n", pid, ret);
|
|
||||||
root_item->state = ret;
|
|
||||||
|
|
||||||
ret = collect_task(root_item);
|
|
||||||
if (ret < 0)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
timing_stop(TIME_FREEZING);
|
|
||||||
timing_start(TIME_FROZEN);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
err:
|
|
||||||
pstree_switch_state(root_item, TASK_ALIVE);
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int collect_file_locks(void)
|
static int collect_file_locks(void)
|
||||||
{
|
{
|
||||||
return parse_file_locks();
|
return parse_file_locks();
|
||||||
|
@ -242,4 +242,6 @@ int parse_cgroups(struct list_head *cgroups, unsigned int *n_cgroups);
|
|||||||
/* callback for AUFS support */
|
/* callback for AUFS support */
|
||||||
extern int aufs_parse(struct mount_info *mi);
|
extern int aufs_parse(struct mount_info *mi);
|
||||||
|
|
||||||
|
int parse_children(pid_t pid, pid_t **_c, int *_n);
|
||||||
|
|
||||||
#endif /* __CR_PROC_PARSE_H__ */
|
#endif /* __CR_PROC_PARSE_H__ */
|
||||||
|
7
include/seize.h
Normal file
7
include/seize.h
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#ifndef __CR_SEIZE_H__
|
||||||
|
#define __CR_SEIZE_H__
|
||||||
|
|
||||||
|
extern int collect_pstree(pid_t pid);
|
||||||
|
extern void pstree_switch_state(struct pstree_item *root_item, int st);
|
||||||
|
|
||||||
|
#endif
|
61
proc_parse.c
61
proc_parse.c
@ -2077,3 +2077,64 @@ bool proc_status_creds_eq(struct proc_status_creds *o1, struct proc_status_creds
|
|||||||
{
|
{
|
||||||
return memcmp(o1, o2, sizeof(struct proc_status_creds)) == 0;
|
return memcmp(o1, o2, sizeof(struct proc_status_creds)) == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int parse_children(pid_t pid, pid_t **_c, int *_n)
|
||||||
|
{
|
||||||
|
pid_t *ch = NULL;
|
||||||
|
int nr = 0;
|
||||||
|
DIR *dir;
|
||||||
|
struct dirent *de;
|
||||||
|
|
||||||
|
dir = opendir_proc(pid, "task");
|
||||||
|
if (dir == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
while ((de = readdir(dir))) {
|
||||||
|
int fd, len;
|
||||||
|
char *pos;
|
||||||
|
|
||||||
|
if (dir_dots(de))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fd = open_proc(pid, "task/%s/children", de->d_name);
|
||||||
|
if (fd < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
len = read(fd, buf, BUF_SIZE);
|
||||||
|
close(fd);
|
||||||
|
if (len < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
buf[len] = '\0';
|
||||||
|
pos = buf;
|
||||||
|
while (1) {
|
||||||
|
pid_t val, *tmp;
|
||||||
|
|
||||||
|
val = strtol(pos, &pos, 0);
|
||||||
|
if (!val) {
|
||||||
|
BUG_ON(*pos != '\0');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = xrealloc(ch, (nr + 1) * sizeof(pid_t));
|
||||||
|
if (!tmp)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
ch = tmp;
|
||||||
|
ch[nr] = val;
|
||||||
|
nr++;
|
||||||
|
pos++; /* space goes after each pid */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*_c = ch;
|
||||||
|
*_n = nr;
|
||||||
|
|
||||||
|
closedir(dir);
|
||||||
|
return 0;
|
||||||
|
err:
|
||||||
|
closedir(dir);
|
||||||
|
xfree(ch);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
337
seize.c
Normal file
337
seize.c
Normal file
@ -0,0 +1,337 @@
|
|||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/time.h>
|
||||||
|
#include <sys/resource.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#include "compiler.h"
|
||||||
|
#include "cr_options.h"
|
||||||
|
#include "pstree.h"
|
||||||
|
#include "ptrace.h"
|
||||||
|
#include "seize.h"
|
||||||
|
#include "stats.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
#include "util.h"
|
||||||
|
|
||||||
|
#define NR_ATTEMPTS 5
|
||||||
|
|
||||||
|
static inline bool child_collected(struct pstree_item *i, pid_t pid)
|
||||||
|
{
|
||||||
|
struct pstree_item *c;
|
||||||
|
|
||||||
|
list_for_each_entry(c, &i->children, sibling)
|
||||||
|
if (c->pid.real == pid)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int collect_task(struct pstree_item *item);
|
||||||
|
static int collect_children(struct pstree_item *item)
|
||||||
|
{
|
||||||
|
pid_t *ch;
|
||||||
|
int ret, i, nr_children, nr_inprogress;
|
||||||
|
|
||||||
|
ret = parse_children(item->pid.real, &ch, &nr_children);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
nr_inprogress = 0;
|
||||||
|
for (i = 0; i < nr_children; i++) {
|
||||||
|
struct pstree_item *c;
|
||||||
|
pid_t pid = ch[i];
|
||||||
|
|
||||||
|
/* Is it already frozen? */
|
||||||
|
if (child_collected(item, pid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
nr_inprogress++;
|
||||||
|
|
||||||
|
pr_info("Seized task %d, state %d\n", pid, ret);
|
||||||
|
|
||||||
|
c = alloc_pstree_item();
|
||||||
|
if (c == NULL) {
|
||||||
|
ret = -1;
|
||||||
|
goto free;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = seize_task(pid, item->pid.real, &dmpi(c)->pi_creds);
|
||||||
|
if (ret < 0) {
|
||||||
|
/*
|
||||||
|
* Here is a race window between parse_children() and seize(),
|
||||||
|
* so the task could die for these time.
|
||||||
|
* Don't worry, will try again on the next attempt. The number
|
||||||
|
* of attempts is restricted, so it will exit if something
|
||||||
|
* really wrong.
|
||||||
|
*/
|
||||||
|
ret = 0;
|
||||||
|
xfree(c);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
c->pid.real = pid;
|
||||||
|
c->parent = item;
|
||||||
|
c->state = ret;
|
||||||
|
list_add_tail(&c->sibling, &item->children);
|
||||||
|
|
||||||
|
/* Here is a recursive call (Depth-first search) */
|
||||||
|
ret = collect_task(c);
|
||||||
|
if (ret < 0)
|
||||||
|
goto free;
|
||||||
|
}
|
||||||
|
free:
|
||||||
|
xfree(ch);
|
||||||
|
return ret < 0 ? ret : nr_inprogress;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void unseize_task_and_threads(const struct pstree_item *item, int st)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (item->state == TASK_DEAD)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The st is the state we want to switch tasks into,
|
||||||
|
* the item->state is the state task was in when we seized one.
|
||||||
|
*/
|
||||||
|
|
||||||
|
unseize_task(item->pid.real, item->state, st);
|
||||||
|
|
||||||
|
if (st == TASK_DEAD)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (i = 1; i < item->nr_threads; i++)
|
||||||
|
if (ptrace(PTRACE_DETACH, item->threads[i].real, NULL, NULL))
|
||||||
|
pr_perror("Unable to detach from %d", item->threads[i].real);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pstree_wait(struct pstree_item *root_item)
|
||||||
|
{
|
||||||
|
struct pstree_item *item = root_item;
|
||||||
|
int pid, status, i;
|
||||||
|
|
||||||
|
for_each_pstree_item(item) {
|
||||||
|
|
||||||
|
if (item->state == TASK_DEAD)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for (i = 0; i < item->nr_threads; i++) {
|
||||||
|
pid = wait4(-1, &status, __WALL, NULL);
|
||||||
|
if (pid < 0) {
|
||||||
|
pr_perror("wait4 failed");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGKILL) {
|
||||||
|
pr_err("Unexpected exit code %d of %d\n", status, pid);
|
||||||
|
BUG();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pid = wait4(-1, &status, __WALL, NULL);
|
||||||
|
if (pid > 0) {
|
||||||
|
pr_err("Unexpected child %d", pid);
|
||||||
|
BUG();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void pstree_switch_state(struct pstree_item *root_item, int st)
|
||||||
|
{
|
||||||
|
struct pstree_item *item = root_item;
|
||||||
|
|
||||||
|
pr_info("Unfreezing tasks into %d\n", st);
|
||||||
|
for_each_pstree_item(item)
|
||||||
|
unseize_task_and_threads(item, st);
|
||||||
|
|
||||||
|
if (st == TASK_DEAD)
|
||||||
|
pstree_wait(root_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
static pid_t item_ppid(const struct pstree_item *item)
|
||||||
|
{
|
||||||
|
item = item->parent;
|
||||||
|
return item ? item->pid.real : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool thread_collected(struct pstree_item *i, pid_t tid)
|
||||||
|
{
|
||||||
|
int t;
|
||||||
|
|
||||||
|
if (i->pid.real == tid) /* thread leader is collected as task */
|
||||||
|
return true;
|
||||||
|
|
||||||
|
for (t = 0; t < i->nr_threads; t++)
|
||||||
|
if (tid == i->threads[t].real)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int collect_threads(struct pstree_item *item)
|
||||||
|
{
|
||||||
|
struct pid *threads = NULL;
|
||||||
|
int nr_threads = 0, i = 0, ret, nr_inprogress, nr_stopped = 0;
|
||||||
|
|
||||||
|
ret = parse_threads(item->pid.real, &threads, &nr_threads);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
if ((item->state == TASK_DEAD) && (nr_threads > 1)) {
|
||||||
|
pr_err("Zombies with threads are not supported\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The number of threads can't be less than allready frozen */
|
||||||
|
item->threads = xrealloc(item->threads, nr_threads * sizeof(struct pid));
|
||||||
|
if (item->threads == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
if (item->nr_threads == 0) {
|
||||||
|
item->threads[0].real = item->pid.real;
|
||||||
|
item->nr_threads = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
nr_inprogress = 0;
|
||||||
|
for (i = 0; i < nr_threads; i++) {
|
||||||
|
pid_t pid = threads[i].real;
|
||||||
|
|
||||||
|
if (thread_collected(item, pid))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
nr_inprogress++;
|
||||||
|
|
||||||
|
pr_info("\tSeizing %d's %d thread\n",
|
||||||
|
item->pid.real, pid);
|
||||||
|
|
||||||
|
ret = seize_task(pid, item_ppid(item), &dmpi(item)->pi_creds);
|
||||||
|
if (ret < 0) {
|
||||||
|
/*
|
||||||
|
* Here is a race window between parse_threads() and seize(),
|
||||||
|
* so the task could die for these time.
|
||||||
|
* Don't worry, will try again on the next attempt. The number
|
||||||
|
* of attempts is restricted, so it will exit if something
|
||||||
|
* really wrong.
|
||||||
|
*/
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BUG_ON(item->nr_threads + 1 > nr_threads);
|
||||||
|
item->threads[item->nr_threads].real = pid;
|
||||||
|
item->nr_threads++;
|
||||||
|
|
||||||
|
if (ret == TASK_DEAD) {
|
||||||
|
pr_err("Zombie thread not supported\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret == TASK_STOPPED) {
|
||||||
|
nr_stopped++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (nr_stopped && nr_stopped != nr_inprogress) {
|
||||||
|
pr_err("Individually stopped threads not supported\n");
|
||||||
|
goto err;
|
||||||
|
}
|
||||||
|
|
||||||
|
xfree(threads);
|
||||||
|
return nr_inprogress;
|
||||||
|
|
||||||
|
err:
|
||||||
|
xfree(threads);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int collect_loop(struct pstree_item *item,
|
||||||
|
int (*collect)(struct pstree_item *))
|
||||||
|
{
|
||||||
|
int attempts = NR_ATTEMPTS, nr_inprogress = 1;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* While we scan the proc and seize the children/threads
|
||||||
|
* new ones can appear (with clone(CLONE_PARENT) or with
|
||||||
|
* pthread_create). Thus, after one go, we need to repeat
|
||||||
|
* the scan-and-freeze again collecting new arrivals. As
|
||||||
|
* new guys may appear again we do NR_ATTEMPTS passes and
|
||||||
|
* fail to seize the item if new tasks/threads still
|
||||||
|
* appear.
|
||||||
|
*/
|
||||||
|
|
||||||
|
while (nr_inprogress > 0 && attempts) {
|
||||||
|
attempts--;
|
||||||
|
nr_inprogress = collect(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* We may fail to collect items or run out of attempts.
|
||||||
|
* In the former case nr_inprogress will be negative, in
|
||||||
|
* the latter -- positive. Thus it's enough just to check
|
||||||
|
* for "no more new stuff" and say "we're OK" if so.
|
||||||
|
*/
|
||||||
|
|
||||||
|
return (nr_inprogress == 0) ? 0 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int collect_task(struct pstree_item *item)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = collect_loop(item, collect_threads);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err_close;
|
||||||
|
|
||||||
|
/* Depth-first search (DFS) is used for traversing a process tree. */
|
||||||
|
ret = collect_loop(item, collect_children);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err_close;
|
||||||
|
|
||||||
|
if ((item->state == TASK_DEAD) && !list_empty(&item->children)) {
|
||||||
|
pr_err("Zombie with children?! O_o Run, run, run!\n");
|
||||||
|
goto err_close;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pstree_alloc_cores(item))
|
||||||
|
goto err_close;
|
||||||
|
|
||||||
|
pr_info("Collected %d in %d state\n", item->pid.real, item->state);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_close:
|
||||||
|
close_pid_proc();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int collect_pstree(pid_t pid)
|
||||||
|
{
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
timing_start(TIME_FREEZING);
|
||||||
|
|
||||||
|
root_item = alloc_pstree_item();
|
||||||
|
if (root_item == NULL)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
root_item->pid.real = pid;
|
||||||
|
ret = seize_task(pid, -1, &dmpi(root_item)->pi_creds);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err;
|
||||||
|
pr_info("Seized task %d, state %d\n", pid, ret);
|
||||||
|
root_item->state = ret;
|
||||||
|
|
||||||
|
ret = collect_task(root_item);
|
||||||
|
if (ret < 0)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
timing_stop(TIME_FREEZING);
|
||||||
|
timing_start(TIME_FROZEN);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
err:
|
||||||
|
pstree_switch_state(root_item, TASK_ALIVE);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user