{{#include ../links.md}} # Writing Binaries ### Binaries - Porcelain and Plumbing Binaries are split into two categories, porcelain (such as `keyfork`) and plumbing (just about everything else). Porcelain binaries include what can be called "the kitchen sink". They offer support for everything - an intuitive interface, automatic `keyforkd` management, interconnectivity between derivation utilities and provisioning utilities, and the ability to read from and write to a configuration file. Plumbing binaries, on the other hand, are often very rough around the edges and pull in as few dependencies as possible. Usually, only cryptographic functionality (such as `sequoia-openpgp` or `dalek-ed25519`) or hardware integration libraries (such as `openpgp-card`) are included. ### Writing Binaries Crates can be either a library, or a library and binary, but should never be just a binary. When creating a crate with a binary, the `main.rs` file should be designed to validate arguments, load any necessary system resources, and call a separate exposed function to do the heavy lifting. The following example was taken from `keyfork-shard` to demonstrate how a program can validate arguments, parse input, and stream an output. ```rust use std::{ collections::VecDeque, env, io::{stdin, stdout}, path::PathBuf, process::ExitCode, str::FromStr, }; use keyfork_shard::openpgp::{combine, discover_certs, openpgp::Cert}; use sequoia_openpgp as openpgp; type Result> = std::result::Result; fn validate(threshold: &str, key_discovery: &str) -> Result<(u8, Vec)> { let threshold = u8::from_str(threshold)?; let key_discovery = PathBuf::from(key_discovery); // Verify path exists std::fs::metadata(&key_discovery)?; // Load certs from path let certs = discover_certs(key_discovery)?; Ok((threshold, certs)) } fn run() -> Result<()> { let mut args = env::args(); let program_name = args.next().expect("program name"); let args = args.collect::>(); let (threshold, cert_list) = match args.as_slice() { [threshold, key_discovery] => validate(threshold, key_discovery)?, _ => panic!("Usage: {program_name} threshold key_discovery"), }; let encrypted_messages = parse_messages(stdin())?; let encrypted_metadata = encrypted_messages .pop_front() .expect("any pgp encrypted message"); combine( threshold, cert_list, encrypted_metadata, encrypted_messages.into(), stdout(), )?; Ok(()) } fn main() -> ExitCode { let result = run(); if let Err(e) = result { eprintln!("Error: {e}"); return ExitCode::FAILURE; } ExitCode::SUCCESS } ``` Designing binaries with this format makes it easier to load them to the Keyfork porcelain binary, since the porcelain can call `combine()` with arguments that it has parsed using its own configuration systems, using a `String` as a `mut Write` as necessary.