static int i2c_eeprom_set_pointer(I2CEEPROM *i2c_eeprom, uint8_t *eeprom_memory_address) { int bytes_written = 0; if (i2c_eeprom == NULL || i2c_eeprom->file < 0) { log_error("I2C EEPROM structure uninitialized"); return -1; } bytes_written = robust_write(i2c_eeprom->file, eeprom_memory_address, 2); if (bytes_written != 2) { // We only use debug here to not spam the log with errors. // This is the expected case if an extension is not present. log_debug("Error setting EEPROM address pointer: %s (%d)", get_errno_name(errno), errno); i2c_eeprom_destroy(i2c_eeprom); return -1; } return bytes_written; }
int i2c_eeprom_read(I2CEEPROM *i2c_eeprom, uint16_t eeprom_memory_address, uint8_t *buffer_to_store, int bytes_to_read) { int bytes_read = 0; uint8_t mem_address[2] = {eeprom_memory_address >> 8, eeprom_memory_address & 0xFF}; if (i2c_eeprom == NULL || i2c_eeprom->file < 0) { log_error("I2C EEPROM structure uninitialized\n"); return -1; } i2c_eeprom_select(i2c_eeprom); if (i2c_eeprom_set_pointer(i2c_eeprom, mem_address) < 0) { return -1; } bytes_read = robust_read(i2c_eeprom->file, buffer_to_store, bytes_to_read); if (bytes_read != bytes_to_read) { log_error("EEPROM read failed: %s (%d)", get_errno_name(errno), errno); i2c_eeprom_destroy(i2c_eeprom); return -1; } i2c_eeprom_deselect(i2c_eeprom); return bytes_read; } int i2c_eeprom_write(I2CEEPROM *i2c_eeprom, uint16_t eeprom_memory_address, uint8_t *buffer_to_write, int bytes_to_write) { int i; int rc; char bytes_written = 0; uint8_t write_byte[3] = {0}; if (i2c_eeprom == NULL || i2c_eeprom->file < 0) { log_error("I2C EEPROM structure uninitialized\n"); return -1; } for (i = 0; i < bytes_to_write; i++) { write_byte[0] = eeprom_memory_address >> 8; write_byte[1] = eeprom_memory_address & 0xFF; write_byte[2] = buffer_to_write[i]; i2c_eeprom_select(i2c_eeprom); rc = robust_write(i2c_eeprom->file, write_byte, 3); i2c_eeprom_deselect(i2c_eeprom); // Wait at least 5ms between writes (see m24128-bw.pdf) usleep(5*1000); if (rc != 3) { log_error("EEPROM write failed (pos(%d), length(%d), expected length(%d): %s (%d)", i, rc, 3, get_errno_name(errno), errno); i2c_eeprom_destroy(i2c_eeprom); return -1; } eeprom_memory_address++; bytes_written++; } return bytes_written; }
// 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; }