self_cmd 0.1.8

Self-executing command builder for Rust programs / Rust 程序自执行命令构建器
Documentation

English | 中文


self_cmd: Self-Executing Command Builder

Overview

self_cmd is a Rust library that creates Command instances for re-executing the current program with identical arguments and stdio inheritance. On Linux, it provides additional support for systemd socket activation by preserving file descriptor mappings through the LISTEN_FDS protocol.

Features

  • Self-Replication: Generate Command objects that execute the current program
  • Argument Preservation: Automatically captures and forwards command-line arguments
  • Stdio Inheritance: Maintains stdin, stdout, and stderr connections
  • Socket Activation Support: Linux-specific file descriptor mapping for systemd integration
  • Robust Error Handling: Custom error types with proper error propagation
  • Logging Integration: Built-in logging support for debugging and monitoring

Usage

Add to your Cargo.toml:

[dependencies]
self_cmd = "0.1.7"

Basic usage:

use self_cmd;

fn main() -> self_cmd::Result<()> {
    // Create a command that will re-execute this program
    let mut cmd = self_cmd::get()?;
    
    // Execute the command
    let status = cmd.status()?;
    println!("Child process exited with: {status}");
    
    Ok(())
}

Advanced usage with custom handling:

use self_cmd;

fn restart_self() -> self_cmd::Result<()> {
    let mut cmd = self_cmd::get()?;
    
    // The command inherits stdio by default and preserves FD mappings on Linux
    // You can modify it if needed
    cmd.spawn()?;
    
    Ok(())
}

Linux Socket Activation

On Linux systems, self_cmd automatically handles systemd socket activation by preserving file descriptor mappings:

use self_cmd;

fn main() -> self_cmd::Result<()> {
    // On Linux, this will automatically:
    // 1. Check for LISTEN_FDS environment variable
    // 2. Duplicate and map file descriptors starting from FD 3
    // 3. Set LISTEN_FDS for the child process
    // 4. Remove LISTEN_PID to indicate new process ownership
    let mut cmd = self_cmd::get()?;
    
    cmd.spawn()?;
    Ok(())
}

The library handles the complete systemd socket activation protocol, making it seamless for services that need to restart while maintaining their listening sockets.

Design Philosophy

The library follows a minimalist approach with a single public function that encapsulates the complexity of self-execution:

graph TD
    A[Program Start] --> B["self_cmd::get()"]
    B --> C["env::current_exe()"]
    C --> D["env::args().skip(1)"]
    D --> E["Command::new(program)"]
    E --> F["cmd.args(args)"]
    F --> G[Configure Stdio Inheritance]
    G --> H[Return Command]
    H --> I[Execute Command]

The design ensures:

  • Simplicity: Single function interface
  • Reliability: Proper error handling without panics
  • Flexibility: Returns Command for further customization
  • Performance: Minimal overhead with lazy evaluation

Technical Stack

  • Language: Rust 2024 Edition
  • Core Dependencies:
    • log for logging functionality
    • thiserror for structured error handling
  • Linux Dependencies:
    • command-fds for file descriptor mapping
    • libc for low-level system calls
  • Error Handling: Custom Error type with thiserror integration
  • Process Management: std::process::Command with FD mapping extensions
  • Environment Access: std::env for executable path and arguments

Project Structure

self_cmd/
├── src/
│   ├── lib.rs          # Core implementation and public API
│   ├── error.rs        # Error types and handling
│   └── fd_mapping.rs   # Linux-specific FD mapping (conditional)
├── tests/
│   └── main.rs         # Integration tests
├── readme/
│   ├── en.md          # English documentation
│   └── zh.md          # Chinese documentation
├── Cargo.toml         # Project configuration
└── test.sh           # Test runner script

API Reference

get() -> Result<Command>

Creates a Command instance configured to re-execute the current program with full context preservation.

Returns:

  • Ok(Command): Configured command ready for execution
  • Err(Error): If unable to determine executable path or configure FD mappings

Behavior:

  • Captures current executable path via env::current_exe()
  • Preserves all command-line arguments except program name
  • Configures stdio inheritance (stdin, stdout, stderr)
  • Linux only: Handles systemd socket activation FD mapping
  • Returns Command for further customization before execution

Example:

match self_cmd::get() {
    Ok(mut cmd) => {
        // Command is ready to use with full context preservation
        cmd.status()?;
    }
    Err(e) => {
        eprintln!("Failed to create self command: {e}");
    }
}

fd_mapping() -> Result<Vec<FdMapping>> (Linux only)

Available on Linux only - Creates file descriptor mappings for systemd socket activation.

Returns:

  • Ok(Vec<FdMapping>): List of FD mappings for socket activation
  • Err(Error): If FD duplication or validation fails

This function is automatically called by get() on Linux systems and typically doesn't need to be used directly.

Error Handling

The library uses a custom Error type built with thiserror for comprehensive error handling:

use self_cmd::{Error, Result};

#[derive(Error, Debug)]
pub enum Error {
    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("FD mapping collision: {0}")]
    FdMappingCollision(#[from] command_fds::FdMappingCollision),
}

All public functions return Result<T> which is an alias for std::result::Result<T, Error>. This provides:

  • Structured Errors: Clear error types for different failure modes
  • Error Chaining: Automatic conversion from underlying error types
  • Debugging Support: Rich error messages with context
  • Integration: Seamless integration with ? operator and error handling patterns

About

This project is an open-source component of js0.site ⋅ Refactoring the Internet Plan.

We are redefining the development paradigm of the Internet in a componentized way. Welcome to follow us:


self_cmd: 自执行命令构建器

概述

self_cmd 是 Rust 库,用于创建重新执行当前程序的 Command 实例,保持相同参数和标准输入输出继承。在 Linux 系统上,通过 LISTEN_FDS 协议保持文件描述符映射,提供对 systemd 套接字激活的额外支持。

功能特性

  • 自我复制: 生成执行当前程序的 Command 对象
  • 参数保持: 自动捕获并转发命令行参数
  • 标准输入输出继承: 维持 stdin、stdout、stderr 连接
  • 套接字激活支持: Linux 特定的文件描述符映射,用于 systemd 集成
  • 健壮的错误处理: 自定义错误类型与正确的错误传播
  • 日志集成: 内置日志支持,用于调试和监控

使用方法

添加到 Cargo.toml:

[dependencies]
self_cmd = "0.1.7"

基本用法:

use self_cmd;

fn main() -> self_cmd::Result<()> {
    // 创建重新执行此程序的命令
    let mut cmd = self_cmd::get()?;
    
    // 执行命令
    let status = cmd.status()?;
    println!("子进程退出状态: {status}");
    
    Ok(())
}

高级用法与自定义处理:

use self_cmd;

fn restart_self() -> self_cmd::Result<()> {
    let mut cmd = self_cmd::get()?;
    
    // 命令默认继承标准输入输出,在 Linux 上保持 FD 映射
    // 可根据需要进行修改
    cmd.spawn()?;
    
    Ok(())
}

Linux 套接字激活

在 Linux 系统上,self_cmd 自动处理 systemd 套接字激活,保持文件描述符映射:

use self_cmd;

fn main() -> self_cmd::Result<()> {
    // 在 Linux 上,这将自动:
    // 1. 检查 LISTEN_FDS 环境变量
    // 2. 从 FD 3 开始复制和映射文件描述符
    // 3. 为子进程设置 LISTEN_FDS
    // 4. 移除 LISTEN_PID 以表示新进程所有权
    let mut cmd = self_cmd::get()?;
    
    cmd.spawn()?;
    Ok(())
}

库处理完整的 systemd 套接字激活协议,使需要重启但保持监听套接字的服务变得无缝。

设计理念

库采用极简主义方法,通过单个公共函数封装自执行的复杂性:

graph TD
    A[程序启动] --> B["self_cmd::get()"]
    B --> C["env::current_exe()"]
    C --> D["env::args().skip(1)"]
    D --> E["Command::new(program)"]
    E --> F["cmd.args(args)"]
    F --> G[配置标准输入输出继承]
    G --> H[返回 Command]
    H --> I[执行命令]

设计确保:

  • 简洁性: 单函数接口
  • 可靠性: 无恐慌的错误处理
  • 灵活性: 返回 Command 供进一步定制
  • 性能: 惰性求值的最小开销

技术栈

  • 语言: Rust 2024 版本
  • 核心依赖:
    • log 用于日志功能
    • thiserror 用于结构化错误处理
  • Linux 依赖:
    • command-fds 用于文件描述符映射
    • libc 用于底层系统调用
  • 错误处理: 自定义 Error 类型与 thiserror 集成
  • 进程管理: std::process::Command 与 FD 映射扩展
  • 环境访问: std::env 获取可执行文件路径和参数

项目结构

self_cmd/
├── src/
│   ├── lib.rs          # 核心实现和公共 API
│   ├── error.rs        # 错误类型和处理
│   └── fd_mapping.rs   # Linux 特定的 FD 映射(条件编译)
├── tests/
│   └── main.rs         # 集成测试
├── readme/
│   ├── en.md          # 英文文档
│   └── zh.md          # 中文文档
├── Cargo.toml         # 项目配置
└── test.sh           # 测试运行脚本

API 参考

get() -> Result<Command>

创建配置为重新执行当前程序的 Command 实例,完整保持上下文。

返回值:

  • Ok(Command): 配置好的命令,可直接执行
  • Err(Error): 无法确定可执行文件路径或配置 FD 映射时

行为:

  • 通过 env::current_exe() 捕获当前可执行文件路径
  • 保留除程序名外的所有命令行参数
  • 配置标准输入输出继承 (stdin, stdout, stderr)
  • 仅 Linux: 处理 systemd 套接字激活 FD 映射
  • 返回 Command 供执行前进一步定制

示例:

match self_cmd::get() {
    Ok(mut cmd) => {
        // 命令已准备就绪,完整保持上下文
        cmd.status()?;
    }
    Err(e) => {
        eprintln!("创建自执行命令失败: {e}");
    }
}

fd_mapping() -> Result<Vec<FdMapping>> (仅 Linux)

仅在 Linux 上可用 - 为 systemd 套接字激活创建文件描述符映射。

返回值:

  • Ok(Vec<FdMapping>): 套接字激活的 FD 映射列表
  • Err(Error): FD 复制或验证失败时

此函数在 Linux 系统上由 get() 自动调用,通常不需要直接使用。

错误处理

库使用基于 thiserror 构建的自定义 Error 类型进行全面的错误处理:

use self_cmd::{Error, Result};

#[derive(Error, Debug)]
pub enum Error {
    #[error("IO error: {0} / IO 错误: {0}")]
    Io(#[from] std::io::Error),
    
    #[error("FD mapping collision: {0} / FD 映射冲突: {0}")]
    FdMappingCollision(#[from] command_fds::FdMappingCollision),
}

所有公共函数返回 Result<T>,这是 std::result::Result<T, Error> 的别名。这提供了:

  • 结构化错误: 不同失败模式的清晰错误类型
  • 错误链: 从底层错误类型的自动转换
  • 调试支持: 带有上下文的丰富错误消息
  • 集成: 与 ? 操作符和错误处理模式的无缝集成

关于

本项目为 js0.site ⋅ 重构互联网计划 的开源组件。

我们正在以组件化的方式重新定义互联网的开发范式,欢迎关注: