| /* SPDX-License-Identifier: LGPL-2.1+ */ |
| |
| #include <sys/wait.h> |
| |
| #include "alloc-util.h" |
| #include "fd-util.h" |
| #include "operation.h" |
| #include "process-util.h" |
| |
| static int operation_done(sd_event_source *s, const siginfo_t *si, void *userdata) { |
| _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
| Operation *o = userdata; |
| int r; |
| |
| assert(o); |
| assert(si); |
| |
| log_debug("Operating " PID_FMT " is now complete with code=%s status=%i", |
| o->pid, |
| sigchld_code_to_string(si->si_code), si->si_status); |
| |
| o->pid = 0; |
| |
| if (si->si_code != CLD_EXITED) { |
| r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child died abnormally."); |
| goto fail; |
| } |
| |
| if (si->si_status == EXIT_SUCCESS) |
| r = 0; |
| else if (read(o->errno_fd, &r, sizeof(r)) != sizeof(r)) { /* Try to acquire error code for failed operation */ |
| r = sd_bus_error_setf(&error, SD_BUS_ERROR_FAILED, "Child failed."); |
| goto fail; |
| } |
| |
| if (o->done) { |
| /* A completion routine is set for this operation, call it. */ |
| r = o->done(o, r, &error); |
| if (r < 0) { |
| if (!sd_bus_error_is_set(&error)) |
| sd_bus_error_set_errno(&error, r); |
| |
| goto fail; |
| } |
| |
| } else { |
| /* The default operation when done is to simply return an error on failure or an empty success |
| * message on success. */ |
| if (r < 0) { |
| sd_bus_error_set_errno(&error, r); |
| goto fail; |
| } |
| |
| r = sd_bus_reply_method_return(o->message, NULL); |
| if (r < 0) |
| log_error_errno(r, "Failed to reply to message: %m"); |
| } |
| |
| operation_free(o); |
| return 0; |
| |
| fail: |
| r = sd_bus_reply_method_error(o->message, &error); |
| if (r < 0) |
| log_error_errno(r, "Failed to reply to message: %m"); |
| |
| operation_free(o); |
| return 0; |
| } |
| |
| int operation_new(Manager *manager, Machine *machine, pid_t child, sd_bus_message *message, int errno_fd, Operation **ret) { |
| Operation *o; |
| int r; |
| |
| assert(manager); |
| assert(child > 1); |
| assert(message); |
| assert(errno_fd >= 0); |
| |
| o = new0(Operation, 1); |
| if (!o) |
| return -ENOMEM; |
| |
| o->extra_fd = -1; |
| |
| r = sd_event_add_child(manager->event, &o->event_source, child, WEXITED, operation_done, o); |
| if (r < 0) { |
| free(o); |
| return r; |
| } |
| |
| o->pid = child; |
| o->message = sd_bus_message_ref(message); |
| o->errno_fd = errno_fd; |
| |
| LIST_PREPEND(operations, manager->operations, o); |
| manager->n_operations++; |
| o->manager = manager; |
| |
| if (machine) { |
| LIST_PREPEND(operations_by_machine, machine->operations, o); |
| o->machine = machine; |
| } |
| |
| log_debug("Started new operation " PID_FMT ".", child); |
| |
| /* At this point we took ownership of both the child and the errno file descriptor! */ |
| |
| if (ret) |
| *ret = o; |
| |
| return 0; |
| } |
| |
| Operation *operation_free(Operation *o) { |
| if (!o) |
| return NULL; |
| |
| sd_event_source_unref(o->event_source); |
| |
| safe_close(o->errno_fd); |
| safe_close(o->extra_fd); |
| |
| if (o->pid > 1) |
| (void) sigkill_wait(o->pid); |
| |
| sd_bus_message_unref(o->message); |
| |
| if (o->manager) { |
| LIST_REMOVE(operations, o->manager->operations, o); |
| o->manager->n_operations--; |
| } |
| |
| if (o->machine) |
| LIST_REMOVE(operations_by_machine, o->machine->operations, o); |
| |
| return mfree(o); |
| } |