int report_problem_in_memory(problem_data_t *pd, int flags) { int result = 0; struct dump_dir *dd = create_dump_dir_from_problem_data(pd, "/tmp"/* /var/tmp ?? */); if (!dd) return -1; char *dir_name = xstrdup(dd->dd_dirname); dd_close(dd); VERB2 log("Temp problem dir: '%s'", dir_name); if (!(flags & LIBREPORT_WAIT)) flags |= LIBREPORT_DEL_DIR; result = report_problem_in_dir(dir_name, flags); /* If we waited for reporter to finish, we should clean up the tmp dir * (if we didn't, cleaning up will be done by reporting child process later). * We can also reload the problem data if requested. */ if (flags & LIBREPORT_WAIT) { if (flags & LIBREPORT_RELOAD_DATA) g_hash_table_remove_all(pd); dd = dd_opendir(dir_name, 0); if (dd) { if (flags & LIBREPORT_RELOAD_DATA) problem_data_load_from_dump_dir(pd, dd, NULL); dd_delete(dd); } } free(dir_name); return result; }
/* Remove dump dir */ static int delete_path(const char *dump_dir_name) { /* If doesn't start with "g_settings_dump_location/"... */ if (!dir_is_in_dump_location(dump_dir_name)) { /* Then refuse to operate on it (someone is attacking us??) */ error_msg("Bad problem directory name '%s', should start with: '%s'", dump_dir_name, g_settings_dump_location); return 400; /* Bad Request */ } if (!dir_has_correct_permissions(dump_dir_name, DD_PERM_DAEMONS)) { error_msg("Problem directory '%s' has wrong owner or group", dump_dir_name); return 400; /* */ } struct dump_dir *dd = dd_opendir(dump_dir_name, DD_OPEN_FD_ONLY); if (dd == NULL) { perror_msg("Can't open problem directory '%s'", dump_dir_name); return 400; } if (!dd_accessible_by_uid(dd, client_uid)) { dd_close(dd); if (errno == ENOTDIR) { error_msg("Path '%s' isn't problem directory", dump_dir_name); return 404; /* Not Found */ } error_msg("Problem directory '%s' can't be accessed by user with uid %ld", dump_dir_name, (long)client_uid); return 403; /* Forbidden */ } dd = dd_fdopendir(dd, /*flags:*/ 0); if (dd) { if (dd_delete(dd) != 0) { error_msg("Failed to delete problem directory '%s'", dump_dir_name); dd_close(dd); return 400; } } return 0; /* success */ }
/* Remove dump dir */ int DeleteDebugDump(const char *dump_dir_name, long caller_uid) { /* If doesn't start with "DEBUG_DUMPS_DIR/"... */ if (strncmp(dump_dir_name, DEBUG_DUMPS_DIR"/", strlen(DEBUG_DUMPS_DIR"/")) != 0 /* or contains "/." anywhere (-> might contain ".." component) */ || strstr(dump_dir_name + strlen(DEBUG_DUMPS_DIR), "/.") ) { /* Then refuse to operate on it (someone is attacking us??) */ error_msg("Bad dump directory name '%s', not deleting", dump_dir_name); return MW_ERROR; } struct dump_dir *dd = dd_opendir(dump_dir_name, /*flags:*/ 0); if (!dd) return MW_NOENT_ERROR; if (caller_uid != 0) /* not called by root */ { char caller_uid_str[sizeof(long) * 3 + 2]; sprintf(caller_uid_str, "%ld", caller_uid); char *uid = dd_load_text_ext(dd, FILENAME_UID, DD_FAIL_QUIETLY_ENOENT | DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE); /* we assume that the dump_dir can be handled by everyone if uid == NULL * e.g: kerneloops */ if (uid != NULL) { bool uid_matches = (strcmp(uid, caller_uid_str) == 0); free(uid); if (!uid_matches) { dd_close(dd); error_msg("Dump directory '%s' can't be accessed by user with uid %ld", dump_dir_name, caller_uid); return 1; } } } dd_delete(dd); return 0; /* success */ }
int run_event_on_problem_data(struct run_event_state *state, problem_data_t *data, const char *event) { state->children_count = 0; struct dump_dir *dd = create_dump_dir_from_problem_data(data, NULL); if (!dd) return -1; char *dir_name = xstrdup(dd->dd_dirname); dd_close(dd); int r = run_event_on_dir_name(state, dir_name, event); g_hash_table_remove_all(data); dd = dd_opendir(dir_name, /*flags:*/ 0); free(dir_name); if (dd) { problem_data_load_from_dump_dir(data, dd, NULL); dd_delete(dd); } return r; }
void runUserTask(uint32_t numTicks){ printf("[User] Doing busy work for %u ticks.\n", numTicks); doBusyWorkForTicks(numTicks); printf("[User] Task complete.\n"); dd_delete(_task_get_id()); }
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; }
/* Queueing the process will also lead to cleaning up the dump location. */ static void queue_post_craete_process(struct abrt_server_proc *proc) { load_abrt_conf(); struct abrt_server_proc *running = s_dir_queue == NULL ? NULL : (struct abrt_server_proc *)s_dir_queue->data; if (g_settings_nMaxCrashReportsSize == 0) goto consider_processing; const char *full_path_ignored = running != NULL ? running->dirname : proc->dirname; const char *ignored = strrchr(full_path_ignored, '/'); if (NULL == ignored) /* Paranoia, this should not happen. */ ignored = full_path_ignored; else /* Move behind '/' */ ++ignored; char *worst_dir = NULL; const double max_size = 1024 * 1024 * g_settings_nMaxCrashReportsSize; while (get_dirsize_find_largest_dir(g_settings_dump_location, &worst_dir, ignored) >= max_size && worst_dir) { const char *kind = "old"; GList *proc_of_deleted_item = NULL; if (proc != NULL && strcmp(worst_dir, proc->dirname) == 0) { kind = "new"; stop_abrt_server(proc); proc = NULL; } else if ((proc_of_deleted_item = g_list_find_custom(s_dir_queue, worst_dir, (GCompareFunc)abrt_server_compare_dirname))) { kind = "unprocessed"; struct abrt_server_proc *removed_proc = (struct abrt_server_proc *)proc_of_deleted_item->data; s_dir_queue = g_list_delete_link(s_dir_queue, proc_of_deleted_item); stop_abrt_server(removed_proc); } log_warning("Size of '%s' >= %u MB (MaxCrashReportsSize), deleting %s directory '%s'", g_settings_dump_location, g_settings_nMaxCrashReportsSize, kind, worst_dir); char *deleted = concat_path_file(g_settings_dump_location, worst_dir); free(worst_dir); worst_dir = NULL; struct dump_dir *dd = dd_opendir(deleted, DD_FAIL_QUIETLY_ENOENT); if (dd != NULL) dd_delete(dd); free(deleted); } consider_processing: /* If the process survived cleaning up the dump location, append it to the * post-create queue. */ if (proc != NULL) s_dir_queue = g_list_append(s_dir_queue, proc); /* If there were no running post-crate process before we added the * currently handled process to the post-create queue, start processing of * the currently handled process. */ if (running == NULL) notify_next_post_create_process(NULL/*finished*/); }
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; }