use std::path::Path;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Shape {
Program,
Bidama,
Script,
Kernel,
}
const FLAKE: &str = r##"{
description = "__NAME__, a blue project";
# A blue project writes no nix: the build facts are in ./Bluefile, and
# blue's project engine lowers its lock (`blue lock .` after an edit).
inputs.blue.url = "github:pleme-io/blue";
outputs = { blue, ... }: blue.lib.project { src = ./.; };
}
"##;
const PROGRAM_BLUEFILE: &str = r##"# __NAME__'s build facts. After an edit, `blue lock .` rewrites Bluefile.lock.
package("__NAME__", "0.1.0")
packages("bidamas")
app("__NAME__", "main.b")
"##;
const MAIN: &str = r##"# `blue main.b ARGS` runs __NAME__; `nix run .#__NAME__ -- ARGS` runs it built.
use("__NAME__", [:main])
main(argv)
"##;
const BIDAMA_BLUEFILE: &str = r##"package("__NAME__", "0.1.0")
needs("retsu", "^0.1")
"##;
const BIDAMA: &str = r##"use("retsu", [:first])
# The greeting for a program's arguments: the first one, or the world.
def greeting(args)
who = first(args)
if who.nil?
"hello, world"
else
"hello, #{who}"
end
end
# The program: what `main.b` runs.
def main(args)
write_stdout("#{greeting(args)}\n")
end
test "with no arguments it greets the world"
assert greeting([]) == "hello, world"
end
test "it greets the first argument and ignores the rest"
assert greeting(["blue", "red"]) == "hello, blue"
end
test "an empty name is still a name"
assert greeting([""]) == "hello, "
end
"##;
const SCRIPT: &str = r##"#!/usr/bin/env blue
# `./__FILE__ ARGS` or `blue __FILE__ ARGS`; `blue new` made it executable.
use("retsu", [:size])
write_stdout("hello, #{argv_get(0, "world")} (#{size(argv)} argument(s))\n")
"##;
const KERNEL_BLUEFILE: &str = r##"# __NAME__'s build facts. After an edit, `blue lock .` rewrites Bluefile.lock.
package("__NAME__", "0.1.0")
allow(%i[clock environment filesystem kernel_bpf process])
check("__NAME__", "__NAME__.b", ["__NAME__.b"])
"##;
const KERNEL: &str = r##"@moduledoc """
__NAME__: a kernel program and its loader in one file. `count_packets` is the program, `main` loads it onto an interface, and the tests run the same source on the pure machine, and on a real kernel when this host has one. docs/tutorial/first-kernel-program.md explains each part.
"""
use("bpf", %i[compile declare_map map_add packet_int packet_len xdp_pass])
use("kaaneru", %i[attached install kernel_absence])
use("retsu", %i[first repeat])
use("tesuto", [:skip_for])
COUNTS = declare_map(:counts, :array, 4, 8, 256)
def count_packets(ctx)
if packet_len(ctx) >= 34
map_add(COUNTS, packet_int(ctx, 23, 1), 1)
end
xdp_pass
end
def program()
compile("__NAME__.b", {entry: :count_packets, kind: :xdp, on_fault: xdp_pass})
end
def frame(protocol)
append(*[repeat(0, 23), [protocol], repeat(0, 10)])
end
def interface_index(name)
path = "/sys/class/net/#{name}/ifindex"
if not path_exists(path)
raise(:bad_option, "no interface #{name} on this host")
end
to_int(trim(read_file(path)))
end
def main()
name = first(argv)
if name.nil?
raise(:bad_option, "name an interface: blue run --allow host --quiet __NAME__.b -- eth0")
end
attached(
program,
{xdp: interface_index(name)},
fn(live)
sleep_ms(5000)
write_stdout("ICMP packets in 5 seconds: #{to_s(kaaneru::map_int(live, :counts, 1))}
")
end
)
end
test "on the machine, a packet of one protocol is counted and passed"
p = program
st = bpf::run(p, {packet: frame(1), maps: p[:maps]})
assert st.ret == xdp_pass
assert bpf::map_int(st, :counts, 1) == 1
assert bpf::map_int(st, :counts, 6) == 0
end
test "on the machine, a frame too short for the field is passed and not counted"
p = program
st = bpf::run(p, {packet: repeat(0, 20), maps: p[:maps]})
assert st.ret == xdp_pass
assert bpf::map_int(st, :counts, 0) == 0
end
test "on a real kernel, the program counts what the machine counts"
skip_for(kernel_absence)
live = install(program)
assert kaaneru::run(live[:prog], {packet: frame(1), repeat: 3})[:ret] == xdp_pass
assert kaaneru::map_int(live, :counts, 1) == 3
assert kaaneru::map_int(live, :counts, 6) == 0
end
"##;
pub fn new(path: &Path, shape: Shape) -> Result<String, String> {
if path.exists() {
return Err(format!("{} already exists", path.display()));
}
let file_name = path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.ok_or_else(|| format!("`{}` names no file or directory", path.display()))?;
match shape {
Shape::Script => {
write(path, &SCRIPT.replace("__FILE__", &file_name))?;
executable(path)?;
Ok(format!(
"created {p}, executable\nrun it ./{p} (or blue {p})",
p = path.display()
))
}
Shape::Bidama => {
let name = bidama_name(&file_name)?;
bidama(path, &name)?;
Ok(format!(
"created {p}/: Bluefile, Bluefile.lock, {name}.b\ntest it blue test {p}/{name}.b",
p = path.display()
))
}
Shape::Kernel => {
let name = bidama_name(&file_name)?;
let fill = |t: &str| t.replace("__NAME__", &name);
std::fs::create_dir_all(path).map_err(|e| format!("{}: {e}", path.display()))?;
write(&path.join("Bluefile"), &fill(KERNEL_BLUEFILE))?;
write(&path.join("flake.nix"), &fill(FLAKE))?;
write(&path.join(format!("{name}.b")), &fill(KERNEL))?;
lock(path)?;
Ok(format!(
"created {p}/: Bluefile, Bluefile.lock, flake.nix, {name}.b\n\
test it cd {p} && blue test {name}.b\n\
run it cd {p} && sudo blue run --allow host --quiet {name}.b -- eth0",
p = path.display()
))
}
Shape::Program => {
let name = bidama_name(&file_name)?;
let fill = |t: &str| t.replace("__NAME__", &name);
std::fs::create_dir_all(path).map_err(|e| format!("{}: {e}", path.display()))?;
write(&path.join("Bluefile"), &fill(PROGRAM_BLUEFILE))?;
write(&path.join("flake.nix"), &fill(FLAKE))?;
write(&path.join("main.b"), &fill(MAIN))?;
lock(path)?;
bidama(&path.join("bidamas").join(&name), &name)?;
Ok(format!(
"created {p}/: Bluefile, Bluefile.lock, flake.nix, main.b, bidamas/{name}/\n\
run it blue {p}/main.b\n\
test it blue test {p}/bidamas/{name}/{name}.b",
p = path.display()
))
}
}
}
fn bidama(dir: &Path, name: &str) -> Result<(), String> {
std::fs::create_dir_all(dir).map_err(|e| format!("{}: {e}", dir.display()))?;
write(
&dir.join("Bluefile"),
&BIDAMA_BLUEFILE.replace("__NAME__", name),
)?;
write(&dir.join(format!("{name}.b")), BIDAMA)?;
lock(dir)
}
fn bidama_name(name: &str) -> Result<String, String> {
if name == blue_lang_syntax::BUILTIN_QUALIFIER {
return Err(format!(
"`{name}` cannot name a bidama: `{name}::` names the builtins"
));
}
if blue_lang_syntax::qualified(&blue_lang_syntax::qualify(name, "x")).is_none() {
return Err(format!(
"`{name}` cannot name a bidama: use one lowercase identifier, such as `{}`",
name.replace(['-', '.'], "")
));
}
if blue_lang_pkg::embedded::Standard.has(name) {
return Err(format!(
"`{name}` is a standard bidama already; a package of that name would shadow it"
));
}
Ok(name.to_string())
}
fn lock(dir: &Path) -> Result<(), String> {
let lock = blue_lang_pkg::lock::lock_dir(dir, &crate::prefetch::NixPrefetch)
.map_err(|e| e.to_string())?;
let text = blue_lang_pkg::lock::render(&lock).map_err(|e| e.to_string())?;
write(&dir.join(blue_lang_pkg::lock::LOCK_FILE), &text)
}
fn write(path: &Path, text: &str) -> Result<(), String> {
let text = if path.extension().is_some_and(|e| e == "b") {
blue_lang_fmt::format_source_lossless(text)
.map_err(|e| format!("{}: {e}", path.display()))?
} else {
text.to_string()
};
std::fs::write(path, text).map_err(|e| format!("{}: {e}", path.display()))
}
#[cfg(unix)]
fn executable(path: &Path) -> Result<(), String> {
use std::os::unix::fs::PermissionsExt;
std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o755))
.map_err(|e| format!("{}: {e}", path.display()))
}
#[cfg(not(unix))]
fn executable(_path: &Path) -> Result<(), String> {
Ok(())
}