blob: cebfbe0cef19c7be64935b1520927b98f245cb96 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use crate::call::Scope;
use futures_util::future::BoxFuture;
use serde::{Deserialize, Serialize};
use std::{collections::BTreeMap, sync::Arc};
#[derive(Clone, Copy, Default, Deserialize, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Lifetime {
#[default]
Invocation,
Instance,
}
#[derive(Clone, Deserialize, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)]
pub struct Command {
pub executable: String,
#[serde(default)]
pub args: Vec<String>,
#[serde(default)]
pub env: BTreeMap<String, String>,
#[serde(default)]
pub lifetime: Lifetime,
}
impl Command {
pub fn validate(&self) -> Result<(), Error> {
if !std::path::Path::new(&self.executable).is_absolute()
|| self.executable.contains('\0')
|| self.args.iter().any(|arg| arg.contains('\0'))
|| self.env.iter().any(|(key, value)| {
key.is_empty() || key.contains(['=', '\0']) || value.contains('\0')
})
{
return Err(Error::Invalid(
"invalid process command or launch limit exceeded".into(),
));
}
Ok(())
}
}
/// A lookup key in this activation, never an execution permission or durable PID.
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(transparent)]
pub struct Id(pub String);
#[derive(Clone, Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Exit {
pub code: Option<i32>,
pub success: bool,
pub stopped: bool,
pub error: Option<String>,
}
#[derive(Clone, Copy, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Stream {
Stdout,
Stderr,
}
#[derive(Serialize)]
pub struct Chunk {
pub stream: Stream,
pub bytes: Vec<u8>,
}
pub struct Handle {
pub id: Id,
pub io: Arc<dyn Process>,
}
pub trait Processes: Send + Sync {
fn spawn(&self, call: Scope, command: Command) -> BoxFuture<'_, Result<Handle, Error>>;
/// Bind an instance-owned process to a new authorized call. Old bindings
/// remain expired; the ID alone cannot keep their permissions alive.
fn open(&self, call: Scope, id: Id) -> Result<Handle, Error>;
/// Cleanup by owner remains allowed after the call binding expires.
fn close(&self, id: Id) -> BoxFuture<'_, Result<(), Error>>;
}
/// The native owner retains the process through physical settlement. Dropping
/// this view does not terminate it; close explicitly or retire its owner.
pub trait Process: Send + Sync {
fn write(&self, bytes: Vec<u8>) -> BoxFuture<'_, Result<(), Error>>;
fn end_input(&self) -> BoxFuture<'_, Result<(), Error>>;
fn next(&self) -> BoxFuture<'_, Result<Option<Chunk>, Error>>;
fn wait(&self) -> BoxFuture<'_, Result<Exit, Error>>;
fn close(&self) -> BoxFuture<'_, Result<(), Error>>;
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("process authority is retired or denied")]
Denied,
#[error("invalid process operation: {0}")]
Invalid(String),
#[error("process operation failed: {0}")]
Failed(String),
#[error("process cleanup is unconfirmed: {0}")]
CleanupUnconfirmed(String),
}
impl From<crate::authorization::CommandError> for Error {
fn from(error: crate::authorization::CommandError) -> Self {
use crate::authorization::CommandError;
match error {
CommandError::Denied | CommandError::Revoked => Self::Denied,
CommandError::Invalid(reason) => Self::Invalid(reason),
CommandError::OutcomeUnknown(reason) => Self::CleanupUnconfirmed(reason),
error => Self::Failed(error.to_string()),
}
}
}