Implement app streams: stdio for apps (#640)
Implement file IO for applications to facilitate text input/output capturing. Apps can now launch apps and: - Read their stdio output - Write to their stdio input
This commit is contained in:
committed by
GitHub
parent
64fb1a9f52
commit
6e5e35610b
@@ -0,0 +1,98 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/file.h>
|
||||
#include <app/io.h>
|
||||
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/error.h>
|
||||
|
||||
/**
|
||||
* One fd's worth of state in an AppFdTable. `file` is valid only while `in_use` is true.
|
||||
* `ever_used` is set once `in_use` is ever set true (including the default stdio binding at
|
||||
* construct time) and never cleared, even once `in_use` goes back to false on close. This is
|
||||
* what lets app_fd_table_is_app_owned() distinguish "this fd number belongs to us, it's just
|
||||
* currently closed" (an app-level EBADF) from "app-module has never touched this fd number" (a
|
||||
* real underlying fd, e.g. from fopen()/open(), that a caller should fall through on).
|
||||
*
|
||||
* @warning This is internal data. Do not read/write to it directly.
|
||||
*/
|
||||
struct AppFdSlot {
|
||||
struct AppFile file;
|
||||
bool in_use;
|
||||
bool ever_used;
|
||||
};
|
||||
|
||||
/**
|
||||
* Every app instance's fd table (see AppInstanceRecord). `mutex` guards `slots` only; it is
|
||||
* never held while calling into an AppFile's ops (dispatch copies the AppFile out under the lock
|
||||
* first), since those calls belong to independently synchronized objects (AppStream's own
|
||||
* mutex/event group) and may run concurrently with a close()/bind() replacing an unrelated slot.
|
||||
*
|
||||
* `shutting_down` is set under `mutex` by app_fd_table_teardown() before it destructs `mutex`,
|
||||
* and checked (also under `mutex`) by every other entry point. Callers are expected to
|
||||
* serialize against teardown externally (see app_stream_subscribe()/app_stream_unsubscribe()),
|
||||
* so a check() failure here means that external synchronization broke, not a condition to
|
||||
* handle gracefully.
|
||||
*
|
||||
* @warning This is internal data. Do not read/write to it directly.
|
||||
*/
|
||||
struct AppFdTable {
|
||||
struct AppFdSlot slots[APP_MAX_FDS];
|
||||
struct Mutex mutex;
|
||||
bool shutting_down;
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** Initializes every fd to the null device (see app/private/null_device.h) and constructs `mutex`. */
|
||||
void app_fd_table_construct(struct AppFdTable* table);
|
||||
|
||||
/** Closes every still-open fd (via AppFileOps::close()) and destructs `mutex`. */
|
||||
void app_fd_table_teardown(struct AppFdTable* table);
|
||||
|
||||
/**
|
||||
* Installs {ops, object} at @a fd, closing whatever was previously there first (the null device
|
||||
* counts as "previously there" for fds 0-2, so this doubles as their initial stdio binding).
|
||||
* @retval ERROR_OUT_OF_RANGE @a fd is outside [0, APP_MAX_FDS)
|
||||
*/
|
||||
error_t app_fd_table_bind(struct AppFdTable* table, int fd, const struct AppFileOps* ops, void* object);
|
||||
|
||||
/**
|
||||
* Installs {ops, object} at the lowest unused fd >= 3.
|
||||
* @retval ERROR_RESOURCE the table is full
|
||||
*/
|
||||
error_t app_fd_table_allocate(struct AppFdTable* table, const struct AppFileOps* ops, void* object, int* out_fd);
|
||||
|
||||
/** @return true and fills @a out_file if @a fd is currently in use, false otherwise. */
|
||||
bool app_fd_table_get(struct AppFdTable* table, int fd, struct AppFile* out_file);
|
||||
|
||||
/**
|
||||
* Like app_fd_table_get(), but also calls the returned AppFile's AppFileOps::retain() (if any)
|
||||
* while still holding `mutex`, atomically with the lookup. A caller that gets true back can
|
||||
* therefore never have @a fd closed/torn down out from under it between this call and actually
|
||||
* dispatching into the returned AppFile. Caller must call AppFileOps::release() once done
|
||||
* dispatching.
|
||||
*/
|
||||
bool app_fd_table_get_and_retain(struct AppFdTable* table, int fd, struct AppFile* out_file);
|
||||
|
||||
/**
|
||||
* Closes @a fd (via AppFileOps::close()) and frees the slot for reuse.
|
||||
* @retval ERROR_NOT_FOUND @a fd is out of range or not currently in use
|
||||
*/
|
||||
error_t app_fd_table_close(struct AppFdTable* table, int fd);
|
||||
|
||||
/**
|
||||
* @return true if @a fd is currently in use, or was in the past (even if since closed). This is
|
||||
* whether the table claims @a fd as one of its own, regardless of its current state. False means
|
||||
* @a fd is a real underlying fd this table has never bound or allocated.
|
||||
*/
|
||||
bool app_fd_table_is_app_owned(struct AppFdTable* table, int fd);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <app/instance.h>
|
||||
#include <app/manifest.h>
|
||||
#include <app/private/fd_table.h>
|
||||
|
||||
#include <tactility/concurrent/mutex.h>
|
||||
#include <tactility/freertos/freertos.h>
|
||||
@@ -49,6 +50,10 @@ struct AppInstanceRecord {
|
||||
/** This instance's completion signal - see AppCompletionSignal. Set once by
|
||||
* app_scheduler_start(), never reassigned. */
|
||||
AppCompletionSignal* completion = nullptr;
|
||||
|
||||
/** This instance's fd table. Constructed by start_internal() before insertion into
|
||||
* AppLedger::instances, torn down (every open fd closed) when the instance's task exits. */
|
||||
AppFdTable fd_table {};
|
||||
};
|
||||
|
||||
struct AppLedger {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/file.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @return the shared null-device AppFile: write() discards data and reports success, read()
|
||||
* reports EOF, close() is a no-op, and it is always reported readable/writable.
|
||||
*/
|
||||
const struct AppFile* app_null_file(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
#pragma once
|
||||
|
||||
#include <app/file.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/** @return the AppFileOps used for stream-backed fd table entries (see app_stream_subscribe()). */
|
||||
const struct AppFileOps* app_stream_ops(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user