static void LIBUSB_CALL usb_remove_pollfd(int fd, void *opaque) { (void)opaque; log_event_debug("Got told to remove libusb pollfd (handle: %d)", fd); event_remove_source(fd, EVENT_SOURCE_TYPE_USB); }
void client_destroy(Client *client) { bool destroy_pending_requests = false; PendingRequest *pending_request; if (client->pending_request_count > 0) { log_warn("Destroying client ("CLIENT_SIGNATURE_FORMAT") while %d request(s) are still pending", client_expand_signature(client), client->pending_request_count); if (network_create_zombie(client) < 0) { log_error("Could not create zombie for %d pending request(s) of ("CLIENT_SIGNATURE_FORMAT")", client->pending_request_count, client_expand_signature(client)); destroy_pending_requests = true; } } writer_destroy(&client->response_writer); event_remove_source(client->io->handle, EVENT_SOURCE_TYPE_GENERIC); io_destroy(client->io); free(client->io); if (destroy_pending_requests) { while (client->pending_request_sentinel.next != &client->pending_request_sentinel) { pending_request = containerof(client->pending_request_sentinel.next, PendingRequest, client_node); pending_request_remove_and_free(pending_request); } } if (client->destroy_done != NULL) { client->destroy_done(); } }
void network_exit(void) { log_debug("Shutting down network subsystem"); array_destroy(&_clients, (ItemDestroyFunction)client_destroy); // might call network_create_zombie array_destroy(&_zombies, (ItemDestroyFunction)zombie_destroy); if (_plain_server_socket_open) { event_remove_source(_plain_server_socket.base.handle, EVENT_SOURCE_TYPE_GENERIC); socket_destroy(&_plain_server_socket); } if (_websocket_server_socket_open) { event_remove_source(_websocket_server_socket.base.handle, EVENT_SOURCE_TYPE_GENERIC); socket_destroy(&_websocket_server_socket); } }
void red_stack_exit(void) { int i; int slave; // Remove reset interrupt as event source if (_red_stack_reset_fd > 0) { event_remove_source(_red_stack_reset_fd, EVENT_SOURCE_TYPE_GENERIC); } // Remove event as possible poll source event_remove_source(_red_stack_notification_event, EVENT_SOURCE_TYPE_GENERIC); // Make sure that Thread shuts down properly if (_red_stack_spi_thread_running) { _red_stack_spi_thread_running = false; // Write in eventfd to make sure that we are not blocking the Thread eventfd_t ev = 1; eventfd_write(_red_stack_notification_event, ev); thread_join(&_red_stack_spi_thread); thread_destroy(&_red_stack_spi_thread); } // Thread is not running anymore, we make sure that all slaves are deselected for (slave = 0; slave < RED_STACK_SPI_MAX_SLAVES; slave++) { red_stack_spi_deselect(&_red_stack.slaves[slave]); } // We can also free the queue and stack now, nobody will use them anymore for (i = 0; i < RED_STACK_SPI_MAX_SLAVES; i++) { queue_destroy(&_red_stack.slaves[i].packet_to_spi_queue, NULL); } hardware_remove_stack(&_red_stack.base); stack_destroy(&_red_stack.base); for (i = 0; i < RED_STACK_SPI_MAX_SLAVES; i++) { mutex_destroy(&_red_stack.slaves[i].packet_queue_mutex); } semaphore_destroy(&_red_stack_dispatch_packet_from_spi_semaphore); // Close file descriptors close(_red_stack_notification_event); close(_red_stack_spi_fd); }
void client_destroy(Client *client) { event_remove_source(client->socket, EVENT_SOURCE_TYPE_GENERIC); socket_destroy(client->socket); if (client->peer != _unknown_peer_name) { free(client->peer); } array_destroy(&client->pending_requests, NULL); }
void udev_exit(void) { log_debug("Shutting down udev subsystem"); event_remove_source(_udev_monitor_fd, EVENT_SOURCE_TYPE_GENERIC); udev_monitor_unref(_udev_monitor); udev_unref(_udev_context); #ifdef BRICKD_WITH_LIBUDEV_DLOPEN udev_dlclose(); #endif }
void mesh_exit(void) { log_info("Shutting down mesh subsystem"); // Cleanup mesh listen socket. if (is_mesh_listen_socket_open) { event_remove_source(mesh_listen_socket.base.handle, EVENT_SOURCE_TYPE_GENERIC); socket_destroy(&mesh_listen_socket); } // Mesh stack related cleanup is done in mesh_stack_destroy(). array_destroy(&mesh_stacks, (ItemDestroyFunction)mesh_stack_destroy); }
// Exit function called from central brickd code void rs485_extension_exit(void) { if (!_initialized) { return; } // Remove event as possible poll source event_remove_source(_send_verify_event, EVENT_SOURCE_TYPE_GENERIC); event_remove_source(_master_poll_slave_event, EVENT_SOURCE_TYPE_GENERIC); event_remove_source(_rs485_serial_fd, EVENT_SOURCE_TYPE_GENERIC); event_remove_source(_master_retry_event, EVENT_SOURCE_TYPE_GENERIC); event_remove_source(_partial_receive_timeout_event, EVENT_SOURCE_TYPE_GENERIC); // We can also free the queue and stack now, nobody will use them anymore queue_destroy(&_rs485_extension.packet_to_modbus_queue, NULL); hardware_remove_stack(&_rs485_extension.base); stack_destroy(&_rs485_extension.base); // Close file descriptors close(_send_verify_event); close(_master_poll_slave_event); close(_partial_receive_timeout_event); close(_master_retry_event); close(_rs485_serial_fd); }
void iokit_exit(void) { log_debug("Shutting down IOKit subsystem"); if (_running) { _running = false; CFRunLoopStop(_run_loop); CFRelease(_run_loop); thread_join(&_poll_thread); } thread_destroy(&_poll_thread); event_remove_source(_notification_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); pipe_destroy(&_notification_pipe); }
void event_exit_platform(void) { semaphore_destroy(&_usb_poll_resume); semaphore_destroy(&_usb_poll_suspend); array_destroy(&_usb_poll_pollfds, NULL); pipe_destroy(&_usb_poll_ready_pipe); usbi_close(_usb_poll_suspend_pipe[0]); usbi_close(_usb_poll_suspend_pipe[1]); event_remove_source(_stop_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); pipe_destroy(&_stop_pipe); free(_socket_error_set); free(_socket_write_set); free(_socket_read_set); }
static void process_destroy(Object *object) { Process *process = (Process *)object; int rc; bool stuck = false; // remove the state change pipe from the event loop to avoid sending // callbacks in case the child process is still alive and has to be killed event_remove_source(process->state_change_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); // FIXME: this code here has the same race condition as process_kill if (process_is_alive(process)) { log_warn("Destroying process object (id: %u, executable: %s) while child process (pid: %u) is still alive", process->base.id, process->executable->buffer, process->pid); rc = kill(process->pid, SIGKILL); if (rc < 0) { if (errno != ESRCH) { stuck = true; } log_error("Could not send SIGKILL signal to child process (executable: %s, pid: %u): %s (%d)", process->executable->buffer, process->pid, get_errno_name(errno), errno); } } if (!stuck) { thread_join(&process->wait_thread); } thread_destroy(&process->wait_thread); pipe_destroy(&process->state_change_pipe); file_release(process->stderr); file_release(process->stdout); file_release(process->stdin); string_unlock_and_release(process->working_directory); list_unlock_and_release(process->environment); list_unlock_and_release(process->arguments); string_unlock_and_release(process->executable); free(process); }
void usb_destroy_context(libusb_context *context) { const struct libusb_pollfd **pollfds = NULL; const struct libusb_pollfd **pollfd; libusb_set_pollfd_notifiers(context, NULL, NULL, NULL); pollfds = libusb_get_pollfds(context); if (pollfds == NULL) { log_error("Could not get pollfds from main libusb context"); } else { for (pollfd = pollfds; *pollfd != NULL; ++pollfd) { event_remove_source((*pollfd)->fd, EVENT_SOURCE_TYPE_USB); } #if defined(_WIN32) || (defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000104) // libusb 1.0.20 libusb_free_pollfds(pollfds); // avoids possible heap-mismatch on Windows #else free(pollfds); #endif } libusb_exit(context); }
int iokit_init(void) { int phase = 0; Semaphore handshake; log_debug("Initializing IOKit subsystem"); // create notification pipe if (pipe_create(&_notification_pipe) < 0) { log_error("Could not create notification pipe: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 1; if (event_add_source(_notification_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, iokit_forward_notifications, NULL) < 0) { goto cleanup; } phase = 2; // create notification poll thread if (semaphore_create(&handshake) < 0) { log_error("Could not create handshake semaphore: %s (%d)", get_errno_name(errno), errno); goto cleanup; } thread_create(&_poll_thread, iokit_poll_notifications, &handshake); semaphore_acquire(&handshake); semaphore_destroy(&handshake); phase = 3; if (!_running) { log_error("Could not start notification poll thread"); goto cleanup; } phase = 4; cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 3: thread_destroy(&_poll_thread); case 2: event_remove_source(_notification_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); case 1: pipe_destroy(&_notification_pipe); default: break; } return phase == 4 ? 0 : -1; }
int event_run_platform(Array *event_sources, bool *running, EventCleanupFunction cleanup) { int result = -1; int i; EventSource *event_source; fd_set *fd_read_set; fd_set *fd_write_set; fd_set *fd_error_set; int ready; int handled; uint8_t byte = 1; int rc; int event_source_count; uint32_t received_events; if (event_add_source(_usb_poll_ready_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, event_forward_usb_events, event_sources) < 0) { return -1; } *running = true; _usb_poll_running = true; thread_create(&_usb_poll_thread, event_poll_usb_events, event_sources); cleanup(); event_cleanup_sources(); while (*running) { // update SocketSet arrays if (event_reserve_socket_set(&_socket_read_set, // FIXME: this over-allocates event_sources->count) < 0) { log_error("Could not resize socket read set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } if (event_reserve_socket_set(&_socket_write_set, // FIXME: this over-allocates event_sources->count) < 0) { log_error("Could not resize socket write set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } if (event_reserve_socket_set(&_socket_error_set, // FIXME: this over-allocates event_sources->count) < 0) { log_error("Could not resize socket error set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } _socket_read_set->count = 0; _socket_write_set->count = 0; _socket_error_set->count = 0; for (i = 0; i < event_sources->count; ++i) { event_source = array_get(event_sources, i); if (event_source->type != EVENT_SOURCE_TYPE_GENERIC) { continue; } if ((event_source->events & EVENT_READ) != 0) { _socket_read_set->sockets[_socket_read_set->count++] = event_source->handle; } if ((event_source->events & EVENT_WRITE) != 0) { _socket_write_set->sockets[_socket_write_set->count++] = event_source->handle; } if ((event_source->events & EVENT_PRIO) != 0) { log_error("Event prio is not supported"); } if ((event_source->events & EVENT_ERROR) != 0) { _socket_error_set->sockets[_socket_error_set->count++] = event_source->handle; } } // start to select log_event_debug("Starting to select on %d + %d + %d %s event source(s)", _socket_read_set->count, _socket_write_set->count, _socket_error_set->count, event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, false)); semaphore_release(&_usb_poll_resume); fd_read_set = event_get_socket_set_as_fd_set(_socket_read_set); fd_write_set = event_get_socket_set_as_fd_set(_socket_write_set); fd_error_set = event_get_socket_set_as_fd_set(_socket_error_set); ready = select(0, fd_read_set, fd_write_set, fd_error_set, NULL); if (_usb_poll_running) { log_event_debug("Sending suspend signal to USB poll thread"); if (usbi_write(_usb_poll_suspend_pipe[1], &byte, 1) < 0) { log_error("Could not write to USB suspend pipe"); _usb_poll_stuck = true; goto cleanup; } semaphore_acquire(&_usb_poll_suspend); if (usbi_read(_usb_poll_suspend_pipe[0], &byte, 1) < 0) { log_error("Could not read from USB suspend pipe"); _usb_poll_stuck = true; goto cleanup; } } if (ready == SOCKET_ERROR) { rc = ERRNO_WINAPI_OFFSET + WSAGetLastError(); if (rc == ERRNO_WINAPI_OFFSET + WSAEINTR) { continue; } log_error("Could not select on %s event sources: %s (%d)", event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, false), get_errno_name(rc), rc); goto cleanup; } // handle select result log_event_debug("Select returned %d %s event source(s) as ready", ready, event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, false)); handled = 0; // cache event source count here to avoid looking at new event // sources that got added during the event handling event_source_count = event_sources->count; for (i = 0; *running && i < event_source_count && ready > handled; ++i) { event_source = array_get(event_sources, i); received_events = 0; if (event_source->type != EVENT_SOURCE_TYPE_GENERIC) { continue; } if (FD_ISSET(event_source->handle, fd_read_set)) { received_events |= EVENT_READ; } if (FD_ISSET(event_source->handle, fd_write_set)) { received_events |= EVENT_WRITE; } if (FD_ISSET(event_source->handle, fd_error_set)) { received_events |= EVENT_ERROR; } if (received_events == 0) { continue; } event_handle_source(event_source, received_events); ++handled; } if (ready == handled) { log_event_debug("Handled all ready %s event sources", event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, false)); } else if (*running) { log_warn("Handled only %d of %d ready %s event source(s)", handled, ready, event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, false)); } // now cleanup event sources that got marked as disconnected/removed // during the event handling cleanup(); event_cleanup_sources(); } result = 0; cleanup: *running = false; if (_usb_poll_running && !_usb_poll_stuck) { _usb_poll_running = false; log_debug("Stopping USB poll thread"); if (usbi_write(_usb_poll_suspend_pipe[1], &byte, 1) < 0) { log_error("Could not write to USB suspend pipe"); } else { semaphore_release(&_usb_poll_resume); thread_join(&_usb_poll_thread); } } thread_destroy(&_usb_poll_thread); event_remove_source(_usb_poll_ready_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); return result; }
int event_init_platform(void) { int phase = 0; // create read set if (event_reserve_socket_set(&_socket_read_set, 32) < 0) { log_error("Could not create socket read set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 1; // create write set if (event_reserve_socket_set(&_socket_write_set, 32) < 0) { log_error("Could not create socket write set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 2; // create error set if (event_reserve_socket_set(&_socket_error_set, 32) < 0) { log_error("Could not create socket error set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 3; // create stop pipe if (pipe_create(&_stop_pipe, 0) < 0) { log_error("Could not create stop pipe: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 4; if (event_add_source(_stop_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, NULL, NULL) < 0) { goto cleanup; } phase = 5; // create USB poll thread _usb_poll_running = false; _usb_poll_stuck = false; if (usbi_pipe(_usb_poll_suspend_pipe) < 0) { log_error("Could not create USB suspend pipe"); goto cleanup; } phase = 6; if (pipe_create(&_usb_poll_ready_pipe, 0) < 0) { log_error("Could not create USB ready pipe: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 7; if (array_create(&_usb_poll_pollfds, 32, sizeof(struct usbi_pollfd), true) < 0) { log_error("Could not create USB pollfd array: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 8; if (semaphore_create(&_usb_poll_resume) < 0) { log_error("Could not create USB resume semaphore: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 9; if (semaphore_create(&_usb_poll_suspend) < 0) { log_error("Could not create USB suspend semaphore: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 10; cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 9: semaphore_destroy(&_usb_poll_suspend); case 8: semaphore_destroy(&_usb_poll_resume); case 7: pipe_destroy(&_usb_poll_ready_pipe); case 6: usbi_close(_usb_poll_suspend_pipe[0]); usbi_close(_usb_poll_suspend_pipe[1]); case 5: event_remove_source(_stop_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); case 4: pipe_destroy(&_stop_pipe); case 3: free(_socket_error_set); case 2: free(_socket_write_set); case 1: free(_socket_read_set); default: break; } return phase == 10 ? 0 : -1; }
// public API APIE process_spawn(ObjectID executable_id, ObjectID arguments_id, ObjectID environment_id, ObjectID working_directory_id, uint32_t uid, uint32_t gid, ObjectID stdin_id, ObjectID stdout_id, ObjectID stderr_id, Session *session, uint16_t object_create_flags, bool release_on_death, ProcessStateChangedFunction state_changed, void *opaque, ObjectID *id, Process **object) { int phase = 0; APIE error_code; String *executable; List *arguments; Array arguments_array; int i; char **item; List *environment; Array environment_array; String *working_directory; File *stdin; File *stdout; File *stderr; pid_t pid; int status_pipe[2]; int sc_open_max; FILE *log_file; Process *process; // acquire and lock executable string object error_code = string_get_acquired_and_locked(executable_id, &executable); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 1; if (*executable->buffer == '\0') { error_code = API_E_INVALID_PARAMETER; log_warn("Cannot spawn child process using empty executable name"); goto cleanup; } // lock arguments list object error_code = list_get_acquired_and_locked(arguments_id, OBJECT_TYPE_STRING, &arguments); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 2; // prepare arguments array for execvpe if (array_create(&arguments_array, 1 + arguments->items.count + 1, sizeof(char *), true) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not create arguments array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } phase = 3; item = array_append(&arguments_array); if (item == NULL) { error_code = api_get_error_code_from_errno(); log_error("Could not append to arguments array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } *item = executable->buffer; for (i = 0; i < arguments->items.count; ++i) { item = array_append(&arguments_array); if (item == NULL) { error_code = api_get_error_code_from_errno(); log_error("Could not append to arguments array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } *item = (*(String **)array_get(&arguments->items, i))->buffer; } item = array_append(&arguments_array); if (item == NULL) { error_code = api_get_error_code_from_errno(); log_error("Could not append to arguments array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } *item = NULL; // lock environment list object error_code = list_get_acquired_and_locked(environment_id, OBJECT_TYPE_STRING, &environment); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 4; // prepare environment array for execvpe if (array_create(&environment_array, environment->items.count + 1, sizeof(char *), true) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not create environment array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } phase = 5; for (i = 0; i < environment->items.count; ++i) { item = array_append(&environment_array); if (item == NULL) { error_code = api_get_error_code_from_errno(); log_error("Could not append to environment array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } // FIXME: if item is not <name>=<value>, but just <name> then use the parent <value> *item = (*(String **)array_get(&environment->items, i))->buffer; } item = array_append(&environment_array); if (item == NULL) { error_code = api_get_error_code_from_errno(); log_error("Could not append to environment array for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } *item = NULL; // acquire and lock working directory string object error_code = string_get_acquired_and_locked(working_directory_id, &working_directory); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 6; if (*working_directory->buffer == '\0') { error_code = API_E_INVALID_PARAMETER; log_warn("Cannot spawn child process (executable: %s) using empty working directory name", executable->buffer); goto cleanup; } if (*working_directory->buffer != '/') { error_code = API_E_INVALID_PARAMETER; log_warn("Cannot spawn child process (executable: %s) using working directory with relative name '%s'", executable->buffer, working_directory->buffer); goto cleanup; } // acquire stdin file object error_code = file_get_acquired(stdin_id, &stdin); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 7; // acquire stdout file object error_code = file_get_acquired(stdout_id, &stdout); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 8; // acquire stderr file object error_code = file_get_acquired(stderr_id, &stderr); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 9; // create status pipe if (pipe(status_pipe) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not create status pipe for spawning child process (executable: %s): %s (%d)", executable->buffer, get_errno_name(errno), errno); goto cleanup; } phase = 10; // fork log_debug("Forking to spawn child process (executable: %s)", executable->buffer); error_code = process_fork(&pid); if (error_code != API_E_SUCCESS) { goto cleanup; } if (pid == 0) { // child close(status_pipe[0]); // change user and groups error_code = process_set_identity(uid, gid); if (error_code != API_E_SUCCESS) { goto child_error; } // change directory if (chdir(working_directory->buffer) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not change directory to '%s' for child process (executable: %s, pid: %u): %s (%d)", working_directory->buffer, executable->buffer, getpid(), get_errno_name(errno), errno); goto child_error; } // get open FD limit sc_open_max = sysconf(_SC_OPEN_MAX); if (sc_open_max < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not get SC_OPEN_MAX value: %s (%d)", get_errno_name(errno), errno); goto child_error; } // redirect stdin if (dup2(file_get_read_handle(stdin), STDIN_FILENO) != STDIN_FILENO) { error_code = api_get_error_code_from_errno(); log_error("Could not redirect stdin for child process (executable: %s, pid: %u): %s (%d)", executable->buffer, getpid(), get_errno_name(errno), errno); goto child_error; } // redirect stdout if (dup2(file_get_write_handle(stdout), STDOUT_FILENO) != STDOUT_FILENO) { error_code = api_get_error_code_from_errno(); log_error("Could not redirect stdout for child process (executable: %s, pid: %u): %s (%d)", executable->buffer, getpid(), get_errno_name(errno), errno); goto child_error; } // stderr is the default log output in non-daemon mode. if this is // the case then disable the log output before redirecting stderr to // avoid polluting stderr for the new process log_file = log_get_file(); if (log_file != NULL && fileno(log_file) == STDERR_FILENO) { log_debug("Disable logging to stderr for child process (executable: %s, pid: %u)", executable->buffer, getpid()); log_set_file(NULL); } // redirect stderr if (dup2(file_get_write_handle(stderr), STDERR_FILENO) != STDERR_FILENO) { error_code = api_get_error_code_from_errno(); log_error("Could not redirect stderr for child process (executable: %s, pid: %u): %s (%d)", executable->buffer, getpid(), get_errno_name(errno), errno); goto child_error; } // notify parent if (robust_write(status_pipe[1], &error_code, sizeof(error_code)) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not write to status pipe for child process (executable: %s, pid: %u): %s (%d)", executable->buffer, getpid(), get_errno_name(errno), errno); goto child_error; } // disable log output. if stderr was not the current log output then // the log file is still open at this point. the next step is to close // all remaining file descriptors. just for good measure disable the // log output beforehand log_set_file(NULL); // close all file descriptors except the std* ones for (i = STDERR_FILENO + 1; i < sc_open_max; ++i) { close(i); } // execvpe only returns in case of an error execvpe(executable->buffer, (char **)arguments_array.bytes, (char **)environment_array.bytes); if (errno == ENOENT) { _exit(PROCESS_E_DOES_NOT_EXIST); } else { _exit(PROCESS_E_CANNOT_EXECUTE); } child_error: // notify parent in all cases if (robust_write(status_pipe[1], &error_code, sizeof(error_code)) < 0) { log_error("Could not write to status pipe for child process (executable: %s, pid: %u): %s (%d)", executable->buffer, getpid(), get_errno_name(errno), errno); } close(status_pipe[1]); _exit(PROCESS_E_INTERNAL_ERROR); } phase = 11; // wait for child to start successfully if (robust_read(status_pipe[0], &error_code, sizeof(error_code)) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not read from status pipe for child process (executable: %s, pid: %u): %s (%d)", executable->buffer, pid, get_errno_name(errno), errno); goto cleanup; } if (error_code != API_E_SUCCESS) { goto cleanup; } // create process object process = calloc(1, sizeof(Process)); if (process == NULL) { error_code = API_E_NO_FREE_MEMORY; log_error("Could not allocate process object: %s (%d)", get_errno_name(ENOMEM), ENOMEM); goto cleanup; } phase = 12; // setup process object process->executable = executable; process->arguments = arguments; process->environment = environment; process->working_directory = working_directory; process->uid = uid; process->gid = gid; process->pid = pid; process->stdin = stdin; process->stdout = stdout; process->stderr = stderr; process->release_on_death = release_on_death; process->state_changed = state_changed; process->opaque = opaque; process->state = PROCESS_STATE_RUNNING; process->timestamp = time(NULL); process->exit_code = 0; // invalid if (pipe_create(&process->state_change_pipe, 0) < 0) { error_code = api_get_error_code_from_errno(); log_error("Could not create state change pipe child process (executable: %s, pid: %u): %s (%d)", executable->buffer, pid, get_errno_name(errno), errno); goto cleanup; } phase = 13; if (event_add_source(process->state_change_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, process_handle_state_change, process) < 0) { goto cleanup; } phase = 14; // create process object error_code = object_create(&process->base, OBJECT_TYPE_PROCESS, session, object_create_flags | OBJECT_CREATE_FLAG_INTERNAL, process_destroy, process_signature); if (error_code != API_E_SUCCESS) { goto cleanup; } phase = 15; if (id != NULL) { *id = process->base.id; } if (object != NULL) { *object = process; } // start thread to wait for child process state changes thread_create(&process->wait_thread, process_wait, process); log_debug("Spawned process object (id: %u, executable: %s, pid: %u)", process->base.id, executable->buffer, process->pid); close(status_pipe[0]); close(status_pipe[1]); array_destroy(&arguments_array, NULL); array_destroy(&environment_array, NULL); cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 14: event_remove_source(process->state_change_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); case 13: pipe_destroy(&process->state_change_pipe); case 12: free(process); case 11: kill(pid, SIGKILL); case 10: close(status_pipe[0]); close(status_pipe[1]); case 9: file_release(stderr); case 8: file_release(stdout); case 7: file_release(stdin); case 6: string_unlock_and_release(working_directory); case 5: array_destroy(&environment_array, NULL); case 4: list_unlock_and_release(environment); case 3: array_destroy(&arguments_array, NULL); case 2: list_unlock_and_release(arguments); case 1: string_unlock_and_release(executable); default: break; } return phase == 15 ? API_E_SUCCESS : error_code; }
// NOTE: this function needs to call RegisterServiceCtrlHandlerEx and // SetServiceStatus in all circumstances if brickd is running as service static int generic_main(int log_to_file, int debug) { int exit_code = EXIT_FAILURE; const char *mutex_name = "Global\\Tinkerforge-Brick-Daemon-Single-Instance"; HANDLE mutex_handle = NULL; int mutex_error = 0; DWORD service_exit_code = NO_ERROR; int rc; char filename[1024]; int i; FILE *logfile = NULL; WSADATA wsa_data; DEV_BROADCAST_DEVICEINTERFACE notification_filter; HDEVNOTIFY notification_handle; mutex_handle = OpenMutex(SYNCHRONIZE, FALSE, mutex_name); if (mutex_handle == NULL) { rc = GetLastError(); if (rc == ERROR_ACCESS_DENIED) { rc = service_is_running(); if (rc < 0) { mutex_error = 1; // FIXME: set service_exit_code goto error_mutex; } else if (rc) { mutex_error = 1; service_exit_code = ERROR_SERVICE_ALREADY_RUNNING; log_error("Could not start as %s, another instance is already running as service", _run_as_service ? "service" : "console application"); goto error_mutex; } } if (rc != ERROR_FILE_NOT_FOUND) { mutex_error = 1; // FIXME: set service_exit_code rc += ERRNO_WINAPI_OFFSET; log_error("Could not open single instance mutex: %s (%d)", get_errno_name(rc), rc); goto error_mutex; } } if (mutex_handle != NULL) { mutex_error = 1; service_exit_code = ERROR_SERVICE_ALREADY_RUNNING; log_error("Could not start as %s, another instance is already running", _run_as_service ? "service" : "console application"); goto error_mutex; } mutex_handle = CreateMutex(NULL, FALSE, mutex_name); if (mutex_handle == NULL) { mutex_error = 1; // FIXME: set service_exit_code rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_error("Could not create single instance mutex: %s (%d)", get_errno_name(rc), rc); goto error_mutex; } if (!_run_as_service && !SetConsoleCtrlHandler(console_ctrl_handler, TRUE)) { rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_warn("Could not set console control handler: %s (%d)", get_errno_name(rc), rc); } if (log_to_file) { if (GetModuleFileName(NULL, filename, sizeof(filename)) == 0) { rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_warn("Could not get module file name: %s (%d)", get_errno_name(rc), rc); } else { i = strlen(filename); if (i < 4) { log_warn("Module file name '%s' is too short", filename); } else { strcpy(filename + i - 3, "log"); logfile = fopen(filename, "a+"); if (logfile == NULL) { log_warn("Could not open logfile '%s'", filename); } else { log_set_file(logfile); } } } } if (debug) { log_set_level(LOG_CATEGORY_EVENT, LOG_LEVEL_DEBUG); log_set_level(LOG_CATEGORY_USB, LOG_LEVEL_DEBUG); log_set_level(LOG_CATEGORY_NETWORK, LOG_LEVEL_DEBUG); log_set_level(LOG_CATEGORY_HOTPLUG, LOG_LEVEL_DEBUG); log_set_level(LOG_CATEGORY_OTHER, LOG_LEVEL_DEBUG); } else { log_set_level(LOG_CATEGORY_EVENT, config_get_log_level(LOG_CATEGORY_EVENT)); log_set_level(LOG_CATEGORY_USB, config_get_log_level(LOG_CATEGORY_USB)); log_set_level(LOG_CATEGORY_NETWORK, config_get_log_level(LOG_CATEGORY_NETWORK)); log_set_level(LOG_CATEGORY_HOTPLUG, config_get_log_level(LOG_CATEGORY_HOTPLUG)); log_set_level(LOG_CATEGORY_OTHER, config_get_log_level(LOG_CATEGORY_OTHER)); } if (_run_as_service) { log_info("Brick Daemon %s started (as service)", VERSION_STRING); } else { log_info("Brick Daemon %s started", VERSION_STRING); } if (config_has_error()) { log_warn("Errors found in config file '%s', run with --check-config option for details", _config_filename); } // initialize service status error_mutex: if (_run_as_service) { if (service_init(service_control_handler) < 0) { // FIXME: set service_exit_code goto error; } if (!mutex_error) { // service is starting service_set_status(SERVICE_START_PENDING, NO_ERROR); } } if (mutex_error) { goto error; } // initialize WinSock2 if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { // FIXME: set service_exit_code rc = ERRNO_WINAPI_OFFSET + WSAGetLastError(); log_error("Could not initialize Windows Sockets 2.2: %s (%d)", get_errno_name(rc), rc); goto error_event; } if (event_init() < 0) { // FIXME: set service_exit_code goto error_event; } if (usb_init() < 0) { // FIXME: set service_exit_code goto error_usb; } // create notification pipe if (pipe_create(_notification_pipe) < 0) { // FIXME: set service_exit_code log_error("Could not create notification pipe: %s (%d)", get_errno_name(errno), errno); goto error_pipe; } if (event_add_source(_notification_pipe[0], EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, forward_notifications, NULL) < 0) { // FIXME: set service_exit_code goto error_pipe_add; } // register device notification ZeroMemory(¬ification_filter, sizeof(notification_filter)); notification_filter.dbcc_size = sizeof(notification_filter); notification_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; notification_filter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE; if (_run_as_service) { notification_handle = RegisterDeviceNotification((HANDLE)service_get_status_handle(), ¬ification_filter, DEVICE_NOTIFY_SERVICE_HANDLE); } else { if (message_pump_start() < 0) { // FIXME: set service_exit_code goto error_pipe_add; } notification_handle = RegisterDeviceNotification(_message_pump_hwnd, ¬ification_filter, DEVICE_NOTIFY_WINDOW_HANDLE); } if (notification_handle == NULL) { // FIXME: set service_exit_code rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_error("Could not register for device notification: %s (%d)", get_errno_name(rc), rc); goto error_notification; } if (network_init() < 0) { // FIXME: set service_exit_code goto error_network; } // running if (_run_as_service) { service_set_status(SERVICE_RUNNING, NO_ERROR); } if (event_run() < 0) { // FIXME: set service_exit_code goto error_run; } exit_code = EXIT_SUCCESS; error_run: network_exit(); error_network: UnregisterDeviceNotification(notification_handle); error_notification: if (!_run_as_service) { message_pump_stop(); } event_remove_source(_notification_pipe[0], EVENT_SOURCE_TYPE_GENERIC); error_pipe_add: pipe_destroy(_notification_pipe); error_pipe: usb_exit(); error_usb: event_exit(); error_event: log_info("Brick Daemon %s stopped", VERSION_STRING); error: log_exit(); config_exit(); if (_run_as_service) { // because the service process can be terminated at any time after // entering SERVICE_STOPPED state the mutex is closed beforehand, // even though this creates a tiny time window in which the service // is still running but the mutex is not held anymore if (mutex_handle != NULL) { CloseHandle(mutex_handle); } // service is now stopped service_set_status(SERVICE_STOPPED, service_exit_code); } else { if (_pause_before_exit) { printf("Press any key to exit...\n"); getch(); } if (mutex_handle != NULL) { CloseHandle(mutex_handle); } } return exit_code; }
void socat_destroy(Socat *socat) { event_remove_source(socat->socket->handle, EVENT_SOURCE_TYPE_GENERIC); socket_destroy(socat->socket); free(socat->socket); }
static void bricklet_stack_transceive(BrickletStack *bricklet_stack) { // If we have not seen any data from the Bricklet we increase a counter. // If the counter reaches BRICKLET_STACK_FIRST_MESSAGE_TRIES we assume that // there is no Bricklet and we stop trying to send to initial message (if a // Bricklet is hotplugged it will send a enumerate itself). if(!bricklet_stack->data_seen) { if(bricklet_stack->first_message_tries < BRICKLET_STACK_FIRST_MESSAGE_TRIES) { bricklet_stack->first_message_tries++; } else { bricklet_stack->buffer_send_length = 0; } } const uint16_t length_read = bricklet_stack_check_missing_length(bricklet_stack); if(bricklet_stack->buffer_send_length == 0) { // If buffer is empty we try to send request from the queue. bricklet_stack_check_request_queue(bricklet_stack); if((bricklet_stack->buffer_send_length == 0) && (bricklet_stack->ack_to_send)) { // If there is no request in the queue (buffer still empty) // and we have to send an ACK still, we send the ACK. bricklet_stack_send_ack(bricklet_stack); } } uint16_t length_write = bricklet_stack->wait_for_ack ? 0 : bricklet_stack->buffer_send_length; uint16_t length = MAX(MAX(length_read, length_write), 1); uint8_t rx[SPITFP_MAX_TFP_MESSAGE_LENGTH] = {0}; uint8_t tx[SPITFP_MAX_TFP_MESSAGE_LENGTH] = {0}; if((length == 1) || (!bricklet_stack->data_seen)) { // If there is nothing to read or to write, we give the Bricklet some breathing // room before we start polling again. // If we have nothing to send and we are currently not awaiting data from the Bricklet, we will // poll every 200 us. uint32_t sleep_us = 200; if(!bricklet_stack->data_seen) { // If we have never seen any data, we will first poll every 1ms with the StackEnumerate message // and switch to polling every 500ms after we tried BRICKLET_STACK_FIRST_MESSAGE_TRIES times. // In this case there is likely no Bricklet connected. If a Bricklet is hotpluged "data_seen" // will be true and we will switch to polling every 200us immediately. if(bricklet_stack->first_message_tries < BRICKLET_STACK_FIRST_MESSAGE_TRIES) { sleep_us = 1*1000; } else { sleep_us = 500*1000; } } struct timespec t; t.tv_sec = 0; t.tv_nsec = 1000*sleep_us; clock_nanosleep(CLOCK_MONOTONIC, 0, &t, NULL); } memcpy(tx, bricklet_stack->buffer_send, length_write); struct spi_ioc_transfer spi_transfer = { .tx_buf = (unsigned long)tx, .rx_buf = (unsigned long)rx, .len = length, }; // Make sure that we only access SPI once at a time mutex_lock(bricklet_stack->config.mutex); // Do chip select by hand if necessary if(bricklet_stack->config.chip_select_driver == CHIP_SELECT_GPIO) { if(gpio_sysfs_set_output(&bricklet_stack->config.chip_select_gpio_sysfs, GPIO_SYSFS_VALUE_LOW) < 0) { log_error("Could not enable chip select"); return; } } int rc = ioctl(bricklet_stack->spi_fd, SPI_IOC_MESSAGE(1), &spi_transfer); // If the length is 1 (i.e. we wanted to see if the SPI slave has data for us) // and he does have data for us, we will immediately retrieve the data without // giving back the mutex. if((length == 1) && (rx[0] != 0) && (rc == length) && (length_write == 0)) { // First add the one byte of already received data to the ringbuffer ringbuffer_add(&bricklet_stack->ringbuffer_recv, rx[0]); // Set rc to 0, so if there is no more data to read, we don't get the // "unexpected result" error rc = 0; // Get length for rest of message length = bricklet_stack_check_missing_length(bricklet_stack); if(length != 0) { // Set first byte back to 0 and the new length, the rest was not touched // and we don't need to reinizialize it. rx[0] = 0; spi_transfer.len = length; rc = ioctl(bricklet_stack->spi_fd, SPI_IOC_MESSAGE(1), &spi_transfer); } } // Do chip deselect by hand if necessary if(bricklet_stack->config.chip_select_driver == CHIP_SELECT_GPIO) { if(gpio_sysfs_set_output(&bricklet_stack->config.chip_select_gpio_sysfs, GPIO_SYSFS_VALUE_HIGH) < 0) { log_error("Could not disable chip select"); return; } } mutex_unlock(bricklet_stack->config.mutex); if (rc < 0) { log_error("ioctl failed: %s (%d)", get_errno_name(errno), errno); return; } if (rc != length) { log_error("ioctl has unexpected result (actual: %d != expected: %d)", rc, length); return; } // We don't expect an ACK to be acked, so we can set the length to 0 here if(bricklet_stack->buffer_send_length == SPITFP_PROTOCOL_OVERHEAD) { bricklet_stack->buffer_send_length = 0; } if(bricklet_stack->buffer_send_length >= SPITFP_MIN_TFP_MESSAGE_LENGTH) { bricklet_stack->wait_for_ack = true; } for(uint16_t i = 0; i < length; i++) { ringbuffer_add(&bricklet_stack->ringbuffer_recv, rx[i]); } } static void bricklet_stack_spi_thread(void *opaque) { BrickletStack *bricklet_stack = (BrickletStack*)opaque; bricklet_stack->spi_thread_running = true; // Depending on the configuration we wait on startup for // other Bricklets to identify themself first. struct timespec t = { .tv_sec = bricklet_stack->config.startup_wait_time, }; clock_nanosleep(CLOCK_MONOTONIC, 0, &t, NULL); // Pre-fill the send buffer with the "StackEnumerate"-Packet. // This packet will trigger an initial enumeration in the Bricklet. // If the Brick Daemon is restarted, we need to // trigger the initial enumeration, since the Bricklet does not know // that it has to enumerate itself again. PacketHeader header = { .uid = 0, .length = sizeof(PacketHeader), .function_id = FUNCTION_STACK_ENUMERATE, .sequence_number_and_options = 0x08, // return expected .error_code_and_future_use = 0 }; bricklet_stack_send_ack_and_message(bricklet_stack, (uint8_t*)&header, sizeof(PacketHeader)); while (bricklet_stack->spi_thread_running) { bricklet_stack_transceive(bricklet_stack); bricklet_stack_check_message(bricklet_stack); } } static int bricklet_stack_init_spi(BrickletStack *bricklet_stack) { // Use hw chip select if it is done by SPI hardware unit, otherwise set SPI_NO_CS flag. const int mode = BRICKLET_STACK_SPI_CONFIG_MODE | (bricklet_stack->config.chip_select_driver == CHIP_SELECT_HARDWARE ? 0 : SPI_NO_CS); const int lsb_first = BRICKLET_STACK_SPI_CONFIG_LSB_FIRST; const int bits_per_word = BRICKLET_STACK_SPI_CONFIG_BITS_PER_WORD; const int max_speed_hz = BRICKLET_STACK_SPI_CONFIG_MAX_SPEED_HZ; // Open spidev bricklet_stack->spi_fd = open(bricklet_stack->config.spi_device, O_RDWR); if (bricklet_stack->spi_fd < 0) { log_error("Could not open %s: : %s (%d)", bricklet_stack->config.spi_device, get_errno_name(errno), errno); return -1; } if (ioctl(bricklet_stack->spi_fd, SPI_IOC_WR_MODE, &mode) < 0) { log_error("Could not configure SPI mode: %s (%d)", get_errno_name(errno), errno); return -1; } if (ioctl(bricklet_stack->spi_fd, SPI_IOC_WR_MAX_SPEED_HZ, &max_speed_hz) < 0) { log_error("Could not configure SPI max speed: %s (%d)", get_errno_name(errno), errno); return -1; } if (ioctl(bricklet_stack->spi_fd, SPI_IOC_WR_BITS_PER_WORD, &bits_per_word) < 0) { log_error("Could not configure SPI bits per word: %s (%d)", get_errno_name(errno), errno); return -1; } if (ioctl(bricklet_stack->spi_fd, SPI_IOC_WR_LSB_FIRST, &lsb_first) < 0) { log_error("Could not configure SPI lsb first: %s (%d)", get_errno_name(errno), errno); return -1; } thread_create(&bricklet_stack->spi_thread, bricklet_stack_spi_thread, bricklet_stack); return 0; } BrickletStack* bricklet_stack_init(BrickletStackConfig *config) { int phase = 0; char bricklet_stack_name[129] = {'\0'}; char notification_name[129] = {'\0'}; log_debug("Initializing BrickletStack subsystem for '%s' (num %d)", config->spi_device, config->chip_select_gpio_sysfs.num); if(config->chip_select_driver == CHIP_SELECT_GPIO) { if(gpio_sysfs_export(&config->chip_select_gpio_sysfs) < 0) { goto cleanup; } if(gpio_sysfs_set_direction(&config->chip_select_gpio_sysfs, GPIO_SYSFS_DIRECTION_OUTPUT) < 0) { goto cleanup; } if(gpio_sysfs_set_output(&config->chip_select_gpio_sysfs, GPIO_SYSFS_VALUE_HIGH) < 0) { goto cleanup; } } // create bricklet_stack struct BrickletStack *bricklet_stack = (BrickletStack*)malloc(sizeof(BrickletStack)); if(bricklet_stack == NULL) { goto cleanup; } memset(bricklet_stack, 0, sizeof(BrickletStack)); bricklet_stack->spi_fd = -1; bricklet_stack->spi_thread_running = false; memcpy(&bricklet_stack->config, config, sizeof(BrickletStackConfig)); ringbuffer_init(&bricklet_stack->ringbuffer_recv, BRICKLET_STACK_SPI_RECEIVE_BUFFER_LENGTH, bricklet_stack->buffer_recv); // create base stack if (snprintf(bricklet_stack_name, 128, "bricklet-stack-%s", bricklet_stack->config.spi_device) < 0) { goto cleanup; } if (stack_create(&bricklet_stack->base, bricklet_stack_name, bricklet_stack_dispatch_to_spi) < 0) { log_error("Could not create base stack for BrickletStack: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 1; // add to stacks array if (hardware_add_stack(&bricklet_stack->base) < 0) { goto cleanup; } phase = 2; if ((bricklet_stack->notification_event = eventfd(0, EFD_NONBLOCK | EFD_SEMAPHORE)) < 0) { log_error("Could not create bricklet notification event: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 3; // Add notification pipe as event source. // Event is used to dispatch packets. if (snprintf(notification_name, 128, "bricklet-stack-notification-%s", bricklet_stack->config.spi_device) < 0) { goto cleanup; } if (event_add_source(bricklet_stack->notification_event, EVENT_SOURCE_TYPE_GENERIC, notification_name, EVENT_READ, bricklet_stack_dispatch_from_spi, bricklet_stack) < 0) { log_error("Could not add bricklet notification pipe as event source"); goto cleanup; } phase = 4; // Initialize SPI packet queues if (queue_create(&bricklet_stack->request_queue, sizeof(Packet)) < 0) { log_error("Could not create SPI request queue: %s (%d)", get_errno_name(errno), errno); goto cleanup; } mutex_create(&bricklet_stack->request_queue_mutex); phase = 5; if (queue_create(&bricklet_stack->response_queue, sizeof(Packet)) < 0) { log_error("Could not create SPI response queue: %s (%d)", get_errno_name(errno), errno); goto cleanup; } mutex_create(&bricklet_stack->response_queue_mutex); phase = 6; if (bricklet_stack_init_spi(bricklet_stack) < 0) { goto cleanup; } phase = 7; cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 6: mutex_destroy(&bricklet_stack->response_queue_mutex); queue_destroy(&bricklet_stack->response_queue, NULL); // fall through case 5: mutex_destroy(&bricklet_stack->request_queue_mutex); queue_destroy(&bricklet_stack->request_queue, NULL); // fall through case 4: event_remove_source(bricklet_stack->notification_event, EVENT_SOURCE_TYPE_GENERIC); // fall through case 3: robust_close(bricklet_stack->notification_event); // fall through case 2: hardware_remove_stack(&bricklet_stack->base); // fall through case 1: stack_destroy(&bricklet_stack->base); // fall through default: break; } return phase == 7 ? bricklet_stack : NULL; } void bricklet_stack_exit(BrickletStack *bricklet_stack) { // Remove event as possible poll source event_remove_source(bricklet_stack->notification_event, EVENT_SOURCE_TYPE_GENERIC); // Make sure that Thread shuts down properly if (bricklet_stack->spi_thread_running) { bricklet_stack->spi_thread_running = false; thread_join(&bricklet_stack->spi_thread); thread_destroy(&bricklet_stack->spi_thread); } hardware_remove_stack(&bricklet_stack->base); stack_destroy(&bricklet_stack->base); queue_destroy(&bricklet_stack->request_queue, NULL); mutex_destroy(&bricklet_stack->request_queue_mutex); queue_destroy(&bricklet_stack->response_queue, NULL); mutex_destroy(&bricklet_stack->response_queue_mutex); // Close file descriptors robust_close(bricklet_stack->notification_event); robust_close(bricklet_stack->spi_fd); // Everything is closed and the threads are destroyed. We can // now free the Bricklet Stack memory. It will not be accessed anymore. free(bricklet_stack); }
int usb_create_context(libusb_context **context) { int phase = 0; int rc; const struct libusb_pollfd **pollfds = NULL; const struct libusb_pollfd **pollfd; const struct libusb_pollfd **last_added_pollfd = NULL; rc = libusb_init(context); if (rc < 0) { log_error("Could not initialize libusb context: %s (%d)", usb_get_error_name(rc), rc); goto cleanup; } switch (log_get_effective_level()) { case LOG_LEVEL_ERROR: libusb_set_debug(*context, 1); break; case LOG_LEVEL_WARN: libusb_set_debug(*context, 2); break; case LOG_LEVEL_INFO: libusb_set_debug(*context, 3); break; case LOG_LEVEL_DEBUG: if (log_is_included(LOG_LEVEL_DEBUG, &_libusb_log_source, LOG_DEBUG_GROUP_LIBUSB)) { libusb_set_debug(*context, 4); } else { libusb_set_debug(*context, 3); } break; default: break; } phase = 1; // get pollfds from main libusb context pollfds = libusb_get_pollfds(*context); if (pollfds == NULL) { log_error("Could not get pollfds from libusb context"); goto cleanup; } for (pollfd = pollfds; *pollfd != NULL; ++pollfd) { if (event_add_source((*pollfd)->fd, EVENT_SOURCE_TYPE_USB, (*pollfd)->events, usb_handle_events, *context) < 0) { goto cleanup; } last_added_pollfd = pollfd; phase = 2; } phase = 3; // register pollfd notifiers libusb_set_pollfd_notifiers(*context, usb_add_pollfd, usb_remove_pollfd, *context); cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 2: for (pollfd = pollfds; pollfd != last_added_pollfd; ++pollfd) { event_remove_source((*pollfd)->fd, EVENT_SOURCE_TYPE_USB); } case 1: libusb_exit(*context); default: break; } #if defined(_WIN32) || (defined(LIBUSB_API_VERSION) && LIBUSB_API_VERSION >= 0x01000104) // libusb 1.0.20 libusb_free_pollfds(pollfds); // avoids possible heap-mismatch on Windows #else free(pollfds); #endif return phase == 3 ? 0 : -1; }
int red_stack_init(void) { int i = 0; int phase = 0; log_debug("Initializing RED Brick SPI Stack subsystem"); _red_stack_spi_poll_delay = config_get_option_value("poll_delay.spi")->integer; if (gpio_sysfs_export(RED_STACK_RESET_PIN_GPIO_NUM) < 0) { // Just issue a warning, RED Brick will work without reset interrupt log_warn("Could not export GPIO %d in sysfs, disabling reset interrupt", RED_STACK_RESET_PIN_GPIO_NUM); } else { if ((_red_stack_reset_fd = gpio_sysfs_get_value_fd(RED_STACK_RESET_PIN_GPIO_NAME)) < 0) { // Just issue a warning, RED Brick will work without reset interrupt log_warn("Could not retrieve fd for GPIO %s in sysfs, disabling reset interrupt", RED_STACK_RESET_PIN_GPIO_NAME); } else { // If everything worked we can set the interrupt to falling. // We ignore the return value here, it may work despite error. gpio_sysfs_set_edge(RED_STACK_RESET_PIN_GPIO_NAME, "falling"); } } // create base stack if (stack_create(&_red_stack.base, "red_stack", red_stack_dispatch_to_spi) < 0) { log_error("Could not create base stack for RED Brick SPI Stack: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 1; // add to stacks array if (hardware_add_stack(&_red_stack.base) < 0) { goto cleanup; } phase = 2; if ((_red_stack_notification_event = eventfd(0, 0)) < 0) { log_error("Could not create red stack notification event: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 3; // Add notification pipe as event source. // Event is used to dispatch packets. if (event_add_source(_red_stack_notification_event, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, red_stack_dispatch_from_spi, NULL) < 0) { log_error("Could not add red stack notification pipe as event source"); goto cleanup; } phase = 4; // Initialize SPI packet queues for (i = 0; i < RED_STACK_SPI_MAX_SLAVES; i++) { if (queue_create(&_red_stack.slaves[i].packet_to_spi_queue, sizeof(REDStackPacket)) < 0) { log_error("Could not create SPI queue %d: %s (%d)", i, get_errno_name(errno), errno); goto cleanup; } } if (semaphore_create(&_red_stack_dispatch_packet_from_spi_semaphore) < 0) { log_error("Could not create SPI request semaphore: %s (%d)", get_errno_name(errno), errno); goto cleanup; } for (i = 0; i < RED_STACK_SPI_MAX_SLAVES; i++) { mutex_create(&_red_stack.slaves[i].packet_queue_mutex); } phase = 5; if (red_stack_init_spi() < 0) { goto cleanup; } // Add reset interrupt as event source if (_red_stack_reset_fd > 0) { char buf[2]; lseek(_red_stack_reset_fd, 0, SEEK_SET); if (read(_red_stack_reset_fd, buf, 2) < 0) {} // ignore return value if (event_add_source(_red_stack_reset_fd, EVENT_SOURCE_TYPE_GENERIC, EVENT_PRIO | EVENT_ERROR, red_stack_reset_handler, NULL) < 0) { log_error("Could not add reset fd event"); goto cleanup; } } phase = 6; cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 5: for (i = 0; i < RED_STACK_SPI_MAX_SLAVES; i++) { mutex_destroy(&_red_stack.slaves[i].packet_queue_mutex); } semaphore_destroy(&_red_stack_dispatch_packet_from_spi_semaphore); case 4: for (i--; i >= 0; i--) { queue_destroy(&_red_stack.slaves[i].packet_to_spi_queue, NULL); } event_remove_source(_red_stack_notification_event, EVENT_SOURCE_TYPE_GENERIC); case 3: close(_red_stack_notification_event); case 2: hardware_remove_stack(&_red_stack.base); case 1: stack_destroy(&_red_stack.base); default: break; } return phase == 6 ? 0 : -1; }
// Init function called from central brickd code int rs485_extension_init(void) { uint8_t _tmp_eeprom_read_buf[4]; int _eeprom_read_status; int phase = 0; log_info("RS485: Checking presence of extension"); // Modbus config: TYPE _eeprom_read_status = i2c_eeprom_read((uint16_t)RS485_EXTENSION_MODBUS_CONFIG_LOCATION_TYPE, _tmp_eeprom_read_buf, 4); if (_eeprom_read_status <= 0) { log_error("RS485: EEPROM read error. Most probably no RS485 extension present"); return 0; } _modbus_serial_config_type = (uint32_t)((_tmp_eeprom_read_buf[0] << 0) | (_tmp_eeprom_read_buf[1] << 8) | (_tmp_eeprom_read_buf[2] << 16) | (_tmp_eeprom_read_buf[3] << 24)); if (_modbus_serial_config_type == RS485_EXTENSION_TYPE) { log_info("RS485: Initializing extension subsystem"); // Create base stack if(stack_create(&_rs485_extension.base, "rs485_extension", (StackDispatchRequestFunction)rs485_extension_dispatch_to_modbus) < 0) { log_error("RS485: Could not create base stack for extension: %s (%d)", get_errno_name(errno), errno); goto cleanup; } phase = 1; // Add to stacks array if(hardware_add_stack(&_rs485_extension.base) < 0) { goto cleanup; } phase = 2; // Initialize modbus packet queue if(queue_create(&_rs485_extension.packet_to_modbus_queue, sizeof(RS485ExtensionPacket)) < 0) { log_error("RS485: Could not create Modbus queue: %s (%d)", get_errno_name(errno), errno); goto cleanup; } // Reading and storing eeprom config // Modbus config: ADDRESS _eeprom_read_status = i2c_eeprom_read((uint16_t)RS485_EXTENSION_MODBUS_CONFIG_LOCATION_ADDRESS, _tmp_eeprom_read_buf, 4); if (_eeprom_read_status <= 0) { log_error("RS485: Could not read config ADDRESS from EEPROM"); goto cleanup; } _modbus_serial_config_address = (uint32_t)((_tmp_eeprom_read_buf[0] << 0) | (_tmp_eeprom_read_buf[1] << 8) | (_tmp_eeprom_read_buf[2] << 16) | (_tmp_eeprom_read_buf[3] << 24)); // Modbus config: BAUDRATE _eeprom_read_status = i2c_eeprom_read((uint16_t)RS485_EXTENSION_MODBUS_CONFIG_LOCATION_BAUDRATE, _tmp_eeprom_read_buf, 4); if (_eeprom_read_status <= 0) { log_error("RS485: Could not read config BAUDRATE from EEPROM"); goto cleanup; } _modbus_serial_config_baudrate = (uint32_t)((_tmp_eeprom_read_buf[0] << 0) | (_tmp_eeprom_read_buf[1] << 8) | (_tmp_eeprom_read_buf[2] << 16) | (_tmp_eeprom_read_buf[3] << 24)); // Modbus config: PARITY _eeprom_read_status = i2c_eeprom_read((uint16_t)RS485_EXTENSION_MODBUS_CONFIG_LOCATION_PARTIY, _tmp_eeprom_read_buf, 1); if (_eeprom_read_status <= 0) { log_error("RS485: Could not read config PARITY from EEPROM"); goto cleanup; } if(_tmp_eeprom_read_buf[0] == RS485_EXTENSION_SERIAL_PARITY_NONE) { _modbus_serial_config_parity = RS485_EXTENSION_SERIAL_PARITY_NONE; } else if (_tmp_eeprom_read_buf[0] == RS485_EXTENSION_SERIAL_PARITY_EVEN){ _modbus_serial_config_parity = RS485_EXTENSION_SERIAL_PARITY_EVEN; } else { _modbus_serial_config_parity = RS485_EXTENSION_SERIAL_PARITY_ODD; } // Modbus config: STOPBITS _eeprom_read_status = i2c_eeprom_read((uint16_t)RS485_EXTENSION_MODBUS_CONFIG_LOCATION_STOPBITS, _tmp_eeprom_read_buf, 1); if (_eeprom_read_status <= 0) { log_error("RS485: Could not read config STOPBITS from EEPROM"); goto cleanup; } _modbus_serial_config_stopbits = _tmp_eeprom_read_buf[0]; // Modbus config (if master): SLAVE ADDRESSES if(_modbus_serial_config_address == 0) { _rs485_extension.slave_num = 0; uint16_t _current_eeprom_location = RS485_EXTENSION_MODBUS_CONFIG_LOCATION_SLAVE_ADDRESSES_START; uint32_t _current_slave_address; _rs485_extension.slave_num = 0; do { _eeprom_read_status = i2c_eeprom_read(_current_eeprom_location, _tmp_eeprom_read_buf, 4); if (_eeprom_read_status <= 0) { log_error("RS485: Could not read config SLAVE ADDRESSES from EEPROM"); goto cleanup; } _current_slave_address = (uint32_t)((_tmp_eeprom_read_buf[0] << 0) | (_tmp_eeprom_read_buf[1] << 8) | (_tmp_eeprom_read_buf[2] << 16) | (_tmp_eeprom_read_buf[3] << 24)); if(_current_slave_address != 0) { _rs485_extension.slaves[_rs485_extension.slave_num] = _current_slave_address; _rs485_extension.slave_num ++; } _current_eeprom_location = _current_eeprom_location + 4; } while(_current_slave_address != 0 && _rs485_extension.slave_num < RS485_EXTENSION_MODBUS_MAX_SLAVES); } // Configuring serial interface from the configs if(rs485_extension_serial_init(RS485_EXTENSION_SERIAL_DEVICE) < 0) { goto cleanup; } // Initial RS485 TX/RX state init_tx_rx_state(); phase = 3; // Setup partial data receive timer setup_timer(&partial_receive_timer, TIME_UNIT_NSEC, PARTIAL_RECEIVE_TIMEOUT); _partial_receive_timeout_event = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if(!(_partial_receive_timeout_event < 0)) { if(event_add_source(_partial_receive_timeout_event, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, partial_receive_timeout_handler, NULL) < 0) { log_error("RS485: Could not add partial receive timeout notification pipe as event source"); goto cleanup; } } else { log_error("RS485: Could not create partial receive timer"); goto cleanup; } phase = 4; // Adding serial data available event if(event_add_source(_rs485_serial_fd, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, rs485_serial_data_available_handler, NULL) < 0) { log_error("RS485: Could not add new serial data event"); goto cleanup; } phase = 5; // Setup master retry timer setup_timer(&master_retry_timer, TIME_UNIT_NSEC, MASTER_RETRY_TIMEOUT); _master_retry_event = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if(!(_master_retry_event < 0)) { if(event_add_source(_master_retry_event, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, master_retry_timeout_handler, NULL) < 0) { log_error("RS485: Could not add Modbus master retry notification pipe as event source"); goto cleanup; } } else { log_error("RS485: Could not create Modbus master retry timer"); goto cleanup; } phase = 6; // Setup send verify timer setup_timer(&send_verify_timer, TIME_UNIT_NSEC, SEND_VERIFY_TIMEOUT); _send_verify_event = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if(!(_send_verify_event < 0)) { if(event_add_source(_send_verify_event, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, send_verify_timeout_handler, NULL) < 0) { log_error("RS485: Could not add Modbus send verify notification pipe as event source"); goto cleanup; } } else { log_error("RS485: Could not create Modbus send verify timer"); goto cleanup; } phase = 7; // Get things going in case of a master if(_modbus_serial_config_address == 0 && _rs485_extension.slave_num > 0) { // Setup master poll slave timer setup_timer(&master_poll_slave_timer, TIME_UNIT_NSEC, MASTER_POLL_SLAVE_TIMEOUT); _master_poll_slave_event = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); if(!(_master_poll_slave_event < 0) ) { if(event_add_source(_master_poll_slave_event, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, master_poll_slave_timeout_handler, NULL) < 0) { log_error("RS485: Could not add Modbus master poll slave notification pipe as event source"); goto cleanup; } } else { log_error("RS485: Could not create Modbus master poll slave timer"); goto cleanup; } master_poll_slave_timeout_handler(NULL); } phase = 8; _initialized = true; } else { log_info("RS485: Extension not present"); goto cleanup; } cleanup: switch (phase) { // no breaks, all cases fall through intentionally case 7: close(_send_verify_event); event_remove_source(_send_verify_event, EVENT_SOURCE_TYPE_GENERIC); case 6: close(_master_retry_event); event_remove_source(_master_retry_event, EVENT_SOURCE_TYPE_GENERIC); case 5: close(_rs485_serial_fd); event_remove_source(_rs485_serial_fd, EVENT_SOURCE_TYPE_GENERIC); case 4: close(_partial_receive_timeout_event); event_remove_source(_partial_receive_timeout_event, EVENT_SOURCE_TYPE_GENERIC); case 3: queue_destroy(&_rs485_extension.packet_to_modbus_queue, NULL); case 2: hardware_remove_stack(&_rs485_extension.base); case 1: stack_destroy(&_rs485_extension.base); default: break; } return phase == 8 ? 0 : -1; }
// NOTE: this function needs to call RegisterServiceCtrlHandlerEx and // SetServiceStatus in all circumstances if brickd is running as service static int generic_main(bool log_to_file, bool debug, bool libusb_debug) { int exit_code = EXIT_FAILURE; const char *mutex_name = "Global\\Tinkerforge-Brick-Daemon-Single-Instance"; HANDLE mutex_handle = NULL; bool fatal_error = false; DWORD service_exit_code = NO_ERROR; int rc; char filename[1024]; int i; FILE *log_file = NULL; WSADATA wsa_data; DEV_BROADCAST_DEVICEINTERFACE notification_filter; HDEVNOTIFY notification_handle; mutex_handle = OpenMutex(SYNCHRONIZE, FALSE, mutex_name); if (mutex_handle == NULL) { rc = GetLastError(); if (rc == ERROR_ACCESS_DENIED) { rc = service_is_running(); if (rc < 0) { fatal_error = true; // FIXME: set service_exit_code goto error_mutex; } else if (rc) { fatal_error = true; service_exit_code = ERROR_SERVICE_ALREADY_RUNNING; log_error("Could not start as %s, another instance is already running as service", _run_as_service ? "service" : "console application"); goto error_mutex; } } if (rc != ERROR_FILE_NOT_FOUND) { fatal_error = true; // FIXME: set service_exit_code rc += ERRNO_WINAPI_OFFSET; log_error("Could not open single instance mutex: %s (%d)", get_errno_name(rc), rc); goto error_mutex; } } if (mutex_handle != NULL) { fatal_error = true; service_exit_code = ERROR_SERVICE_ALREADY_RUNNING; log_error("Could not start as %s, another instance is already running", _run_as_service ? "service" : "console application"); goto error_mutex; } mutex_handle = CreateMutex(NULL, FALSE, mutex_name); if (mutex_handle == NULL) { fatal_error = true; // FIXME: set service_exit_code rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_error("Could not create single instance mutex: %s (%d)", get_errno_name(rc), rc); goto error_mutex; } if (log_to_file) { if (GetModuleFileName(NULL, filename, sizeof(filename)) == 0) { rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_warn("Could not get module file name: %s (%d)", get_errno_name(rc), rc); } else { i = strlen(filename); if (i < 4) { log_warn("Module file name '%s' is too short", filename); } else { filename[i - 3] = '\0'; string_append(filename, "log", sizeof(filename)); log_file = fopen(filename, "a+"); if (log_file == NULL) { log_warn("Could not open log file '%s'", filename); } else { printf("Logging to '%s'\n", filename); log_set_file(log_file); } } } } else if (_run_as_service) { log_set_file(NULL); } if (!_run_as_service && !SetConsoleCtrlHandler(console_ctrl_handler, TRUE)) { rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_warn("Could not set console control handler: %s (%d)", get_errno_name(rc), rc); } log_set_debug_override(debug); log_set_level(LOG_CATEGORY_EVENT, config_get_option("log_level.event")->value.log_level); log_set_level(LOG_CATEGORY_USB, config_get_option("log_level.usb")->value.log_level); log_set_level(LOG_CATEGORY_NETWORK, config_get_option("log_level.network")->value.log_level); log_set_level(LOG_CATEGORY_HOTPLUG, config_get_option("log_level.hotplug")->value.log_level); log_set_level(LOG_CATEGORY_HARDWARE, config_get_option("log_level.hardware")->value.log_level); log_set_level(LOG_CATEGORY_WEBSOCKET, config_get_option("log_level.websocket")->value.log_level); log_set_level(LOG_CATEGORY_OTHER, config_get_option("log_level.other")->value.log_level); if (config_has_error()) { log_error("Error(s) in config file '%s', run with --check-config option for details", _config_filename); fatal_error = true; goto error_config; } if (_run_as_service) { log_info("Brick Daemon %s started (as service)", VERSION_STRING); } else { log_info("Brick Daemon %s started", VERSION_STRING); } if (config_has_warning()) { log_warn("Warning(s) in config file '%s', run with --check-config option for details", _config_filename); } // initialize service status error_config: error_mutex: if (_run_as_service) { if (service_init(service_control_handler) < 0) { // FIXME: set service_exit_code goto error; } if (!fatal_error) { // service is starting service_set_status(SERVICE_START_PENDING, NO_ERROR); } } if (fatal_error) { goto error; } // initialize WinSock2 if (WSAStartup(MAKEWORD(2, 2), &wsa_data) != 0) { // FIXME: set service_exit_code rc = ERRNO_WINAPI_OFFSET + WSAGetLastError(); log_error("Could not initialize Windows Sockets 2.2: %s (%d)", get_errno_name(rc), rc); goto error_event; } if (event_init() < 0) { // FIXME: set service_exit_code goto error_event; } if (hardware_init() < 0) { // FIXME: set service_exit_code goto error_hardware; } if (usb_init(libusb_debug) < 0) { // FIXME: set service_exit_code goto error_usb; } // create notification pipe if (pipe_create(&_notification_pipe) < 0) { // FIXME: set service_exit_code log_error("Could not create notification pipe: %s (%d)", get_errno_name(errno), errno); goto error_pipe; } if (event_add_source(_notification_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, forward_notifications, NULL) < 0) { // FIXME: set service_exit_code goto error_pipe_add; } // register device notification ZeroMemory(¬ification_filter, sizeof(notification_filter)); notification_filter.dbcc_size = sizeof(notification_filter); notification_filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; notification_filter.dbcc_classguid = GUID_DEVINTERFACE_USB_DEVICE; if (_run_as_service) { notification_handle = RegisterDeviceNotification((HANDLE)service_get_status_handle(), ¬ification_filter, DEVICE_NOTIFY_SERVICE_HANDLE); } else { if (message_pump_start() < 0) { // FIXME: set service_exit_code goto error_pipe_add; } notification_handle = RegisterDeviceNotification(_message_pump_hwnd, ¬ification_filter, DEVICE_NOTIFY_WINDOW_HANDLE); } if (notification_handle == NULL) { // FIXME: set service_exit_code rc = ERRNO_WINAPI_OFFSET + GetLastError(); log_error("Could not register for device notification: %s (%d)", get_errno_name(rc), rc); goto error_notification; } if (network_init() < 0) { // FIXME: set service_exit_code goto error_network; } // running if (_run_as_service) { service_set_status(SERVICE_RUNNING, NO_ERROR); } if (event_run(network_cleanup_clients_and_zombies) < 0) { // FIXME: set service_exit_code goto error_run; } exit_code = EXIT_SUCCESS; error_run: network_exit(); error_network: UnregisterDeviceNotification(notification_handle); error_notification: if (!_run_as_service) { message_pump_stop(); } event_remove_source(_notification_pipe.read_end, EVENT_SOURCE_TYPE_GENERIC); error_pipe_add: pipe_destroy(&_notification_pipe); error_pipe: usb_exit(); error_usb: hardware_exit(); error_hardware: event_exit(); error_event: log_info("Brick Daemon %s stopped", VERSION_STRING); error: if (!_run_as_service) { // unregister the console handler before exiting the log. otherwise a // control event might be send to the control handler after the log // is not available anymore and the control handler tries to write a // log messages triggering a crash. this situation could easily be // created by clicking the close button of the command prompt window // while the getch call is waiting for the user to press a key. SetConsoleCtrlHandler(console_ctrl_handler, FALSE); } log_exit(); config_exit(); if (_run_as_service) { // because the service process can be terminated at any time after // entering SERVICE_STOPPED state the mutex is closed beforehand, // even though this creates a tiny time window in which the service // is still running but the mutex is not held anymore if (mutex_handle != NULL) { CloseHandle(mutex_handle); } // service is now stopped service_set_status(SERVICE_STOPPED, service_exit_code); } else { if (_pause_before_exit) { printf("Press any key to exit...\n"); getch(); } if (mutex_handle != NULL) { CloseHandle(mutex_handle); } } return exit_code; }
int event_run_platform(Array *event_sources, int *running) { int result = -1; int i; EventSource *event_source; fd_set *fd_read_set; fd_set *fd_write_set; int ready; int handled; uint8_t byte = 1; int rc; int event_source_count; int received_events; if (event_add_source(_usb_poller.ready_pipe[0], EVENT_SOURCE_TYPE_GENERIC, EVENT_READ, event_forward_usb_events, event_sources) < 0) { return -1; } *running = 1; _usb_poller.running = 1; thread_create(&_usb_poller.thread, event_poll_usb_events, event_sources); event_cleanup_sources(); while (*running) { // update SocketSet arrays if (event_reserve_socket_set(&_socket_read_set, // FIXME: this over allocates event_sources->count) < 0) { log_error("Could not resize socket read set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } if (event_reserve_socket_set(&_socket_write_set, // FIXME: this over allocates event_sources->count) < 0) { log_error("Could not resize socket write set: %s (%d)", get_errno_name(errno), errno); goto cleanup; } _socket_read_set->count = 0; _socket_write_set->count = 0; for (i = 0; i < event_sources->count; i++) { event_source = array_get(event_sources, i); if (event_source->type != EVENT_SOURCE_TYPE_GENERIC) { continue; } if (event_source->events & EVENT_READ) { _socket_read_set->sockets[_socket_read_set->count++] = event_source->handle; } if (event_source->events & EVENT_WRITE) { _socket_write_set->sockets[_socket_write_set->count++] = event_source->handle; } } // start to select log_debug("Starting to select on %d + %d %s event source(s)", _socket_read_set->count, _socket_write_set->count, event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, 0)); semaphore_release(&_usb_poller.resume); fd_read_set = event_get_socket_set_as_fd_set(_socket_read_set); fd_write_set = event_get_socket_set_as_fd_set(_socket_write_set); ready = select(0, fd_read_set, fd_write_set, NULL, NULL); if (_usb_poller.running) { log_debug("Sending suspend signal to USB poll thread"); if (usbi_write(_usb_poller.suspend_pipe[1], &byte, 1) < 0) { log_error("Could not write to USB suspend pipe"); _usb_poller.stuck = 1; *running = 0; goto cleanup; } semaphore_acquire(&_usb_poller.suspend); if (usbi_read(_usb_poller.suspend_pipe[0], &byte, 1) < 0) { log_error("Could not read from USB suspend pipe"); _usb_poller.stuck = 1; *running = 0; goto cleanup; } } if (ready < 0) { rc = ERRNO_WINAPI_OFFSET + WSAGetLastError(); if (rc == ERRNO_WINAPI_OFFSET + WSAEINTR) { continue; } log_error("Could not select on %s event sources: %s (%d)", event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, 0), get_errno_name(rc), rc); *running = 0; goto cleanup; } // handle select result log_debug("Select returned %d %s event source(s) as ready", ready, event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, 0)); handled = 0; // cache event source count here to avoid looking at new event // sources that got added during the event handling event_source_count = event_sources->count; for (i = 0; i < event_source_count && ready > handled; ++i) { event_source = array_get(event_sources, i); received_events = 0; if (event_source->type != EVENT_SOURCE_TYPE_GENERIC) { continue; } if (FD_ISSET(event_source->handle, fd_read_set)) { received_events |= EVENT_READ; } if (FD_ISSET(event_source->handle, fd_write_set)) { received_events |= EVENT_WRITE; } if (received_events == 0) { continue; } if (event_source->state != EVENT_SOURCE_STATE_NORMAL) { log_debug("Ignoring %s event source (handle: %d, received events: %d) marked as removed at index %d", event_get_source_type_name(event_source->type, 0), event_source->handle, received_events, i); } else { log_debug("Handling %s event source (handle: %d, received events: %d) at index %d", event_get_source_type_name(event_source->type, 0), event_source->handle, received_events, i); if (event_source->function != NULL) { event_source->function(event_source->opaque); } } ++handled; if (!*running) { break; } } if (ready == handled) { log_debug("Handled all ready %s event sources", event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, 0)); } else { log_warn("Handled only %d of %d ready %s event source(s)", handled, ready, event_get_source_type_name(EVENT_SOURCE_TYPE_GENERIC, 0)); } // now remove event sources that got marked as removed during the // event handling event_cleanup_sources(); } result = 0; cleanup: if (_usb_poller.running && !_usb_poller.stuck) { _usb_poller.running = 0; log_debug("Stopping USB poll thread"); if (usbi_write(_usb_poller.suspend_pipe[1], &byte, 1) < 0) { log_error("Could not write to USB suspend pipe"); } else { semaphore_release(&_usb_poller.resume); thread_join(&_usb_poller.thread); } } event_remove_source(_usb_poller.ready_pipe[0], EVENT_SOURCE_TYPE_GENERIC); return result; }