Seb: Software Engineering / Dev Ops at the Hammer Lab.
`awk | wc`
, …).→ Make composable tools that allow people to setup/monitor/clean-up their own infrastructure.
(and it's more fun, and a better use of software people's time)
Unix.execve
It always looks simple at first …
Unix.execv "/usr/bin/apt-get" [| "apt-get";"install"; "-y"; "postgresql" |]
let cmd =
["apt-get";"install"; "-y"; "postgresql"]
|> List.map ~f:Filename.quote
|> String.concat ~sep:" "
in
Unix.execv "/usr/bin/ssh" [| "ssh"; host_info ; cmd |]
Who failed? ssh
or apt-get
?
Everybody ends-up reading some Stack-overflow answer
It's all strings after all:
gcloud compute create
deprecates the already dysfunctional --wait
option
sudo
in some Debian version erases new lines …
0.0.0
Language.t
is a 30+ entry GADT.
to_string
/of_string
+ (very) basic lists.if-then-else
, loops.exec
.let username_trimmed : string t =
(* The usual shell-pipe operator is ||>,
output_as_string takes stdout from a unit t as a string t. *)
(exec ["whoami"] ||> exec ["tr"; "-d"; "\\n"]) |> output_as_string
with_failwith (fun error_function ->
let get_user = (* the contents of `$USER`: *) getenv (string "USER") in
(* The operator `=$=` is `string t` equality, it returns a `bool t` that
we can use with `if_seq`: *)
if_seq
(get_user =$= username_trimmed)
~t:[ (* more commands *) ]
~e:[
(* `$USER` is different from `whoami`, system is broken,
we exit using the failwith funtion: *)
error_function
~message:(string "I'm dying") ~return:(int 1)
])
let cli_spec =
Command_line.Arg.(
string
~doc:"The URL to the stuff" ["-u"; "--url"]
~default:no_value
& flag ["-c"; "--all-in-tmp"] ~doc:"Do everything in the temp-dir"
& string ["-f"; "--local-filename"]
~doc:"Override the downloaded file-name"
~default:no_value
& string ["-t"; "--tmp-dir"]
~doc:"Use <dir> as temp-dir"
~default:(Genspio.EDSL.string "/tmp/genspio-downloader-tmpdir")
& usage "Download archives and decrypt/unarchive them.\n\
./downloader -u URL [-c] [-f <file>] [-t <tmpdir>]"
) in
Command_line.parse cli_spec
begin fun ~anon url all_in_tmp filename_ov tmp_dir ->
let on_stdin_lines ~body =
let fresh =
sprintf "var_%d_%s" Random.(int 10_000)
(Genspio.Language.to_one_liner (body (string "bouh"))
|> Digest.string |> Digest.to_hex) in
loop_while (exec ["read"; "-r"; fresh] |> succeeds)
~body:(seq [
exec ["export"; fresh];
body (getenv (string fresh));
])
(* ... *)
exec ["ldd"; exe]
||> exec ["awk"; "{ if ( $2 ~ /=>/ ) { print $3 } else { print $1 } }"]
||> on_stdin_lines begin fun line ->
seq [
call [string "printf"; string "Line %s\\n"; line];
call [string "cp"; line; string ("/tmp" // basename)];
]
end
That's when “crazy” really means “insane.”
| Output_as_string e ->
sprintf "\"$( { %s ; } | od -t o1 -An -v | tr -d ' \\n' )\"" (continue e)
Vs
let expand_octal s =
sprintf
{sh| printf -- "$(printf -- '%%s' %s | sed -e 's/\(.\{3\}\)/\\\1/g')" |sh}
s in
let to_argument varprefix =
let argument ?declaration ?variable_name argument =
(* ... *)
function
| `String (Literal (Literal.String s)) when Literal.String.easy_to_escape s ->
argument (Filename.quote s)
| `String (Literal (Literal.String s)) when
Literal.String.impossible_to_escape_for_variable s ->
ksprintf failwith "to_shell: sorry literal %S is impossible to \
escape as `exec` argument" s
| `String v ->
let variable_name = Unique_name.variable varprefix in
let declaration =
sprintf "%s=$(%s; printf 'x')" variable_name (continue v |> expand_octal) in
argument ~variable_name ~declaration
(sprintf "\"${%s%%?}\"" variable_name)
Future work: 2 string types …
In the beginning there was UNIX …
#include <stdio.h>
int main (int argc, char *argv[])
{
/* Insert VULN Here */
}
Test tries all the shells it knows about on the current host:
Summary:
* Test "dash" (`'dash' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 0 / 190 failures
- time: 13.31 s.
- version: `"Version: 0.5.8-2.1ubuntu2"`.
* Test "bash" (`'bash' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 0 / 190 failures
- time: 23.37 s.
- version: `"GNU bash, version 4.3.46(1)-release (x86_64-pc-linux-gnu)"`.
* Test "sh" (`'sh' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 0 / 190 failures
- time: 13.59 s.
- version: `""`.
* Test "busybox" (`'busybox' 'ash' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 0 / 190 failures
- time: 8.80 s.
- version: `"BusyBox v1.22.1 (Ubuntu 1:1.22.0-15ubuntu1) multi-call binary."`.
* Test "ksh" (`'ksh' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 20 / 190 failures
- time: 14.78 s.
- version: `"version sh (AT&T Research) 93u+ 2012-08-01"`.
- Cf. `/tmp/genspio-test-ksh-failures.txt`.
* Test "mksh" (`'mksh' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 2 / 190 failures
- time: 25.56 s.
- version: `"Version: 52c-2"`.
- Cf. `/tmp/genspio-test-mksh-failures.txt`.
* Test "posh" (`'posh' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 2 / 190 failures
- time: 24.40 s.
- version: `"Version: 0.12.6"`.
- Cf. `/tmp/genspio-test-posh-failures.txt`.
* Test "zsh" (`'zsh' '-x' '-c' '<command>' '--' '<arg1>' '<arg2>' '<arg-n>'`):
- 20 / 190 failures
- time: 17.94 s.
- version: `"zsh 5.1.1 (x86_64-ubuntu-linux-gnu)"`.
- Cf. `/tmp/genspio-test-zsh-failures.txt`.
All “known” shells were tested ☺
--------------------------------------------------------------------------------
export add_shells="
Freebsd-gcloud, escape, <cmd>,
printf '%s' <cmd> | ssh -i ~/.ssh/google_compute_engine $(freebsd_ip_address) 'sh -x'
"
export only_dash=true # We don't run all the other local tests this time
export single_test_timeout=50
_build/src/test/genspio-test.byte
We get the usual report:
* Test "Freebsd-gcloud" (`printf '%s' 'askjdeidjiedjjjdjekjdeijjjidejdejlksi () { <command> ; } ; askjdeidjiedjjjdjekjdeijjjidejdejlksi '\''<arg1>'\'' '\''<arg2>'\'' '\''<arg-n>'\''' | ssh -i ~/.ssh/google_compute_engine 42.42.42.42 'sh -x'`):
- 0 / 190 failures
- time: 165.19 s.
- version: `"Command-line"`.
qemu-system-arm -M realview-pbx-a9 -m 1024M \
-kernel openwrt-realview-vmlinux.elf \
-net nic \
-net user,hostfwd=tcp::10022-:22 \
-nographic \
-sd openwrt-realview-sdcard.img \
-append "console=ttyAMA0 verbose debug root=/dev/mmcblk0p1"
root@OpenWrt:/# df -h
Filesystem Size Used Available Use% Mounted on
/dev/root 46.5M 2.9M 42.7M 6% /
tmpfs 378.1M 612.0K 377.5M 0% /tmp
tmpfs 512.0K 0 512.0K 0% /dev
* Test "OpenWRT-qemu-arm" (`printf '%s' 'askjdeidjiedjjjdjekjdeijjjidejdejlksi () { <command> ; } ; askjdeidjiedjjjdjekjdeijjjidejdejlksi '\''<arg1>'\'' '\''<arg2>'\'' '\''<arg-n>'\''' | ssh -oStrictHostKeyChecking=no -p 10022 root@localhost 'sh -x'`):
- 0 / 190 failures
- time: 800.90 s.
- version: `"Command-line"`.
For a given shell
, trying:
$shell -c ' exec 4>&3 ; echo "Exec-returns: $?"' ; echo "Shell-returns: $?"
The POSIX ones:
shell=dash
, shell=sh
, shell='busbox ash'
: Shell-returns: 2
shell=ksh
, shell=mksh
: Shell-returns: 1
The non-POSIX ones:
shell=bash
, shell=zsh
: Exec-returns: 1 Shell-returns: 0
→ even bash not always POSIX.
Real-world example.
docker-compose
.Simple “build-stuff” EDSL, compiled to a Makefile
+ scripts:
.tgz
(e.g. an executable + output of ldd
).#HackyExample
#WIP
https://gitlab.com/smondet/habust
"deb-arm-emacs", Build_definition.Construct.(
within (qemu_arm debian_wheezy) [
exec ["apt-get"; "update"];
exec ["apt-get"; "install"; "--yes"; "emacs23"];
get_executable "/usr/bin/emacs" ~dest:"emacs-armv7l-bin";
]
);
"deb-arm-ketrew", Build_definition.Construct.(
(* ... *)
within (qemu_arm debian_wheezy) [
ensure (executables_available ["unzip"; "gcc"; "make"; "git"]) [
(* ... *)
];
ensure (md5 opam_bin (`Contains "46e25cc5b26")) [
["wget"; opam_arm7l_url; "-O"; opam_bin];
];
(* ... *)
ensure (returns_zero @@ opam_exec ["vidimetro"; "--version"]) [
(* opam_exec ["opam"; "remove"; "--yes"; "ocamlfind"]; *)
pin_github "ketrew";
opam_exec ["opam"; "depext"; "--yes"; "ketrew"];
opam_install ["ketrew"];
];
get_executable (strf "/opam-root/%s/bin/ketrew" ocaml_version) ~dest:"ketrew-armv7l-bin";
(* ... *)
Could not get the graphical apps I wanted to show:
#=== ERROR while installing uri.1.9.4 ================================#
# opam-version 1.2.2
# os linux
# command jbuilder build -p uri -j 4
# path /opam-root/4.03.0/build/uri.1.9.4
# compiler 4.03.0
# [...]
### stderr ###
# [...]
# /tmp/camlasm6e1b43.s:445651: Error: offset out of range
# /tmp/camlasm6e1b43.s:445679: Error: offset out of range
# /tmp/camlasm6e1b43.s:445687: Error: offset out of range
# File "etc/uri_services_full.ml", line 1:
# Error: Assembler error, input left in file /tmp/camlasm6e1b43.s
Questions?