char *run_in_shell_and_save_output(int flags, const char *cmd, const char *dir, size_t *size_p) { flags |= EXECFLG_OUTPUT; flags &= ~EXECFLG_INPUT; const char *argv[] = { "/bin/sh", "-c", cmd, NULL }; int pipeout[2]; pid_t child = fork_execv_on_steroids(flags, (char **)argv, pipeout, /*env_vec:*/ NULL, dir, /*uid (unused):*/ 0); size_t pos = 0; char *result = NULL; while (1) { result = (char*) xrealloc(result, pos + 4*1024 + 1); size_t sz = safe_read(pipeout[0], result + pos, 4*1024); if (sz <= 0) { break; } pos += sz; } result[pos] = '\0'; if (size_p) *size_p = pos; close(pipeout[0]); safe_waitpid(child, NULL, 0); return result; }
/* Signal pipe handler */ static gboolean handle_signal_cb(GIOChannel *gio, GIOCondition condition, gpointer ptr_unused) { uint8_t signo; gsize len = 0; g_io_channel_read_chars(gio, (void*) &signo, 1, &len, NULL); if (len == 1) { /* we did receive a signal */ log_debug("Got signal %d through signal pipe", signo); if (signo != SIGCHLD) g_main_loop_quit(s_main_loop); else { pid_t cpid; int status; while ((cpid = safe_waitpid(-1, &status, WNOHANG)) > 0) { if (WIFSIGNALED(status)) log_debug("abrt-server(%d) signaled with %d", cpid, WTERMSIG(status)); else if (WIFEXITED(status)) log_debug("abrt-server(%d) exited with %d", cpid, WEXITSTATUS(status)); else { log_debug("abrt-server(%d) is being debugged", cpid); continue; } remove_abrt_server_proc(cpid, status); } } } start_idle_timeout(); return TRUE; /* "please don't remove this event" */ }
/// Blocks to wait for completion of a subprocess. /// /// \param pid Identifier of the process to wait for. /// /// \return The termination status of the child process that terminated. /// /// \throw process::system_error If the call to wait(2) fails. process::status process::wait(const int pid) { const process::status status = safe_waitpid(pid); { signals::interrupts_inhibiter inhibiter; signals::remove_pid_to_kill(pid); } return status; }
static int decompress_using_fork_execvp(const char** cmd, int fdi, int fdo) { pid_t child = fork(); if (child < 0) { VERB1 perror_msg("fork() for decompression"); return -1; } if (child == 0) { close(STDIN_FILENO); if (dup2(fdi, STDIN_FILENO) < 0) { VERB1 perror_msg("Decompression failed: dup2(fdi, STDIN_FILENO)"); exit(EXIT_FAILURE); } close(STDOUT_FILENO); if (dup2(fdo, STDOUT_FILENO) < 0) { VERB1 perror_msg("Decompression failed: dup2(fdo, STDOUT_FILENO)"); exit(EXIT_FAILURE); } execvp(cmd[0], (char **)cmd); VERB1 perror_msg("Decompression failed: execlp('%s')", cmd[0]); exit(EXIT_FAILURE); } int status = 0; int r = safe_waitpid(child, &status, 0); if (r < 0) { VERB1 perror_msg("Decompression failed: waitpid($1) failed"); return -2; } if (!WIFEXITED(status)) { log_info("Decompression process returned abnormally"); return -3; } if (WEXITSTATUS(status) != 0) { log_info("Decompression process exited with %d", WEXITSTATUS(r)); return -4; } return 0; }
// generic signal handler static void signal_handler(int signo) { #define err signo if (SIGALRM == signo) { bb_error_msg_and_die("timed out"); } // SIGCHLD. reap zombies if (safe_waitpid(G.helper_pid, &err, WNOHANG) > 0) { if (WIFSIGNALED(err)) bb_error_msg_and_die("helper killed by signal %u", WTERMSIG(err)); if (WIFEXITED(err)) { G.helper_pid = 0; if (WEXITSTATUS(err)) bb_error_msg_and_die("helper exited (%u)", WEXITSTATUS(err)); } } #undef err }
static unsigned custom(struct svdir *s, char c) { int pid; int w; char a[10]; struct stat st; char *prog[2]; if (s->islog) return 0; strcpy(a, "control/?"); a[8] = c; /* replace '?' */ if (stat(a, &st) == 0) { if (st.st_mode & S_IXUSR) { pid = vfork(); if (pid == -1) { warn_cannot("vfork for control/?"); return 0; } if (!pid) { /* child */ if (haslog && dup2(logpipe.wr, 1) == -1) warn_cannot("setup stdout for control/?"); prog[0] = a; prog[1] = NULL; execv(a, prog); fatal_cannot("run control/?"); } /* parent */ while (safe_waitpid(pid, &w, 0) == -1) { warn_cannot("wait for child control/?"); return 0; } return !wait_exitcode(w); } } else { if (errno != ENOENT) warn_cannot("stat control/?"); } return 0; }
/* This does a fork/exec in one call, using vfork(). Returns PID of new child, * -1 for failure. Runs argv[0], searching path if that has no / in it. */ pid_t FAST_FUNC spawn(char **argv) { /* Compiler should not optimize stores here */ volatile int failed; pid_t pid; fflush_all(); /* Be nice to nommu machines. */ failed = 0; pid = vfork(); if (pid < 0) /* error */ return pid; if (!pid) { /* child */ /* This macro is ok - it doesn't do NOEXEC/NOFORK tricks */ BB_EXECVP(argv[0], argv); /* We are (maybe) sharing a stack with blocked parent, * let parent know we failed and then exit to unblock parent * (but don't run atexit() stuff, which would screw up parent.) */ failed = errno; /* mount, for example, does not want the message */ /*bb_perror_msg("can't execute '%s'", argv[0]);*/ _exit(111); } /* parent */ /* Unfortunately, this is not reliable: according to standards * vfork() can be equivalent to fork() and we won't see value * of 'failed'. * Interested party can wait on pid and learn exit code. * If 111 - then it (most probably) failed to exec */ if (failed) { safe_waitpid(pid, NULL, 0); /* prevent zombie */ errno = failed; return -1; } return pid; }
static unsigned custom(struct svdir *s, char c) { pid_t pid; int w; char a[10]; struct stat st; if (s->islog) return 0; strcpy(a, "control/?"); a[8] = c; /* replace '?' */ if (stat(a, &st) == 0) { if (st.st_mode & S_IXUSR) { pid = vfork(); if (pid == -1) { warn_cannot("vfork for control/?"); return 0; } if (pid == 0) { /* child */ if (haslog && dup2(logpipe.wr, 1) == -1) warn_cannot("setup stdout for control/?"); execl(a, a, (char *) NULL); fatal_cannot("run control/?"); } /* parent */ if (safe_waitpid(pid, &w, 0) == -1) { warn_cannot("wait for child control/?"); return 0; } return WEXITSTATUS(w) == 0; } } else { if (errno != ENOENT) warn_cannot("stat control/?"); } return 0; }
static int run_post_create(const char *dirname) { /* If doesn't start with "g_settings_dump_location/"... */ if (!dir_is_in_dump_location(dirname)) { /* Then refuse to operate on it (someone is attacking us??) */ error_msg("Bad problem directory name '%s', should start with: '%s'", dirname, g_settings_dump_location); return 400; /* Bad Request */ } if (!dir_has_correct_permissions(dirname, DD_PERM_EVENTS)) { error_msg("Problem directory '%s' has wrong owner or group", dirname); return 400; /* */ } /* Check completness */ { struct dump_dir *dd = dd_opendir(dirname, DD_OPEN_READONLY); char *provoker = NULL; const bool event_dir = dd && problem_dump_dir_was_provoked_by_abrt_event(dd, &provoker); if (event_dir) { if (g_settings_debug_level == 0) { error_msg("Removing problem provoked by ABRT(pid:%s): '%s'", provoker, dirname); dd_delete(dd); } else { char *dumpdir = NULL; char *event = NULL; char *reason = NULL; char *cmdline = NULL; /* Ignore errors */ dd_get_env_variable(dd, "DUMP_DIR", &dumpdir); dd_get_env_variable(dd, "EVENT", &event); reason = dd_load_text(dd, FILENAME_REASON); cmdline = dd_load_text(dd, FILENAME_CMDLINE); error_msg("ABRT_SERVER_PID=%s;DUMP_DIR='%s';EVENT='%s';REASON='%s';CMDLINE='%s'", provoker, dumpdir, event, reason, cmdline); } free(provoker); return 400; } const bool complete = dd && problem_dump_dir_is_complete(dd); dd_close(dd); if (complete) { error_msg("Problem directory '%s' has already been processed", dirname); return 403; } } int child_stdout_fd; int child_pid = spawn_event_handler_child(dirname, "post-create", &child_stdout_fd); char *dup_of_dir = NULL; struct strbuf *cmd_output = strbuf_new(); bool child_is_post_create = 1; /* else it is a notify child */ read_child_output: //log("Reading from event fd %d", child_stdout_fd); /* Read streamed data and split lines */ for (;;) { char buf[250]; /* usually we get one line, no need to have big buf */ errno = 0; int r = safe_read(child_stdout_fd, buf, sizeof(buf) - 1); if (r <= 0) break; buf[r] = '\0'; /* split lines in the current buffer */ char *raw = buf; char *newline; while ((newline = strchr(raw, '\n')) != NULL) { *newline = '\0'; strbuf_append_str(cmd_output, raw); char *msg = cmd_output->buf; if (child_is_post_create && prefixcmp(msg, "DUP_OF_DIR: ") == 0 ) { free(dup_of_dir); dup_of_dir = xstrdup(msg + strlen("DUP_OF_DIR: ")); } else log("%s", msg); strbuf_clear(cmd_output); /* jump to next line */ raw = newline + 1; } /* beginning of next line. the line continues by next read */ strbuf_append_str(cmd_output, raw); } /* EOF/error */ /* Wait for child to actually exit, collect status */ int status = 0; if (safe_waitpid(child_pid, &status, 0) <= 0) /* should not happen */ perror_msg("waitpid(%d)", child_pid); /* If it was a "notify[-dup]" event, then we're done */ if (!child_is_post_create) goto ret; /* exit 0 means "this is a good, non-dup dir" */ /* exit with 1 + "DUP_OF_DIR: dir" string => dup */ if (status != 0) { if (WIFSIGNALED(status)) { log("'post-create' on '%s' killed by signal %d", dirname, WTERMSIG(status)); goto delete_bad_dir; } /* else: it is WIFEXITED(status) */ if (!dup_of_dir) { log("'post-create' on '%s' exited with %d", dirname, WEXITSTATUS(status)); goto delete_bad_dir; } } const char *work_dir = (dup_of_dir ? dup_of_dir : dirname); /* Load problem_data (from the *first dir* if this one is a dup) */ struct dump_dir *dd = dd_opendir(work_dir, /*flags:*/ 0); if (!dd) /* dd_opendir already emitted error msg */ goto delete_bad_dir; /* Update count */ char *count_str = dd_load_text_ext(dd, FILENAME_COUNT, DD_FAIL_QUIETLY_ENOENT); unsigned long count = strtoul(count_str, NULL, 10); /* Don't increase crash count if we are working with newly uploaded * directory (remote crash) which already has its crash count set. */ if ((status != 0 && dup_of_dir) || count == 0) { count++; char new_count_str[sizeof(long)*3 + 2]; sprintf(new_count_str, "%lu", count); dd_save_text(dd, FILENAME_COUNT, new_count_str); /* This condition can be simplified to either * (status * != 0 && * dup_of_dir) or (count == 1). But the * chosen form is much more reliable and safe. We must not call * dd_opendir() to locked dd otherwise we go into a deadlock. */ if (strcmp(dd->dd_dirname, dirname) != 0) { /* Update the last occurrence file by the time file of the new problem */ struct dump_dir *new_dd = dd_opendir(dirname, DD_OPEN_READONLY); char *last_ocr = NULL; if (new_dd) { /* TIME must exists in a valid dump directory but we don't want to die * due to broken duplicated dump directory */ last_ocr = dd_load_text_ext(new_dd, FILENAME_TIME, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE | DD_FAIL_QUIETLY_ENOENT); dd_close(new_dd); } else { /* dd_opendir() already produced a message with good information about failure */ error_msg("Can't read the last occurrence file from the new dump directory."); } if (!last_ocr) { /* the new dump directory may lie in the dump location for some time */ log("Using current time for the last occurrence file which may be incorrect."); time_t t = time(NULL); last_ocr = xasprintf("%lu", (long)t); } dd_save_text(dd, FILENAME_LAST_OCCURRENCE, last_ocr); free(last_ocr); } } /* Reset mode/uig/gid to correct values for all files created by event run */ dd_sanitize_mode_and_owner(dd); dd_close(dd); if (!dup_of_dir) log_notice("New problem directory %s, processing", work_dir); else { log_warning("Deleting problem directory %s (dup of %s)", strrchr(dirname, '/') + 1, strrchr(dup_of_dir, '/') + 1); delete_dump_dir(dirname); } /* Run "notify[-dup]" event */ int fd; child_pid = spawn_event_handler_child( work_dir, (dup_of_dir ? "notify-dup" : "notify"), &fd ); //log("Started notify, fd %d -> %d", fd, child_stdout_fd); xmove_fd(fd, child_stdout_fd); child_is_post_create = 0; strbuf_clear(cmd_output); free(dup_of_dir); dup_of_dir = NULL; goto read_child_output; delete_bad_dir: log_warning("Deleting problem directory '%s'", dirname); delete_dump_dir(dirname); ret: strbuf_free(cmd_output); free(dup_of_dir); close(child_stdout_fd); return 0; }
static void run_scanner_prog(int fd, struct stat *statbuf, GList *match_list, char **prog) { /* fstat(fd, &statbuf) was just done by caller */ off_t cur_pos = lseek(fd, 0, SEEK_CUR); if (statbuf->st_size <= cur_pos) { /* If file was truncated, treat it as a new file. * (changing inode# causes caller to think that file was closed or renamed) */ if (statbuf->st_size < cur_pos) statbuf->st_ino++; return; /* we are at EOF, nothing to do */ } log_info("File grew by %llu bytes, from %llu to %llu", (long long)(statbuf->st_size - cur_pos), (long long)(cur_pos), (long long)(statbuf->st_size)); if (match_list && (statbuf->st_size - cur_pos) < MAX_SCAN_BLOCK) { size_t length = statbuf->st_size - cur_pos; off_t mapofs = cur_pos & ~(off_t)(page_size - 1); size_t maplen = statbuf->st_size - mapofs; void *map = mmap(NULL, maplen, PROT_READ, MAP_SHARED, fd, mapofs); if (map != MAP_FAILED) { char *start = (char*)map + (cur_pos & (page_size - 1)); for (GList *l = match_list; l; l = l->next) { log_debug("Searching for '%s' in '%.*s'", (char*)l->data, length > 20 ? 20 : (int)length, start ); if (memstr(start, length, (char*)l->data)) { log_debug("FOUND:'%s'", (char*)l->data); goto found; } } /* None of the strings are found */ log_debug("NOT FOUND"); munmap(map, maplen); lseek(fd, statbuf->st_size, SEEK_SET); return; found: ; munmap(map, maplen); } } fflush(NULL); /* paranoia */ pid_t pid = vfork(); if (pid < 0) perror_msg_and_die("vfork"); if (pid == 0) { xmove_fd(fd, STDIN_FILENO); log_debug("Execing '%s'", prog[0]); execvp(prog[0], prog); perror_msg_and_die("Can't execute '%s'", prog[0]); } safe_waitpid(pid, NULL, 0); /* Check fd's position, and move to end if it wasn't advanced. * This means that child failed to read its stdin. * This is not supposed to happen, so warn about it. */ if (lseek(fd, 0, SEEK_CUR) <= cur_pos) { log("Warning, '%s' did not process its input", prog[0]); lseek(fd, statbuf->st_size, SEEK_SET); } }
/* A binary wrapper is needed around python scripts if we want * to run them in sgid/suid mode. * * This is such a wrapper. */ int main(int argc, char **argv) { /* I18n */ setlocale(LC_ALL, ""); #if ENABLE_NLS bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE); #endif abrt_init(argv); /* Can't keep these strings/structs static: _() doesn't support that */ const char *program_usage_string = _( "& [-y] [-i BUILD_IDS_FILE|-i -] [-e PATH[:PATH]...]\n" "\t[-r REPO]\n" "\n" "Installs debuginfo packages for all build-ids listed in BUILD_IDS_FILE to\n" "ABRT system cache." ); enum { OPT_v = 1 << 0, OPT_y = 1 << 1, OPT_i = 1 << 2, OPT_e = 1 << 3, OPT_r = 1 << 4, OPT_s = 1 << 5, }; const char *build_ids = "build_ids"; const char *exact = NULL; const char *repo = NULL; const char *size_mb = NULL; struct options program_options[] = { OPT__VERBOSE(&g_verbose), OPT_BOOL ('y', "yes", NULL, _("Noninteractive, assume 'Yes' to all questions")), OPT_STRING('i', "ids", &build_ids, "BUILD_IDS_FILE", _("- means STDIN, default: build_ids")), OPT_STRING('e', "exact", &exact, "EXACT", _("Download only specified files")), OPT_STRING('r', "repo", &repo, "REPO", _("Pattern to use when searching for repos, default: *debug*")), OPT_STRING('s', "size_mb", &size_mb, "SIZE_MB", _("Ignored option")), OPT_END() }; const unsigned opts = parse_opts(argc, argv, program_options, program_usage_string); const gid_t egid = getegid(); const gid_t rgid = getgid(); const uid_t euid = geteuid(); const gid_t ruid = getuid(); /* We need to open the build ids file under the caller's UID/GID to avoid * information disclosures when reading files with changed UID. * Unfortunately, we cannot replace STDIN with the new fd because ABRT uses * STDIN to communicate with the caller. So, the following code opens a * dummy file descriptor to the build ids file and passes the new fd's proc * path to the wrapped program in the ids argument. * The new fd remains opened, the OS will close it for us. */ char *build_ids_self_fd = NULL; if (strcmp("-", build_ids) != 0) { if (setregid(egid, rgid) < 0) perror_msg_and_die("setregid(egid, rgid)"); if (setreuid(euid, ruid) < 0) perror_msg_and_die("setreuid(euid, ruid)"); const int build_ids_fd = open(build_ids, O_RDONLY); if (setregid(rgid, egid) < 0) perror_msg_and_die("setregid(rgid, egid)"); if (setreuid(ruid, euid) < 0 ) perror_msg_and_die("setreuid(ruid, euid)"); if (build_ids_fd < 0) perror_msg_and_die("Failed to open file '%s'", build_ids); /* We are not going to free this memory. There is no place to do so. */ build_ids_self_fd = xasprintf("/proc/self/fd/%d", build_ids_fd); } char tmp_directory[] = LARGE_DATA_TMP_DIR"/abrt-tmp-debuginfo.XXXXXX"; if (mkdtemp(tmp_directory) == NULL) perror_msg_and_die("Failed to create working directory"); log_info("Created working directory: %s", tmp_directory); /* name, -v, --ids, -, -y, -e, EXACT, -r, REPO, -t, PATH, --, NULL */ const char *args[13]; { const char *verbs[] = { "", "-v", "-vv", "-vvv" }; unsigned i = 0; args[i++] = EXECUTABLE; args[i++] = "--ids"; args[i++] = (build_ids_self_fd != NULL) ? build_ids_self_fd : "-"; if (g_verbose > 0) args[i++] = verbs[g_verbose <= 3 ? g_verbose : 3]; if ((opts & OPT_y)) args[i++] = "-y"; if ((opts & OPT_e)) { args[i++] = "--exact"; args[i++] = exact; } if ((opts & OPT_r)) { args[i++] = "--repo"; args[i++] = repo; } args[i++] = "--tmpdir"; args[i++] = tmp_directory; args[i++] = "--"; args[i] = NULL; } /* Switch real user/group to effective ones. * Otherwise yum library gets confused - gets EPERM (why??). */ /* do setregid only if we have to, to not upset selinux needlessly */ if (egid != rgid) IGNORE_RESULT(setregid(egid, egid)); if (euid != ruid) { IGNORE_RESULT(setreuid(euid, euid)); /* We are suid'ed! */ /* Prevent malicious user from messing up with suid'ed process: */ #if 1 // We forgot to sanitize PYTHONPATH. And who knows what else we forgot // (especially considering *future* new variables of this kind). // We switched to clearing entire environment instead: // However since we communicate through environment variables // we have to keep a whitelist of variables to keep. static const char *whitelist[] = { "REPORT_CLIENT_SLAVE", // Check if the app is being run as a slave "LANG", }; const size_t wlsize = sizeof(whitelist)/sizeof(char*); char *setlist[sizeof(whitelist)/sizeof(char*)] = { 0 }; char *p = NULL; for (size_t i = 0; i < wlsize; i++) if ((p = getenv(whitelist[i])) != NULL) setlist[i] = xstrdup(p); // Now we can clear the environment clearenv(); // And once again set whitelisted variables for (size_t i = 0; i < wlsize; i++) if (setlist[i] != NULL) { xsetenv(whitelist[i], setlist[i]); free(setlist[i]); } #else /* Clear dangerous stuff from env */ static const char forbid[] = "LD_LIBRARY_PATH" "\0" "LD_PRELOAD" "\0" "LD_TRACE_LOADED_OBJECTS" "\0" "LD_BIND_NOW" "\0" "LD_AOUT_LIBRARY_PATH" "\0" "LD_AOUT_PRELOAD" "\0" "LD_NOWARN" "\0" "LD_KEEPDIR" "\0" ; const char *p = forbid; do { unsetenv(p); p += strlen(p) + 1; } while (*p); #endif /* Set safe PATH */ // Adding configure --bindir and --sbindir to the PATH so that // abrt-action-install-debuginfo doesn't fail when spawning // abrt-action-trim-files char path_env[] = "PATH=/usr/sbin:/sbin:/usr/bin:/bin:"BIN_DIR":"SBIN_DIR; if (euid != 0) strcpy(path_env, "PATH=/usr/bin:/bin:"BIN_DIR); putenv(path_env); /* Use safe umask */ umask(0022); } pid_t pid = fork(); if (pid < 0) perror_msg_and_die("fork"); if (pid == 0) { execvp(EXECUTABLE, (char **)args); error_msg_and_die("Can't execute %s", EXECUTABLE); } int status; if (safe_waitpid(pid, &status, 0) < 0) perror_msg_and_die("waitpid"); if (rmdir(tmp_directory) >= 0) log_info("Removed working directory: %s", tmp_directory); else if (errno != ENOENT) perror_msg("Failed to remove working directory"); /* Normal execution should exit here. */ if (WIFEXITED(status)) return WEXITSTATUS(status); if (WIFSIGNALED(status)) error_msg_and_die("Child terminated with signal %d", WTERMSIG(status)); error_msg_and_die("Child exit failed"); }
/** * * @param[out] status See `man 2 wait` for status information. * @return Malloc'ed string */ static char* exec_vp(char **args, int redirect_stderr, int exec_timeout_sec, int *status) { /* Nuke everything which may make setlocale() switch to non-POSIX locale: * we need to avoid having gdb output in some obscure language. */ static const char *const env_vec[] = { "LANG", "LC_ALL", "LC_COLLATE", "LC_CTYPE", "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME", /* Workaround for * http://sourceware.org/bugzilla/show_bug.cgi?id=9622 * (gdb emitting ESC sequences even with -batch) */ "TERM", NULL }; int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETSID | EXECFLG_QUIET; if (redirect_stderr) flags |= EXECFLG_ERR2OUT; VERB1 flags &= ~EXECFLG_QUIET; int pipeout[2]; pid_t child = fork_execv_on_steroids(flags, args, pipeout, (char**)env_vec, /*dir:*/ NULL, /*uid(unused):*/ 0); /* We use this function to run gdb and unstrip. Bugs in gdb or corrupted * coredumps were observed to cause gdb to enter infinite loop. * Therefore we have a (largish) timeout, after which we kill the child. */ ndelay_on(pipeout[0]); int t = time(NULL); /* int is enough, no need to use time_t */ int endtime = t + exec_timeout_sec; struct strbuf *buf_out = strbuf_new(); while (1) { int timeout = endtime - t; if (timeout < 0) { kill(child, SIGKILL); strbuf_append_strf(buf_out, "\n" "Timeout exceeded: %u seconds, killing %s.\n" "Looks like gdb hung while generating backtrace.\n" "This may be a bug in gdb. Consider submitting a bug report to gdb developers.\n" "Please attach coredump from this crash to the bug report if you do.\n", exec_timeout_sec, args[0] ); break; } /* We don't check poll result - checking read result is enough */ struct pollfd pfd; pfd.fd = pipeout[0]; pfd.events = POLLIN; poll(&pfd, 1, timeout * 1000); char buff[1024]; int r = read(pipeout[0], buff, sizeof(buff) - 1); if (r <= 0) { /* I did see EAGAIN happening here */ if (r < 0 && errno == EAGAIN) goto next; break; } buff[r] = '\0'; strbuf_append_str(buf_out, buff); next: t = time(NULL); } close(pipeout[0]); /* Prevent having zombie child process, and maybe collect status * (note that status == NULL is ok too) */ safe_waitpid(child, status, 0); return strbuf_free_nobuf(buf_out); }
static int writeTarFile(const int tar_fd, const int verboseFlag, const unsigned long dereferenceFlag, const llist_t *include, const llist_t *exclude, const int gzip) { pid_t gzipPid = 0; int errorFlag = FALSE; struct TarBallInfo tbInfo; tbInfo.hlInfoHead = NULL; fchmod(tar_fd, 0644); tbInfo.tarFd = tar_fd; tbInfo.verboseFlag = verboseFlag; /* Store the stat info for the tarball's file, so * can avoid including the tarball into itself.... */ if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0) bb_perror_msg_and_die("cannot stat tar file"); #if ENABLE_FEATURE_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2 if (gzip) { #if ENABLE_FEATURE_TAR_GZIP && ENABLE_FEATURE_TAR_BZIP2 const char *zip_exec = (gzip == 1) ? "gzip" : "bzip2"; #elif ENABLE_FEATURE_TAR_GZIP const char *zip_exec = "gzip"; #else /* only ENABLE_FEATURE_TAR_BZIP2 */ const char *zip_exec = "bzip2"; #endif // On Linux, vfork never unpauses parent early, although standard // allows for that. Do we want to waste bytes checking for it? #define WAIT_FOR_CHILD 0 volatile int vfork_exec_errno = 0; #if WAIT_FOR_CHILD struct fd_pair gzipStatusPipe; #endif struct fd_pair gzipDataPipe; xpiped_pair(gzipDataPipe); #if WAIT_FOR_CHILD xpiped_pair(gzipStatusPipe); #endif signal(SIGPIPE, SIG_IGN); /* we only want EPIPE on errors */ #if defined(__GNUC__) && __GNUC__ /* Avoid vfork clobbering */ (void) &include; (void) &errorFlag; (void) &zip_exec; #endif gzipPid = vfork(); if (gzipPid < 0) bb_perror_msg_and_die("vfork gzip"); if (gzipPid == 0) { /* child */ /* NB: close _first_, then move fds! */ close(gzipDataPipe.wr); #if WAIT_FOR_CHILD close(gzipStatusPipe.rd); /* gzipStatusPipe.wr will close only on exec - * parent waits for this close to happen */ fcntl(gzipStatusPipe.wr, F_SETFD, FD_CLOEXEC); #endif xmove_fd(gzipDataPipe.rd, 0); xmove_fd(tbInfo.tarFd, 1); /* exec gzip/bzip2 program/applet */ BB_EXECLP(zip_exec, zip_exec, "-f", NULL); vfork_exec_errno = errno; _exit(1); } /* parent */ xmove_fd(gzipDataPipe.wr, tbInfo.tarFd); close(gzipDataPipe.rd); #if WAIT_FOR_CHILD close(gzipStatusPipe.wr); while (1) { char buf; int n; /* Wait until child execs (or fails to) */ n = full_read(gzipStatusPipe.rd, &buf, 1); if (n < 0 /* && errno == EAGAIN */) continue; /* try it again */ } close(gzipStatusPipe.rd); #endif if (vfork_exec_errno) { errno = vfork_exec_errno; bb_perror_msg_and_die("cannot exec %s", zip_exec); } } #endif tbInfo.excludeList = exclude; /* Read the directory/files and iterate over them one at a time */ while (include) { if (!recursive_action(include->data, ACTION_RECURSE | (dereferenceFlag ? ACTION_FOLLOWLINKS : 0), writeFileToTarball, writeFileToTarball, &tbInfo, 0)) { errorFlag = TRUE; } include = include->link; } /* Write two empty blocks to the end of the archive */ memset(block_buf, 0, 2*TAR_BLOCK_SIZE); xwrite(tbInfo.tarFd, block_buf, 2*TAR_BLOCK_SIZE); /* To be pedantically correct, we would check if the tarball * is smaller than 20 tar blocks, and pad it if it was smaller, * but that isn't necessary for GNU tar interoperability, and * so is considered a waste of space */ /* Close so the child process (if any) will exit */ close(tbInfo.tarFd); /* Hang up the tools, close up shop, head home */ if (ENABLE_FEATURE_CLEAN_UP) freeHardLinkInfo(&tbInfo.hlInfoHead); if (errorFlag) bb_error_msg("error exit delayed from previous errors"); if (gzipPid) { #if ENABLE_FEATURE_TAR_GZIP || ENABLE_FEATURE_TAR_BZIP2 int status; if (safe_waitpid(gzipPid, &status, 0) == -1) bb_perror_msg("waitpid"); else if (!WIFEXITED(status) || WEXITSTATUS(status)) /* gzip was killed or has exited with nonzero! */ errorFlag = TRUE; #endif } return errorFlag; }
int openvt_main(int argc UNUSED_PARAM, char **argv) { char vtname[sizeof(VC_FORMAT) + sizeof(int)*3]; struct vt_stat vtstat; char *str_c; int vtno; int flags; enum { OPT_c = (1 << 0), OPT_w = (1 << 1), OPT_s = (1 << 2), OPT_l = (1 << 3), OPT_f = (1 << 4), OPT_v = (1 << 5), }; /* "+" - stop on first non-option */ flags = getopt32(argv, "+c:wslfv", &str_c); argv += optind; if (flags & OPT_c) { /* Check for illegal vt number: < 1 or > 63 */ vtno = xatou_range(str_c, 1, 63); } else { vtno = find_free_vtno(); } /* Grab new VT */ sprintf(vtname, VC_FORMAT, vtno); /* (Try to) clean up stray open fds above fd 2 */ bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS | DAEMON_ONLY_SANITIZE, NULL); close(STDIN_FILENO); /*setsid(); - BAD IDEA: after we exit, child is SIGHUPed... */ xopen(vtname, O_RDWR); xioctl(STDIN_FILENO, VT_GETSTATE, &vtstat); if (flags & OPT_s) { console_make_active(STDIN_FILENO, vtno); } if (!argv[0]) { argv--; argv[0] = getenv("SHELL"); if (!argv[0]) argv[0] = (char *) DEFAULT_SHELL; /*argv[1] = NULL; - already is */ } xdup2(STDIN_FILENO, STDOUT_FILENO); xdup2(STDIN_FILENO, STDERR_FILENO); #ifdef BLOAT { /* Handle -l (login shell) option */ const char *prog = argv[0]; if (flags & OPT_l) argv[0] = xasprintf("-%s", argv[0]); } #endif vfork_child(argv); if (flags & OPT_w) { /* We have only one child, wait for it */ safe_waitpid(-1, NULL, 0); /* loops on EINTR */ if (flags & OPT_s) { console_make_active(STDIN_FILENO, vtstat.v_active); // Compat: even with -c N (try to) disallocate: // # /usr/app/kbd-1.12/bin/openvt -f -c 9 -ws sleep 5 // openvt: could not deallocate console 9 xioctl(STDIN_FILENO, VT_DISALLOCATE, (void*)(ptrdiff_t)vtno); } } return EXIT_SUCCESS; }
int consume_event_command_output(struct run_event_state *state, const char *dump_dir_name) { int r = 0; char buf[256]; errno = 0; struct strbuf *cmd_output = state->command_output; while ((r = safe_read(state->command_out_fd, buf, sizeof(buf) - 1)) > 0) { char *newline; char *raw; buf[r] = '\0'; raw = buf; while ((newline = strchr(raw, '\n')) != NULL) { *newline = '\0'; strbuf_append_str(cmd_output, raw); char *msg = cmd_output->buf; char *response = NULL; /* just cut off prefix, no waiting */ if (prefixcmp(msg, REPORT_PREFIX_ALERT) == 0) { state->alert_callback(msg + sizeof(REPORT_PREFIX_ALERT) - 1 , state->interaction_param); } /* wait for y/N/f response on the same line */ else if (prefixcmp(msg, REPORT_PREFIX_ASK_YES_NO_YESFOREVER) == 0) { /* example: * ASK_YES_NO_YESFOREVER ask_before_delete Do you want to delete selected files? */ char *key = msg + sizeof(REPORT_PREFIX_ASK_YES_NO_YESFOREVER) - 1; char *key_end = strchr(key, ' '); bool ans = false; if (!key_end) { /* example: * ASK_YES_NO_YESFOREVER Continue? * * Print a wraning only and do not scary users with error messages. */ log_warning("invalid input format (missing option name), using simple ask yes/no"); /* can't simply use 'goto ask_yes_no' because of different lenght of prefixes */ ans = state->ask_yes_no_callback(key, state->interaction_param); } else { key_end[0] = '\0'; /* split 'key msg' to 'key' and 'msg' */ ans = state->ask_yes_no_yesforever_callback(key, key + strlen(key) + 1, state->interaction_param); key_end[0] = ' '; /* restore original message, not sure if it is necessary */ } response = xstrdup(ans ? "y" : "N"); } /* wait for y/N response on the same line */ else if (prefixcmp(msg, REPORT_PREFIX_ASK_YES_NO) == 0) { const bool ans = state->ask_yes_no_callback(msg + sizeof(REPORT_PREFIX_ASK_YES_NO) - 1, state->interaction_param); response = xstrdup(ans ? "y" : "N"); } /* wait for the string on the same line */ else if (prefixcmp(msg, REPORT_PREFIX_ASK) == 0) { response = state->ask_callback(msg + sizeof(REPORT_PREFIX_ASK) - 1, state->interaction_param); } /* set echo off and wait for password on the same line */ else if (prefixcmp(msg, REPORT_PREFIX_ASK_PASSWORD) == 0) { response = state->ask_password_callback(msg + sizeof(REPORT_PREFIX_ASK_PASSWORD) - 1, state->interaction_param); } /* no special prefix -> forward to log if applicable * note that callback may take ownership of buf by returning NULL */ else if (state->logging_callback) { char *logged = state->logging_callback(xstrdup(msg), state->logging_param); free(logged); } if (response) { size_t len = strlen(response); response[len++] = '\n'; if (full_write(state->command_in_fd, response, len) != len) { if (state->error_callback) state->error_callback("<WRITE ERROR>", state->error_param); else perror_msg_and_die("Can't write %zu bytes to child's stdin", len); } free(response); } strbuf_clear(cmd_output); /* jump to next line */ raw = newline + 1; } /* beginning of next line. the line continues by next read() */ strbuf_append_str(cmd_output, raw); } /* Hope that child's stdout fd was set to O_NONBLOCK */ if (r == -1 && errno == EAGAIN) return -1; strbuf_clear(cmd_output); /* Wait for child to actually exit, collect status */ safe_waitpid(state->command_pid, &(state->process_status), 0); int retval = WEXITSTATUS(state->process_status); if (WIFSIGNALED(state->process_status)) retval = WTERMSIG(state->process_status) + 128; if (retval == 0 && state->post_run_callback) retval = state->post_run_callback(dump_dir_name, state->post_run_param); return retval; }
static int create_and_upload_archive( const char *dump_dir_name, map_string_t *settings) { int result = 1; /* error */ pid_t child; TAR* tar = NULL; const char* errmsg = NULL; char* tempfile = NULL; struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) xfunc_die(); /* error msg is already logged by dd_opendir */ /* Gzipping e.g. 0.5gig coredump takes a while. Let client know what we are doing */ log(_("Compressing data")); //TODO: //Encrypt = yes //ArchiveType = .tar.bz2 //ExcludeFiles = foo,bar*,b*z const char* opt = getenv("Upload_URL"); if (!opt) opt = get_map_string_item_or_empty(settings, "URL"); char *url = opt[0] != '\0' ? xstrdup(opt) : ask_url(_("Please enter a URL (scp, ftp, etc.) where the problem data is to be exported:")); /* Create a child gzip which will compress the data */ /* SELinux guys are not happy with /tmp, using /var/run/abrt */ /* Reverted back to /tmp for ABRT2 */ /* Changed again to /var/tmp because of Fedora feature tmp-on-tmpfs */ tempfile = concat_path_basename(LARGE_DATA_TMP_DIR, dump_dir_name); tempfile = append_to_malloced_string(tempfile, ".tar.gz"); int pipe_from_parent_to_child[2]; xpipe(pipe_from_parent_to_child); child = vfork(); if (child == 0) { /* child */ close(pipe_from_parent_to_child[1]); xmove_fd(pipe_from_parent_to_child[0], 0); xmove_fd(xopen3(tempfile, O_WRONLY | O_CREAT | O_EXCL, 0600), 1); execlp("gzip", "gzip", NULL); perror_msg_and_die("Can't execute '%s'", "gzip"); } close(pipe_from_parent_to_child[0]); /* If child died (say, in xopen), then parent might get SIGPIPE. * We want to properly unlock dd, therefore we must not die on SIGPIPE: */ signal(SIGPIPE, SIG_IGN); /* Create tar writer object */ if (tar_fdopen(&tar, pipe_from_parent_to_child[1], tempfile, /*fileops:(standard)*/ NULL, O_WRONLY | O_CREAT, 0644, TAR_GNU) != 0) { errmsg = "Can't create temporary file in "LARGE_DATA_TMP_DIR; goto ret; } /* Write data to the tarball */ { string_vector_ptr_t exclude_from_report = get_global_always_excluded_elements(); dd_init_next_file(dd); char *short_name, *full_name; while (dd_get_next_file(dd, &short_name, &full_name)) { if (exclude_from_report && is_in_string_list(short_name, (const_string_vector_const_ptr_t)exclude_from_report)) goto next; // dd_get_next_file guarantees that it's a REG: //struct stat stbuf; //if (stat(full_name, &stbuf) != 0) // || !S_ISREG(stbuf.st_mode) //) { // goto next; //} if (tar_append_file(tar, full_name, short_name) != 0) { errmsg = "Can't create temporary file in "LARGE_DATA_TMP_DIR; free(short_name); free(full_name); goto ret; } next: free(short_name); free(full_name); } } dd_close(dd); dd = NULL; /* Close tar writer... */ if (tar_append_eof(tar) != 0 || tar_close(tar) != 0) { errmsg = "Can't create temporary file in "LARGE_DATA_TMP_DIR; goto ret; } tar = NULL; /* ...and check that gzip child finished successfully */ int status; safe_waitpid(child, &status, 0); child = -1; if (status != 0) { /* We assume the error was out-of-disk-space or out-of-quota */ errmsg = "Can't create temporary file in "LARGE_DATA_TMP_DIR; goto ret; } /* Upload the tarball */ /* Upload from /tmp to /tmp + deletion -> BAD, exclude this possibility */ if (url && url[0] && strcmp(url, "file://"LARGE_DATA_TMP_DIR"/") != 0) { post_state_t *state = new_post_state(POST_WANT_ERROR_MSG); state->username = getenv("Upload_Username"); char *password_inp = NULL; if (state->username != NULL && state->username[0] != '\0') { /* Load Password only if Username is configured, it doesn't make */ /* much sense to load Password without Username. */ state->password = getenv("Upload_Password"); if (state->password == NULL) { /* Be permissive and nice, ask only once and don't check */ /* the result. User can dismiss this prompt but the upload */ /* may work somehow??? */ char *msg = xasprintf(_("Please enter password for uploading:"), state->username); state->password = password_inp = ask_password(msg); free(msg); } } char *remote_name = upload_file_ext(state, url, tempfile, UPLOAD_FILE_HANDLE_ACCESS_DENIALS); result = (remote_name == NULL); /* error if NULL */ free(remote_name); free(password_inp); free_post_state(state); /* cleanup code will delete tempfile */ } else { result = 0; /* success */ log(_("Archive is created: '%s'"), tempfile); free(tempfile); tempfile = NULL; } ret: free(url); dd_close(dd); if (tar) tar_close(tar); /* close(pipe_from_parent_to_child[1]); - tar_close() does it itself */ if (child > 0) safe_waitpid(child, NULL, 0); if (tempfile) { unlink(tempfile); free(tempfile); } if (errmsg) error_msg_and_die("%s", errmsg); return result; }
void FAST_FUNC data_extract_to_command(archive_handle_t *archive_handle) { file_header_t *file_header = archive_handle->file_header; #if 0 /* do we need this? ENABLE_FEATURE_TAR_SELINUX */ char *sctx = archive_handle->tar__sctx[PAX_NEXT_FILE]; if (!sctx) sctx = archive_handle->tar__sctx[PAX_GLOBAL]; if (sctx) { /* setfscreatecon is 4 syscalls, avoid if possible */ setfscreatecon(sctx); free(archive_handle->tar__sctx[PAX_NEXT_FILE]); archive_handle->tar__sctx[PAX_NEXT_FILE] = NULL; } #endif if ((file_header->mode & S_IFMT) == S_IFREG) { pid_t pid; int p[2], status; char *tar_env[TAR_MAX]; memset(tar_env, 0, sizeof(tar_env)); xpipe(p); pid = BB_MMU ? xfork() : xvfork(); if (pid == 0) { /* Child */ /* str2env(tar_env, TAR_FILETYPE, "f"); - parent should do it once */ oct2env(tar_env, TAR_MODE, file_header->mode); str2env(tar_env, TAR_FILENAME, file_header->name); str2env(tar_env, TAR_REALNAME, file_header->name); #if ENABLE_FEATURE_TAR_UNAME_GNAME str2env(tar_env, TAR_UNAME, file_header->tar__uname); str2env(tar_env, TAR_GNAME, file_header->tar__gname); #endif dec2env(tar_env, TAR_SIZE, file_header->size); dec2env(tar_env, TAR_UID, file_header->uid); dec2env(tar_env, TAR_GID, file_header->gid); close(p[1]); xdup2(p[0], STDIN_FILENO); signal(SIGPIPE, SIG_DFL); execl(archive_handle->tar__to_command_shell, archive_handle->tar__to_command_shell, "-c", archive_handle->tar__to_command, NULL); bb_perror_msg_and_die("can't execute '%s'", archive_handle->tar__to_command_shell); } close(p[0]); /* Our caller is expected to do signal(SIGPIPE, SIG_IGN) * so that we don't die if child don't read all the input: */ bb_copyfd_exact_size(archive_handle->src_fd, p[1], -file_header->size); close(p[1]); if (safe_waitpid(pid, &status, 0) == -1) bb_perror_msg_and_die("waitpid"); if (WIFEXITED(status) && WEXITSTATUS(status)) bb_error_msg_and_die("'%s' returned status %d", archive_handle->tar__to_command, WEXITSTATUS(status)); if (WIFSIGNALED(status)) bb_error_msg_and_die("'%s' terminated on signal %d", archive_handle->tar__to_command, WTERMSIG(status)); if (!BB_MMU) { int i; for (i = 0; i < TAR_MAX; i++) { if (tar_env[i]) bb_unsetenv_and_free(tar_env[i]); } } } #if 0 /* ENABLE_FEATURE_TAR_SELINUX */ if (sctx) /* reset the context after creating an entry */ setfscreatecon(NULL); #endif }
char *run_unstrip_n(const char *dump_dir_name, unsigned timeout_sec) { int flags = EXECFLG_INPUT_NUL | EXECFLG_OUTPUT | EXECFLG_SETSID | EXECFLG_QUIET; VERB1 flags &= ~EXECFLG_QUIET; int pipeout[2]; char* args[4]; args[0] = (char*)"eu-unstrip"; args[1] = xasprintf("--core=%s/"FILENAME_COREDUMP, dump_dir_name); args[2] = (char*)"-n"; args[3] = NULL; pid_t child = fork_execv_on_steroids(flags, args, pipeout, /*env_vec:*/ NULL, /*dir:*/ NULL, /*uid(unused):*/ 0); free(args[1]); /* Bugs in unstrip or corrupted coredumps can cause it to enter infinite loop. * Therefore we have a (largish) timeout, after which we kill the child. */ ndelay_on(pipeout[0]); int t = time(NULL); /* int is enough, no need to use time_t */ int endtime = t + timeout_sec; struct strbuf *buf_out = strbuf_new(); while (1) { int timeout = endtime - t; if (timeout < 0) { kill(child, SIGKILL); strbuf_free(buf_out); buf_out = NULL; break; } /* We don't check poll result - checking read result is enough */ struct pollfd pfd; pfd.fd = pipeout[0]; pfd.events = POLLIN; poll(&pfd, 1, timeout * 1000); char buff[1024]; int r = read(pipeout[0], buff, sizeof(buff) - 1); if (r <= 0) { /* I did see EAGAIN happening here */ if (r < 0 && errno == EAGAIN) goto next; break; } buff[r] = '\0'; strbuf_append_str(buf_out, buff); next: t = time(NULL); } close(pipeout[0]); /* Prevent having zombie child process */ int status; safe_waitpid(child, &status, 0); if (status != 0 || buf_out == NULL) { /* unstrip didnt exit with exit code 0, or we timed out */ strbuf_free(buf_out); return NULL; } return strbuf_free_nobuf(buf_out); }
/* gcc 4.2.1 inlines it, making code bigger */ static NOINLINE int writeTarFile(int tar_fd, int verboseFlag, int dereferenceFlag, const llist_t *include, const llist_t *exclude, int gzip) { int errorFlag = FALSE; struct TarBallInfo tbInfo; tbInfo.hlInfoHead = NULL; tbInfo.tarFd = tar_fd; tbInfo.verboseFlag = verboseFlag; /* Store the stat info for the tarball's file, so * can avoid including the tarball into itself.... */ if (fstat(tbInfo.tarFd, &tbInfo.statBuf) < 0) bb_perror_msg_and_die("cannot stat tar file"); #if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 if (gzip) vfork_compressor(tbInfo.tarFd, gzip); #endif tbInfo.excludeList = exclude; /* Read the directory/files and iterate over them one at a time */ while (include) { if (!recursive_action(include->data, ACTION_RECURSE | (dereferenceFlag ? ACTION_FOLLOWLINKS : 0), writeFileToTarball, writeFileToTarball, &tbInfo, 0)) { errorFlag = TRUE; } include = include->link; } /* Write two empty blocks to the end of the archive */ memset(block_buf, 0, 2*TAR_BLOCK_SIZE); xwrite(tbInfo.tarFd, block_buf, 2*TAR_BLOCK_SIZE); /* To be pedantically correct, we would check if the tarball * is smaller than 20 tar blocks, and pad it if it was smaller, * but that isn't necessary for GNU tar interoperability, and * so is considered a waste of space */ /* Close so the child process (if any) will exit */ close(tbInfo.tarFd); /* Hang up the tools, close up shop, head home */ if (ENABLE_FEATURE_CLEAN_UP) freeHardLinkInfo(&tbInfo.hlInfoHead); if (errorFlag) bb_error_msg("error exit delayed from previous errors"); #if ENABLE_FEATURE_SEAMLESS_GZ || ENABLE_FEATURE_SEAMLESS_BZ2 if (gzip) { int status; if (safe_waitpid(-1, &status, 0) == -1) bb_perror_msg("waitpid"); else if (!WIFEXITED(status) || WEXITSTATUS(status)) /* gzip was killed or has exited with nonzero! */ errorFlag = TRUE; } #endif return errorFlag; }
int report_problem_in_dir(const char *dirname, int flags) { /* Prepare it before fork, to avoid thread-unsafe setenv there */ char *prgname = (char*) g_get_prgname(); if (prgname) prgname = xasprintf("LIBREPORT_PRGNAME=%s", prgname); fflush(NULL); pid_t pid = fork(); if (pid < 0) /* error */ { perror_msg("fork"); return -1; } if (pid == 0) /* child */ { const char *event_name; const char *path, *path1, *path2; char *args[7], **pp; /* Graphical tool */ event_name = "report-gui"; path1 = BIN_DIR"/report-gtk"; path2 = "report-gtk"; pp = args; *pp++ = (char *)"report-gtk"; if (flags & LIBREPORT_DEL_DIR) *pp++ = (char *)"--delete"; *pp++ = (char *)"--"; *pp++ = (char *)dirname; *pp = NULL; if (prgname) putenv(prgname); if (flags & LIBREPORT_RUN_NEWT) { /* we want to run newt first */ event_name = "report-cli"; path1 = BIN_DIR"/report-newt"; path2 = "report-newt"; pp = args; *pp++ = (char *)"report-newt"; if (flags & LIBREPORT_DEL_DIR) *pp++ = (char *)"--delete"; *pp++ = (char *)"--"; *pp++ = (char *)dirname; *pp = NULL; } else if (!getenv("DISPLAY") || (flags & LIBREPORT_RUN_CLI)) { /* GUI won't work, use command line tool instead */ event_name = "report-cli"; path1 = BIN_DIR"/report-cli"; path2 = "report-cli"; pp = args; *pp++ = (char *)"report-cli"; if (flags & LIBREPORT_DEL_DIR) *pp++ = (char *)"--delete"; *pp++ = (char *)"-e"; *pp++ = (char *)"report-cli"; *pp++ = (char *)"--"; *pp++ = (char *)dirname; *pp = NULL; } /* Some callers set SIGCHLD to SIG_IGN. * However, reporting spawns child processes. * Suppressing child death notification terribly confuses some of them. * Just in case, undo it. * Note that we do it in the child, so the parent is never affected. */ signal(SIGCHLD, SIG_DFL); if (!(flags & (LIBREPORT_WAIT | LIBREPORT_GETPID))) { /* Caller doesn't want to wait for completion (!LIBREPORT_WAIT), * and doesn't want to have pid returned (!LIBREPORT_GETPID). * Create a grandchild, and then exit. * This reparents grandchild to init, and makes waitpid * in parent detect our exit and return almost immediately. */ pid_t pid = fork(); if (pid < 0) /* error */ perror_msg_and_die("fork"); if (pid != 0) /* not grandchild */ { /* And now we exit: */ _exit(0); } /* There's an alternative approach to achieve this, * instead of using --delete. * We can create yet another intermediate process which * waits for reporting child to finish, and then * removes temporary dump dir. * Pros: deletion becomes more robust. * Even if child crashes, dir will be deleted. * Cons: having another process would use some resources, * and we'll need to at least close all open file descriptors, * and reopen stdio to /dev/null. We also might keep * a lot of libraries loaded: * who knows what parent process links against. * (can be worked around by exec'ing a "wait & delete" helper) */ } struct run_event_state *run_state = new_run_event_state(); int r = run_event_on_dir_name(run_state, dirname, event_name); int no_such_event = (r == 0 && run_state->children_count == 0); free_run_event_state(run_state); if (!no_such_event) { if (flags & LIBREPORT_DEL_DIR) { struct dump_dir *dd = dd_opendir(dirname, 0); if (dd) dd_delete(dd); } _exit(r); } /* No "report-cli/gui" event found, do it old-style */ path = path1; VERB1 log("Executing: %s", path); execv(path, args); /* Did not find the desired executable in the installation directory. * Trying to find it in PATH. */ path = path2; execvp(path, args); perror_msg_and_die("Can't execute %s", path); } /* parent */ free(prgname); if (!(flags & LIBREPORT_WAIT) && (flags & LIBREPORT_GETPID)) return pid; /* we are here either if LIBREPORT_WAIT (caller wants exitcode) * or !LIBREPORT_GETPID (caller doesn't want to have a child). * In both cases, we need to wait for child: */ int status; pid = safe_waitpid(pid, &status, 0); if (pid <= 0) { perror_msg("waitpid"); return -1; } if (WIFEXITED(status)) { VERB2 log("reporting finished with exitcode %d", WEXITSTATUS(status)); return WEXITSTATUS(status); } /* child died from a signal: WIFSIGNALED(status) should be true */ VERB2 log("reporting killed by signal %d", WTERMSIG(status)); return WTERMSIG(status) + 128; }