From 2f172c8b24a6c0da424c464cd739e8c9eec27068 Mon Sep 17 00:00:00 2001 From: Andrey Vagin Date: Thu, 6 Aug 2015 12:37:26 +0300 Subject: [PATCH] crtools: split cr-dump.c in two files Signed-off-by: Andrey Vagin Signed-off-by: Pavel Emelyanov --- Makefile.crtools | 1 + cr-dump.c | 380 +------------------------------------------ include/proc_parse.h | 2 + include/seize.h | 7 + proc_parse.c | 61 +++++++ seize.c | 337 ++++++++++++++++++++++++++++++++++++++ 6 files changed, 409 insertions(+), 379 deletions(-) create mode 100644 include/seize.h create mode 100644 seize.c diff --git a/Makefile.crtools b/Makefile.crtools index a33168473..42c5a7cf1 100644 --- a/Makefile.crtools +++ b/Makefile.crtools @@ -73,6 +73,7 @@ obj-y += sd-daemon.o obj-y += plugin.o obj-y += cr-errno.o obj-y += pie/pie-relocs.o +obj-y += seize.o ifneq ($(MAKECMDGOALS),clean) incdeps := y diff --git a/cr-dump.c b/cr-dump.c index af1b281cc..806544d6d 100644 --- a/cr-dump.c +++ b/cr-dump.c @@ -79,11 +79,10 @@ #include "security.h" #include "lsm.h" #include "seccomp.h" +#include "seize.h" #include "asm/dump.h" -#define NR_ATTEMPTS 5 - static char loc_buf[PAGE_SIZE]; static void close_vma_file(struct vma_area *vma) @@ -713,353 +712,6 @@ err: 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) { struct pstree_item *item; @@ -1101,36 +753,6 @@ int collect_pstree_ids(void) 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) { return parse_file_locks(); diff --git a/include/proc_parse.h b/include/proc_parse.h index 20bd5bcdc..d084e7602 100644 --- a/include/proc_parse.h +++ b/include/proc_parse.h @@ -242,4 +242,6 @@ int parse_cgroups(struct list_head *cgroups, unsigned int *n_cgroups); /* callback for AUFS support */ extern int aufs_parse(struct mount_info *mi); +int parse_children(pid_t pid, pid_t **_c, int *_n); + #endif /* __CR_PROC_PARSE_H__ */ diff --git a/include/seize.h b/include/seize.h new file mode 100644 index 000000000..3c23e2457 --- /dev/null +++ b/include/seize.h @@ -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 diff --git a/proc_parse.c b/proc_parse.c index 3b65c01bb..2b25dea08 100644 --- a/proc_parse.c +++ b/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; } + +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; +} + diff --git a/seize.c b/seize.c new file mode 100644 index 000000000..e9be332aa --- /dev/null +++ b/seize.c @@ -0,0 +1,337 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} +