···2121 match Sys.argv.(1) with
2222 | "shelter" ->
2323 let dir = state_dir env#fs "shelter" in
2424- Shelter.main env#clock env#process_mgr dir
2424+ Shelter.main env#fs env#clock env#process_mgr dir
2525 | _ | (exception Invalid_argument _) ->
2626 let dir = state_dir env#fs "passthrough" in
2727- Pass.main env#clock env#process_mgr dir
2727+ Pass.main env#fs env#clock env#process_mgr dir
+10-10
src/lib/cshell.ml
···44module Make (H : History.S) (Engine : Engine.S with type entry = H.t) = struct
55 module Store = Irmin_fs_unix.KV.Make (H)
6677- let run clock proc store =
77+ let run fs clock proc store =
88 let store = History.Store ((module Store), store) in
99- Engine.init store;
1010- let rec loop store exit_code =
99+ let initial_ctx = Engine.init fs proc store in
1010+ let rec loop store ctx exit_code =
1111 let prompt = Engine.prompt exit_code store in
1212 match LNoise.linenoise prompt with
1313 | None -> ()
1414 | Some input -> (
1515 let action = Engine.action_of_command input in
1616- match Engine.run clock proc store action with
1616+ match Engine.run fs clock proc (store, ctx) action with
1717 | Error (Eio.Process.Child_error exit_code) ->
1818 Fmt.epr "%a\n%!" Eio.Process.pp_status exit_code;
1919- loop store exit_code
1919+ loop store ctx exit_code
2020 | Error (Eio.Process.Executable_not_found m) ->
2121 Fmt.epr "cshell: excutable not found %s\n%!" m;
2222- loop store (`Exited 127)
2323- | Ok store -> loop store (`Exited 0))
2222+ loop store ctx (`Exited 127)
2323+ | Ok (store, ctx) -> loop store ctx (`Exited 0))
2424 in
2525- loop store (`Exited 0)
2525+ loop store initial_ctx (`Exited 0)
26262727- let main clock proc directory =
2727+ let main fs clock proc directory =
2828 Irmin_fs.run directory @@ fun () ->
2929 let conf = Irmin_fs.config (Eio.Path.native_exn directory) in
3030 let repo = Store.Repo.v conf in
3131 let store = Store.main repo in
3232- run clock proc store
3232+ run fs clock proc store
3333end
+12-3
src/lib/engine.ml
···7788 type entry
991010- val init : entry History.t -> unit
1010+ type ctx
1111+ (** A context that is not persisted, but is passed through each loop of the
1212+ shell *)
1313+1414+ val init :
1515+ _ Eio.Path.t ->
1616+ Eio_unix.Process.mgr_ty Eio_unix.Process.mgr ->
1717+ entry History.t ->
1818+ ctx
1119 (** [init store] will be called before entering the shell loop. You may wish
1220 to setup history completions etc. with LNoise. *)
13211422 val run :
2323+ _ Eio.Path.t ->
1524 _ Eio.Time.clock ->
1625 Eio_unix.Process.mgr_ty Eio_unix.Process.mgr ->
1717- entry History.t ->
2626+ entry History.t * ctx ->
1827 action ->
1919- (entry History.t, Eio.Process.error) result
2828+ (entry History.t * ctx, Eio.Process.error) result
2029 (** [run history action] runs the action in [history]. Return a new [history]
2130 that can be persisted *)
2231
+9-5
src/lib/passthrough/cshell_passthrough.ml
···1616let history_key = [ "history" ]
1717let key () = history_key @ [ string_of_float @@ Unix.gettimeofday () ]
18181919-let init (Cshell.History.Store ((module S), store) : entry Cshell.History.t) =
1919+type ctx = unit
2020+2121+let init _ _ (Cshell.History.Store ((module S), store) : entry Cshell.History.t)
2222+ =
2023 match S.list store history_key with
2124 | [] -> ()
2225 | xs ->
···3033 in
3134 List.iter (fun v -> LNoise.history_add v |> ignore) entries
32353333-let run clock proc
3434- ((Cshell.History.Store ((module S), store) : entry Cshell.History.t) as
3535- full_store) (Exec command) =
3636+let run _fs clock proc
3737+ ( ((Cshell.History.Store ((module S), store) : entry Cshell.History.t) as
3838+ full_store),
3939+ () ) (Exec command) =
3640 let info () =
3741 S.Info.v ~message:"cshell" (Eio.Time.now clock |> Int64.of_float)
3842 in
···4751 if res = `Exited 0 then (
4852 S.set_exn ~info store (key ()) command;
4953 let _ : (unit, string) result = LNoise.history_add command in
5050- Ok full_store)
5454+ Ok (full_store, ()))
5155 else Error (Eio.Process.Child_error res)
5256 with Eio.Exn.Io (Eio.Process.E e, _) -> Error e
+39
src/lib/shelter/diff.ml
···11+type diff =
22+ | Modified of string
33+ | Created of string
44+ | Renamed of string * string
55+ | Removed of string
66+[@@deriving repr]
77+88+type t = diff list [@@deriving repr]
99+1010+let truncate_path s =
1111+ match Astring.String.cut ~sep:"rootfs" s with Some (_, p) -> p | None -> s
1212+1313+let parse_row = function
1414+ | [ "M"; s ] ->
1515+ let path = truncate_path s in
1616+ if String.equal path "" then None else Some (Modified path)
1717+ | [ "+"; s ] ->
1818+ let path = truncate_path s in
1919+ if String.equal path "" then None else Some (Created path)
2020+ | [ "R"; a; b ] ->
2121+ let a_path = truncate_path a in
2222+ let b_path = truncate_path b in
2323+ Some (Renamed (a_path, b_path))
2424+ | [ "-"; s ] ->
2525+ let path = truncate_path s in
2626+ if String.equal path "" then None else Some (Removed path)
2727+ | s ->
2828+ Fmt.invalid_arg "Unknown ZFS diff: %a"
2929+ (Fmt.list ~sep:Fmt.comma Fmt.string)
3030+ s
3131+3232+let of_zfs s : t =
3333+ let lines = String.split_on_char '\n' s in
3434+ let tsv =
3535+ List.map (String.split_on_char '\t') lines
3636+ |> List.map (List.filter (fun s -> not (String.equal "" s)))
3737+ |> List.filter (function [] -> false | _ -> true)
3838+ in
3939+ List.filter_map parse_row tsv
···11+let ( / ) = Eio.Path.( / )
22+33+let get_image ~dir ~proc image =
44+ let container_id =
55+ Eio.Process.parse_out proc Eio.Buf_read.take_all
66+ [ "docker"; "run"; "-d"; image ]
77+ |> String.trim
88+ in
99+ let tar = image ^ ".tar.gz" in
1010+ let dir_s = Eio.Path.native_exn dir in
1111+ let () =
1212+ Eio.Process.run proc
1313+ [ "docker"; "export"; container_id; "-o"; Filename.concat dir_s tar ]
1414+ in
1515+ Eio.Path.mkdir ~perm:0o777 (dir / "rootfs");
1616+ let () =
1717+ Eio.Process.run proc
1818+ [
1919+ "tar";
2020+ "-xf";
2121+ Filename.concat dir_s "alpine.tar.gz";
2222+ "-C";
2323+ Filename.concat dir_s "rootfs";
2424+ ]
2525+ in
2626+ Filename.concat dir_s "rootfs"
+173-56
src/lib/shelter/shelter.ml
···11open Eio
22+module Store = Store
33+module H = Cshell.History
2433-module Build = struct
44- type cid = Cid.t
55+module History = struct
66+ type mode = Void.mode
5766- let cid_of_string s =
77- match Cid.of_string s with
88- | Ok v -> v
99- | Error (`Msg m) -> failwith m
1010- | Error (`Unsupported _) -> failwith "unsupported cid"
88+ let mode_t =
99+ Repr.map Repr.string
1010+ (function
1111+ | "R" -> Void.R | "RW" -> Void.RW | _ -> failwith "Malformed Void.mode")
1212+ (function Void.R -> "R" | Void.RW -> "RW")
11131212- let cid_t = Repr.map Repr.string cid_of_string Cid.to_string
1313-1414- type t = Image of string | Build of cid [@@deriving repr]
1515-end
1616-1717-type mode = R | RW [@@deriving repr]
1818-1919-module History = struct
2020- type t = { mode : mode; build : Build.t; args : string list }
1414+ type t = {
1515+ mode : mode;
1616+ build : Store.Build.t;
1717+ args : string list;
1818+ time : int64;
1919+ diff : Diff.t;
2020+ }
2121 [@@deriving repr]
22222323 let merge = Irmin.Merge.(default (Repr.option t))
···2626type entry = History.t
27272828type action =
2929- | Set_mode of mode
2929+ | Set_mode of History.mode
3030 | Set_session of string
3131 | Exec of string list
3232 | Info
3333+ | Undo
3434+ | Fork of string
3535+ | Replay of string
3336 | Unknown of string list
3737+ | History
3438[@@deriving repr]
35393640let split_and_remove_empty s =
···3943let action = action_t
40444145let shelter_action = function
4242- | "set" :: "mode" :: [ "r" ] -> Set_mode R
4343- | "set" :: "mode" :: [ "rw" ] -> Set_mode R
4646+ | "mode" :: [ "r" ] -> Set_mode R
4747+ | "mode" :: [ "rw" ] -> Set_mode R
4448 | "session" :: [ m ] -> Set_session m
4949+ | "fork" :: [ m ] -> Fork m
5050+ | "replay" :: [ onto ] -> Replay onto
4551 | [ "info" ] -> Info
5252+ | [ "undo" ] -> Undo
5353+ | [ "history" ] -> History
4654 | other -> Unknown other
47554856let action_of_command cmd =
···5462let history_key = [ "history" ]
5563let key clock = history_key @ [ string_of_float @@ Eio.Time.now clock ]
56645757-let list (Cshell.History.Store ((module S), store) : entry Cshell.History.t) =
6565+let list (H.Store ((module S), store) : entry H.t) =
5866 match S.list store history_key with
5967 | [] -> []
6068 | xs ->
···6472 | [] -> List.rev acc
6573 in
6674 loop [] (List.map (fun (v, tree) -> (v, S.Tree.to_concrete tree)) xs)
6767- |> List.stable_sort (fun (s1, _) (s2, _) -> String.compare s1 s2)
7575+ |> List.stable_sort (fun (s1, _) (s2, _) ->
7676+ Float.compare (Float.of_string s1) (Float.of_string s2))
6877 |> List.rev
69787079let with_latest ~default s f =
···72817382let text c = Fmt.(styled (`Fg c) string)
74837575-let sessions (Cshell.History.Store ((module S), store) : entry Cshell.History.t)
7676- =
8484+let sessions (H.Store ((module S), store) : entry H.t) =
7785 S.Branch.list (S.repo store)
78867979-let commit ~message clock
8080- (Cshell.History.Store ((module S), store) : entry Cshell.History.t) v =
8787+let commit ~message clock (H.Store ((module S), store) : entry H.t) v =
8188 let info () = S.Info.v ~message (Eio.Time.now clock |> Int64.of_float) in
8289 S.set_exn ~info store (key clock) v
83908484-let which_branch
8585- ((Cshell.History.Store ((module S), session) : entry Cshell.History.t) as s)
8686- =
9191+let which_branch ((H.Store ((module S), session) : entry H.t) as s) =
8792 let branches = sessions s in
8893 let repo = S.repo session in
8994 let heads = List.map (fun b -> (S.Branch.find repo b, b)) branches in
9095 let head = S.Head.find session in
9196 List.assoc_opt head heads
92979393-let prompt status
9494- ((Cshell.History.Store ((module S), _session) : entry Cshell.History.t) as
9595- store) =
9898+(* Reset the head of the current session by one commit *)
9999+let reset_hard ((H.Store ((module S), session) : entry H.t) as s) =
100100+ match
101101+ List.filter_map (S.Commit.of_hash (S.repo session))
102102+ @@ S.Commit.parents (S.Head.get session)
103103+ with
104104+ | [] -> s
105105+ | p :: _ ->
106106+ S.Head.set session p;
107107+ s
108108+109109+(* Fork a new session from an existing one *)
110110+let fork (H.Store ((module S), session) : entry H.t) new_branch =
111111+ let repo = S.repo session in
112112+ match (S.Head.find session, S.Branch.find repo new_branch) with
113113+ | _, Some _ ->
114114+ Error (new_branch ^ " already exists, try @ session " ^ new_branch)
115115+ | None, _ -> Error "Current branch needs at least one commit"
116116+ | Some commit, None ->
117117+ let new_store = S.of_branch (S.repo session) new_branch in
118118+ S.Branch.set repo new_branch commit;
119119+ let store = H.Store ((module S), new_store) in
120120+ Ok store
121121+122122+(* Fork a new session from an existing one *)
123123+let display_history (H.Store ((module S), session) : entry H.t) =
124124+ let history = S.history ~depth:max_int session in
125125+ let content c =
126126+ H.Store ((module S), S.of_commit c) |> list |> List.hd |> snd
127127+ in
128128+ let pp_diff fmt d =
129129+ if d = [] then () else Fmt.pf fmt "\n %a" (Repr.pp Diff.t) d
130130+ in
131131+ let pp_entry fmt (e : entry) =
132132+ Fmt.pf fmt "%-10s %s%a"
133133+ Fmt.(str "%a" (styled (`Fg `Yellow) uint64_ns_span) e.time)
134134+ (String.concat " " e.args) pp_diff e.diff
135135+ in
136136+ let linearize =
137137+ S.History.fold_vertex (fun c v -> content c :: v) history [] |> List.rev
138138+ in
139139+ List.iter (fun c -> Fmt.pr "%a\n%!" pp_entry c) linearize
140140+141141+let prompt status ((H.Store ((module S), _session) : entry H.t) as store) =
96142 let sesh = Option.value ~default:"main" (which_branch store) in
97143 let prompt () =
98144 Fmt.(styled (`Fg `Yellow) string) Format.str_formatter "shelter> ";
···112158 in
113159 with_latest store ~default:prompt prompt_entry
114160115115-let init s =
161161+type ctx = Store.t
162162+163163+let init fs proc s =
164164+ let store = Store.init fs proc "test-pool" in
116165 List.iter
117166 (fun (_, { History.args; _ }) ->
118167 LNoise.history_add (String.concat " " args) |> ignore)
119119- (list s)
168168+ (list s);
169169+ store
120170121121-let run clock proc
122122- ((Cshell.History.Store ((module S), store) : entry Cshell.History.t) as s) =
123123- function
171171+let run _fs clock _proc (((H.Store ((module S), store) : entry H.t) as s), ctx)
172172+ = function
124173 | Set_mode mode ->
125125- with_latest ~default:(fun _ -> Ok s) s @@ fun (_, entry) ->
174174+ with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
126175 commit ~message:"mode change" clock s { entry with mode };
127127- Ok s
176176+ Ok (s, ctx)
128177 | Set_session m ->
129129- with_latest ~default:(fun _ -> Ok s) s @@ fun (_, entry) ->
178178+ with_latest ~default:(fun _ -> Ok (s, ctx)) s @@ fun (_, entry) ->
130179 let new_store = S.of_branch (S.repo store) m in
131131- let new_full_store = Cshell.History.Store ((module S), new_store) in
180180+ let new_full_store = H.Store ((module S), new_store) in
132181 commit ~message:"new session" clock new_full_store entry;
133133- Ok new_full_store
182182+ Ok (new_full_store, ctx)
134183 | Unknown args ->
135184 Fmt.epr "%a: %s\n%!" (text `Red) "Unknown Shelter Action"
136185 (String.concat " " args);
137137- Ok s
186186+ Ok (s, ctx)
138187 | Info ->
139188 let sessions = sessions s in
140189 let sesh = Option.value ~default:"main" (which_branch s) in
···144193 in
145194 let commits =
146195 S.History.fold_vertex
147147- (fun commit acc ->
196196+ (fun commit acc ->
148197 let info = S.Commit.info commit |> S.Info.message in
149198 let hash = S.Commit.hash commit |> Repr.to_string S.Hash.t in
150150- ((String.sub hash 0 7), info) :: acc)
199199+ (String.sub hash 0 7, info) :: acc)
151200 history []
152201 in
153153- Fmt.pr "Sessions: %a\nCurrent: %a\nCommits:@. %a\n%!"
202202+ let latest =
203203+ with_latest
204204+ ~default:(fun () -> None)
205205+ s
206206+ (fun (_, e) -> Some (Repr.to_string Store.Build.t e.build))
207207+ in
208208+ Fmt.pr "Sessions: %a\nCurrent: %a\nHash: %a\nCommits:@. %a\n%!"
154209 Fmt.(list ~sep:(Fmt.any ", ") string)
155210 sessions (text `Green) sesh
211211+ Fmt.(option string)
212212+ latest
156213 Fmt.(vbox ~indent:2 @@ list pp_commit)
157214 commits;
158158- Ok s
159159- | Exec [] -> Ok s
215215+ Ok (s, ctx)
216216+ | Exec [] -> Ok (s, ctx)
217217+ | Undo -> Ok (reset_hard s, ctx)
218218+ | Fork new_branch -> (
219219+ match fork s new_branch with
220220+ | Error err ->
221221+ Fmt.pr "[fork]: %a\n%!" (text `Red) err;
222222+ Ok (s, ctx)
223223+ | Ok store -> Ok (store, ctx))
224224+ | Replay _ -> Ok (s, ctx)
225225+ | History ->
226226+ display_history s;
227227+ Ok (s, ctx)
160228 | Exec command -> (
161161- Switch.run @@ fun sw ->
229229+ let entry =
230230+ with_latest
231231+ ~default:(fun () ->
232232+ History.
233233+ {
234234+ mode = Void.RW;
235235+ build = Store.Build.Image "alpine";
236236+ args = command;
237237+ time = 0L;
238238+ diff = [];
239239+ })
240240+ s
241241+ @@ fun (_, e) -> e
242242+ in
243243+ let build =
244244+ match entry.build with
245245+ | Store.Build.Image img -> Store.fetch ctx img
246246+ | Store.Build.Build cid -> cid
247247+ in
248248+ let hash_entry = { entry with build = Build build; args = command } in
249249+ let new_cid = Store.cid (Repr.to_string History.t hash_entry) in
250250+ let with_rootfs fn =
251251+ if entry.mode = R then (Store.Run.with_build ctx build fn, [])
252252+ else Store.Run.with_clone ctx ~src:build new_cid fn
253253+ in
162254 try
163163- let proc = Eio.Process.spawn ~sw proc [ "bash"; "-c"; String.concat " " command ] in
164164- let res = Eio.Process.await proc in
165165- if res = `Exited 0 then (
166166- let entry =
167167- History.{ mode = RW; build = Image "TODO"; args = command }
255255+ let new_entry, diff =
256256+ with_rootfs @@ fun rootfs ->
257257+ let void =
258258+ Void.empty
259259+ |> Void.rootfs ~mode:entry.mode rootfs
260260+ |> Void.exec [ "/bin/ash"; "-c"; String.concat " " command ]
261261+ in
262262+ Switch.run @@ fun sw ->
263263+ let start = Mtime_clock.now () in
264264+ let proc = Void.spawn ~sw void in
265265+ let res =
266266+ Void.exit_status proc |> Eio.Promise.await |> Void.to_eio_status
168267 in
169169- commit ~message:("exec " ^ (String.concat " " command)) clock s entry;
268268+ let stop = Mtime_clock.now () in
269269+ let span = Mtime.span start stop in
270270+ let time = Mtime.Span.to_uint64_ns span in
271271+ (* Add command to history regardless of exit status *)
170272 let _ : (unit, string) result =
171273 LNoise.history_add (String.concat " " command)
172274 in
173173- Ok s)
174174- else Error (Eio.Process.Child_error res)
275275+ if res = `Exited 0 then
276276+ if entry.mode = RW then
277277+ Ok { hash_entry with build = Build new_cid; time }
278278+ else Ok hash_entry
279279+ else Error (Eio.Process.Child_error res)
280280+ in
281281+ match new_entry with
282282+ | Error e -> Error e
283283+ | Ok entry ->
284284+ (* Set diff *)
285285+ let entry = { entry with diff } in
286286+ (* Commit if RW *)
287287+ if entry.mode = RW then
288288+ commit
289289+ ~message:("exec " ^ String.concat " " command)
290290+ clock s entry;
291291+ Ok (s, ctx)
175292 with Eio.Exn.Io (Eio.Process.E e, _) -> Error e)
+8-6
src/lib/shelter/shelter.mli
···11-module Build : sig
22- type t = Image of string | Build of Cid.t [@@deriving repr]
33-end
44-55-type mode = R | RW
11+module Store = Store
6273module History : sig
88- type t = { mode : mode; build : Build.t; args : string list }
44+ type t = {
55+ mode : Void.mode;
66+ build : Store.Build.t;
77+ args : string list;
88+ time : int64;
99+ diff : Diff.t;
1010+ }
911 [@@deriving repr]
10121113 include Irmin.Contents.S with type t := t
+158
src/lib/shelter/store.ml
···11+(* A store a bit like OBuilder's but a little simplified
22+ for our purposes *)
33+module Build = struct
44+ type cid = Cid.t
55+66+ let cid_of_string s =
77+ match Cid.of_string s with
88+ | Ok v -> v
99+ | Error (`Msg m) -> failwith m
1010+ | Error (`Unsupported _) -> failwith "unsupported cid"
1111+1212+ let cid_t = Repr.map Repr.string cid_of_string Cid.to_string
1313+1414+ type t = Image of string | Build of cid [@@deriving repr]
1515+end
1616+1717+type path = string list
1818+1919+type t = {
2020+ fs : Eio.Fs.dir_ty Eio.Path.t;
2121+ proc : Eio_unix.Process.mgr_ty Eio_unix.Process.mgr;
2222+ zfs : Zfs.Handle.t;
2323+ pool : string;
2424+}
2525+2626+module Datasets : sig
2727+ type dataset = private string
2828+ type snapshot = private string
2929+3030+ val builds : string -> dataset
3131+ val build : string -> string -> dataset
3232+ val snapshot : dataset -> snapshot
3333+end = struct
3434+ type dataset = string
3535+ type snapshot = string
3636+3737+ let ( / ) a b = a ^ "/" ^ b
3838+ let builds pool : dataset = pool / "builds"
3939+ let build pool path : dataset = builds pool / path
4040+ let snapshot ds = ds ^ "@snappy"
4141+end
4242+4343+let with_dataset ?(typ = Zfs.Types.filesystem) t dataset f =
4444+ let exists = Zfs.exists t.zfs (dataset :> string) typ in
4545+ if not exists then Zfs.create t.zfs dataset typ;
4646+ let dataset = Zfs.open_ t.zfs dataset typ in
4747+ Fun.protect ~finally:(fun () -> Zfs.close dataset) (fun () -> f dataset)
4848+4949+let mount_dataset ?(typ = Zfs.Types.dataset) t (dataset : Datasets.dataset) =
5050+ match Zfs.is_mounted t.zfs (dataset :> string) with
5151+ | Some _ -> ()
5252+ | None -> with_dataset ~typ t (dataset :> string) @@ fun d -> Zfs.mount d
5353+5454+let unmount_dataset t (dataset : Datasets.dataset) =
5555+ match Zfs.is_mounted t.zfs (dataset :> string) with
5656+ | None -> ()
5757+ | Some _ ->
5858+ with_dataset t (dataset :> string) @@ fun d ->
5959+ let _todo () = Zfs.unmount d in
6060+ ()
6161+6262+let create_dataset t (dataset : Datasets.dataset) =
6363+ with_dataset t (dataset :> string) (fun _ -> ())
6464+6565+let create_and_mount t (dataset : Datasets.dataset) =
6666+ create_dataset t dataset;
6767+ mount_dataset t dataset
6868+6969+let init fs proc pool =
7070+ let zfs = Zfs.init () in
7171+ Zfs.debug zfs true;
7272+ let t =
7373+ {
7474+ fs :> Eio.Fs.dir_ty Eio.Path.t;
7575+ proc :> Eio_unix.Process.mgr_ty Eio_unix.Process.mgr;
7676+ zfs;
7777+ pool;
7878+ }
7979+ in
8080+ create_and_mount t (Datasets.builds t.pool);
8181+ t
8282+8383+let snapshot t (snap : Datasets.snapshot) =
8484+ let exists = Zfs.exists t.zfs (snap :> string) Zfs.Types.snapshot in
8585+ if not exists then Zfs.snapshot t.zfs (snap :> string) true
8686+8787+let clone t (snap : Datasets.snapshot) (tgt : Datasets.dataset) =
8888+ with_dataset ~typ:Zfs.Types.snapshot t (snap :> string) @@ fun src ->
8989+ Zfs.clone src (tgt :> string)
9090+9191+let read_all fd =
9292+ let buf = Buffer.create 128 in
9393+ let bytes = Bytes.create 4096 in
9494+ let rec loop () =
9595+ match Unix.read fd bytes 0 4096 with
9696+ | 0 | (exception End_of_file) -> Buffer.contents buf
9797+ | n ->
9898+ Buffer.add_bytes buf (Bytes.sub bytes 0 n);
9999+ loop ()
100100+ in
101101+ loop ()
102102+103103+let diff t (data : Datasets.snapshot) (snap : Datasets.snapshot) =
104104+ let data_fs =
105105+ String.sub (data :> string) 0 (String.index (data :> string) '@')
106106+ in
107107+ let zh = Zfs.open_ t.zfs data_fs Zfs.Types.filesystem in
108108+ let diff =
109109+ let r, w = Unix.pipe ~cloexec:false () in
110110+ try
111111+ Zfs.show_diff zh ~from_:(data :> string) ~to_:(snap :> string) w;
112112+ let f = read_all r in
113113+ Unix.close r;
114114+ f
115115+ with e ->
116116+ Unix.close r;
117117+ raise e
118118+ in
119119+ Zfs.close zh;
120120+ Diff.of_zfs diff
121121+122122+let cid s =
123123+ let hash =
124124+ Multihash_digestif.of_cstruct `Sha2_256 (Cstruct.of_string s)
125125+ |> Result.get_ok
126126+ in
127127+ Cid.v ~version:`Cidv1 ~base:`Base32 ~codec:`Raw ~hash
128128+129129+let fetch t image =
130130+ let cid = cid image in
131131+ let cids = cid |> Cid.to_string in
132132+ let dataset = Datasets.build t.pool cids in
133133+ let dir = Eio.Path.(t.fs / ("/" ^ (Datasets.build t.pool cids :> string))) in
134134+ if Zfs.exists t.zfs (dataset :> string) Zfs.Types.filesystem then cid
135135+ else (
136136+ create_and_mount t dataset;
137137+ let _dir : string = Fetch.get_image ~dir ~proc:t.proc image in
138138+ snapshot t (Datasets.snapshot dataset);
139139+ cid)
140140+141141+module Run = struct
142142+ let with_build t cid fn =
143143+ let ds = Datasets.build t.pool (Cid.to_string cid) in
144144+ Fun.protect ~finally:(fun () -> unmount_dataset t ds) @@ fun () ->
145145+ mount_dataset t ds;
146146+ fn ("/" ^ (ds :> string) ^ "/rootfs")
147147+148148+ let with_clone t ~src new_cid fn =
149149+ let ds = Datasets.build t.pool (Cid.to_string src) in
150150+ let tgt = Datasets.build t.pool (Cid.to_string new_cid) in
151151+ let src_snap = Datasets.snapshot ds in
152152+ let tgt_snap = Datasets.snapshot tgt in
153153+ clone t src_snap tgt;
154154+ let v = with_build t new_cid fn in
155155+ snapshot t tgt_snap;
156156+ let d = diff t src_snap tgt_snap in
157157+ (v, d)
158158+end
+33
vendor/ocaml-libbpf/.gitignore
···11+*.annot
22+*.cmo
33+*.cma
44+*.cmi
55+*.a
66+*.o
77+*.cmx
88+*.cmxs
99+*.cmxa
1010+1111+# ocamlbuild working directory
1212+_build/
1313+1414+# ocamlbuild targets
1515+*.byte
1616+*.native
1717+1818+# oasis generated files
1919+setup.data
2020+setup.log
2121+2222+# Merlin configuring file for Vim and Emacs
2323+.merlin
2424+2525+# Dune generated files
2626+*.install
2727+2828+# Local OPAM switch
2929+_opam/
3030+3131+# generated files
3232+*.txt
3333+*.bin
···11+## v0.1.1 (2024-07-05)
22+- Improve documentation
33+44+## v0.1.0 (2024-07-01)
55+- Initial release.
66+77+ `ocaml_libbpf`:
88+ - [supported](./supported.json) bindings
99+ - high level API's for open/load/attach/teardown
1010+1111+ `ocaml_libbpf_maps`:
1212+ - high level API's for BPF ring_buffer map
+15
vendor/ocaml-libbpf/LICENSE.md
···11+/*
22+ * Copyright (C) 2024 Lee Koon Wen
33+ *
44+ * Permission to use, copy, modify, and distribute this software for any
55+ * purpose with or without fee is hereby granted, provided that the above
66+ * copyright notice and this permission notice appear in all copies.
77+ *
88+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
99+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1010+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1111+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1212+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
1313+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
1414+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1515+ */
+220
vendor/ocaml-libbpf/README.md
···11+[](https://ocaml.ci.dev/github/koonwen/ocaml-libbpf)
22+- [API documentation](https://koonwen.github.io/ocaml-libbpf/)
33+44+# ocaml-libbpf
55+Libbpf C-bindings for loading eBPF ELF files into the kernel with OCaml.
66+77+Writing eBPF programs consist of two distinct parts. Implementing the
88+code that executes in-kernel **and** user-level code responsible for
99+loading/initializing/linking/teardown of the in-kernel code. This
1010+OCaml library provides the latter via binding the C
1111+[libbpf](https://github.com/libbpf/libbpf) library. It exposes both
1212+the raw low-level bindings as well as a set of high-level API's for
1313+handling your eBPF objects. As of now, the kernel part must still be
1414+written in [restricted
1515+C](https://stackoverflow.com/questions/57688344/what-is-not-allowed-in-restricted-c-for-ebpf)
1616+and compiled with llvm to eBPF bytecode.
1717+1818+The full API set of Libbpf is quite large, see [supported](supported.json) for the list
1919+of currently bound API's. Contributions are welcome.
2020+2121+### External dependencies
2222+ocaml-libbpf depends on the system package of `libbpf`.
2323+2424+# Usage
2525+> ⚠️ **Disambiguation:** The name of this repository and
2626+> references to it will be "ocaml-libbpf". However, the library's
2727+> entry module and package name is **Libbpf**. To install it, you
2828+> would use `opam install libbpf`. To access it's High-level API's use
2929+> `Libbpf.<api>`. To use the raw bindings, they are exposed in
3030+> `Libbpf.C.<api>` namespace.
3131+3232+See `examples` directory on how ocaml-libbpf can be used to load eBPF
3333+ELF files into the kernel and interact with the loaded kernel program.
3434+The eBPF kernel programs are defined in *.bpf.c source files and are
3535+compiled with clang as specified in the `dune` rules. ocaml-libbpf
3636+exposes some high-level API's exposed by the toplevel `Libbpf` module
3737+to make it easy to perform repetitive tasks such as
3838+open/load/linking/initializing/teardown of bpf programs.
3939+4040+To run these examples, clone this repository and set up the package with
4141+```bash
4242+git clone git@github.com:koonwen/ocaml-libbpf.git
4343+cd ocaml-libbpf
4444+opam install . --deps-only
4545+eval $(opam env)
4646+```
4747+4848+then run `make < minimal | kprobe | bootstrap | tc >` to try out the
4949+different bpf programs. These examples are all taken from
5050+[libbpf-bootstrap](https://github.com/libbpf/libbpf-bootstrap)
5151+repository and rewritten in OCaml.
5252+5353+### Open/Load/Link
5454+Now let's run through an example of how we would use
5555+ocaml-libbpf. This usage tutorial assumes some knowledge of how to
5656+write eBPF kernel programs in C compile them to ELF files. If not, you
5757+can check out this
5858+[resource](https://nakryiko.com/posts/libbpf-bootstrap/#the-bpf-side). ocaml-libbpf
5959+provides an easy API to install your eBPF program into the kernel. Say
6060+your eBPF kernel program looks like this where we print something
6161+whenever the syscall `write` event occurs. We also want to implement a
6262+filtering mechanism to only print on `write` calls for our process. To
6363+do this, we initialize a BPF array map with a single entry that works
6464+like a holder for our global variable. The BPF map is neccessary to
6565+because it allows us to communicate values between user and kernel
6666+space.
6767+6868+> The libbpf C library in fact already supports declarations of global
6969+> variables in the usual form with the ability to manage them in user
7070+> space. However for various technical reasons, ocaml-libbpf does not
7171+> enable that feature yet. So we use the old style of working with
7272+> global variables here.
7373+7474+```c
7575+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
7676+/* Copyright (c) 2020 Facebook */
7777+#include <linux/bpf.h>
7878+#include "bpf/bpf_helpers.h" /* This is from our libbpf library */
7979+8080+char LICENSE[] SEC("license") = "Dual BSD/GPL";
8181+8282+/* Globals implemented as an array */
8383+struct {
8484+ __uint(type, BPF_MAP_TYPE_ARRAY);
8585+ __uint(max_entries, 1);
8686+ __type(key, int);
8787+ __type(value, long);
8888+} globals SEC(".maps");
8989+9090+int my_pid_index = 0;
9191+9292+SEC("tp/syscalls/sys_enter_write")
9393+int handle_tp(void *ctx) {
9494+ int pid = bpf_get_current_pid_tgid() >> 32;
9595+9696+ long *my_pid;
9797+ my_pid = bpf_map_lookup_elem(&globals, &my_pid_index);
9898+ if (my_pid == NULL) {
9999+ bpf_printk("Error got NULL");
100100+ return 1;
101101+ };
102102+103103+ if (pid != *my_pid)
104104+ return 0;
105105+106106+ bpf_printk("Hello, BPF triggered from PID %d", pid);
107107+108108+ return 0;
109109+}
110110+111111+```
112112+113113+After compilation to eBPF ELF file as `minimal.o`. Users just need to
114114+provide the path to this ELF file along with the name of the program
115115+and optionally an initialization function. Note that the name of the
116116+program refers to the function identifier under the SEC(...)
117117+attribute, in this case it is "handle_tp".
118118+119119+```ocaml
120120+open Libbpf
121121+122122+let obj_path = "minimal.bpf.o"
123123+let program_names = [ "handle_tp" ]
124124+125125+let () =
126126+ with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
127127+ (fun obj link ->
128128+129129+ < user code to interact with bpf program running in kernel >
130130+131131+ )
132132+```
133133+134134+The API provided by ocaml-libbpf `with_bpf_object_open_load_link` is
135135+a context manager that ensures the proper cleanup of resources if a
136136+failure is encountered. Right now our loaded kernel program is
137137+attached to the kernel and then immediately unloaded, users are
138138+responsible for keeping the bpf program alive by looping within the
139139+function block.
140140+141141+> Users may also pin the bpf program to persist after user code
142142+> exits. Do note that if pinning is desired, users should not use the
143143+> `with_bpf_object_open_load_link` API and instead manually load and
144144+> attach their bpf program since the context manager shutdowns all
145145+> resources on exit.
146146+147147+Now let's add some looping logic to keep the loaded bpf program alive.
148148+149149+```ocaml
150150+let obj_path = "minimal.bpf.o"
151151+let program_names = [ "handle_tp" ]
152152+153153+let () =
154154+ with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
155155+ (fun obj link ->
156156+157157+ (* Set up signal handlers *)
158158+ let exitting = ref true in
159159+ let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
160160+ Sys.(set_signal sigint sig_handler);
161161+ Sys.(set_signal sigterm sig_handler);
162162+163163+ Printf.printf
164164+ "Successfully started! Please run `sudo cat \
165165+ /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF \
166166+ programs.\n\
167167+ %!"
168168+169169+ (* Loop until Ctrl-C is called *)
170170+ while !exitting do
171171+ Printf.eprintf ".%!";
172172+ Unix.sleepf 1.0
173173+ done)
174174+```
175175+176176+Our bpf program is now running in the kernel until we decide to
177177+interrupt it. However, it doesn't do exactly what we want. In
178178+particular, it doesn't filter for our process PID. This is because we
179179+haven't loaded our process PID into the BPF map. To do this, we need
180180+the name of the map we declared in the `minimal.bpf.c` program. In
181181+this case, our BPF array map was named `globals`.
182182+183183+```ocaml
184184+let map = "globals"
185185+186186+(* Load PID into BPF map *)
187187+let before_link obj =
188188+ let pid = Unix.getpid () |> Signed.Long.of_int in
189189+ let global_map = bpf_object_find_map_by_name obj map in
190190+ (* When updating an element, users need to specify the type of the key and value
191191+ declared by the map which checks that the key and value size are consistent. *)
192192+ bpf_map_update_elem ~key_ty:Ctypes.int ~val_ty:Ctypes.long global_map 0 pid
193193+```
194194+195195+Put together in [minimal.ml](./examples/minimal.ml), your bpf program
196196+runs in kernel and print to the trace pipe every second.
197197+198198+### Maps
199199+`libbpf_maps` is an optional convenience package that provides
200200+wrappers for BPF maps. Currently only Ringbuffer maps are added. An
201201+example usage of them can be found in
202202+[examples/bootstrap.ml](./examples/bootstrap.ml). This has been
203203+packaged separately since it drags in `libffi` dependency.
204204+205205+## Notes on compatibility
206206+> The libbpf C library is designed to be kernel-agnostic and work
207207+> across multitude of kernel versions. It has built-in mechanisms to
208208+> gracefully handle older kernels, that are missing some of the
209209+> features, by working around or gracefully degrading functionality.
210210+211211+Vendoring libbpf was a option. However, since bpf programs require
212212+writing the kernel components that may use libbpf, we made the choice
213213+to use the system's package versioned instead. This avoids users from
214214+knowingly/unknowingly using libbpf API's from two different
215215+versions. As a consequence, this library support operating systems
216216+that package libbpf.v.1.1 and up. Check ocaml-ci for the list of
217217+operating systems that successfully builds.
218218+219219+If so desired, you can also checkout the `vendored` branch in this
220220+repo which builds ocaml-libbpf with the latest libbpf package.
···11+(lang dune 3.13)
22+33+(name libbpf)
44+(source
55+ (github koonwen/ocaml-libbpf))
66+(authors "Lee Koon Wen")
77+(maintainers "Lee Koon Wen")
88+(license ISC BSD-3-Clause)
99+(documentation https://koonwen.github.io/ocaml-libbpf)
1010+1111+(package
1212+ (allow_empty)
1313+ (name conf-libbpf)
1414+ (synopsis "Virtual package for system installation of libbpf"))
1515+1616+(package
1717+ (allow_empty)
1818+ (name conf-bpftool)
1919+ (synopsis "Virtual package for system installation of bpftool"))
2020+2121+(package
2222+ (name libbpf)
2323+ (synopsis "Libbpf bindings")
2424+ (description "Wrapped libbpf api's for writing BPF user programs in OCaml")
2525+ (depends
2626+ (ocaml
2727+ (>= 4.08))
2828+ dune
2929+ (ctypes
3030+ (>= 0.22.0))
3131+ ppx_deriving
3232+ ppx_expect
3333+ conf-libbpf
3434+ conf-bpftool
3535+ conf-clang)
3636+ ; This is only a dependency for the examples directory
3737+ ; to show how to use dune to build bpf programs, it is
3838+ ; not part of the library bindings, however, we can't
3939+ ; remove it since it builds alongside the library
4040+ (tags
4141+ (bindings bpf libbpf)))
4242+4343+(package
4444+ (name libbpf_maps)
4545+ (synopsis "Libbpf maps API")
4646+ (description "High level API's for interacting with BPF maps in OCaml")
4747+ (depends
4848+ (ctypes
4949+ (>= 0.22.0))
5050+ (ctypes-foreign
5151+ (>= 0.22.0))
5252+ (libbpf
5353+ (= :version)))
5454+ (tags
5555+ (bindings bpf libbpf)))
+29
vendor/ocaml-libbpf/examples/LICENSE.md
···11+BSD 3-Clause License
22+33+Copyright (c) 2020, Andrii Nakryiko
44+All rights reserved.
55+66+Redistribution and use in source and binary forms, with or without
77+modification, are permitted provided that the following conditions are met:
88+99+1. Redistributions of source code must retain the above copyright notice, this
1010+ list of conditions and the following disclaimer.
1111+1212+2. Redistributions in binary form must reproduce the above copyright notice,
1313+ this list of conditions and the following disclaimer in the documentation
1414+ and/or other materials provided with the distribution.
1515+1616+3. Neither the name of the copyright holder nor the names of its
1717+ contributors may be used to endorse or promote products derived from
1818+ this software without specific prior written permission.
1919+2020+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
2121+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2222+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
2323+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
2424+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2525+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
2626+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
2727+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2828+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2929+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
···11+open Libbpf
22+open Libbpf_maps
33+open Ctypes
44+55+let obj_path = "bootstrap.bpf.o"
66+let program_names = [ "handle_exec"; "handle_exit" ]
77+let rb_name = "rb"
88+99+(* event structure layout from bootstrap.h *)
1010+let event : [ `Event ] structure typ = Ctypes.structure "event"
1111+let ( -: ) ty label = field event label ty
1212+let pid = int -: "pid"
1313+let ppid = int -: "ppid"
1414+let exit_code = uint -: "exit_code"
1515+let duration = ullong -: "duration_ns"
1616+let comm = array 16 char -: "comm"
1717+let filename = array 127 char -: "filename"
1818+let exit_event = bool -: "exit_event"
1919+let _ = seal event
2020+2121+let char_array_as_string a =
2222+ let len = CArray.length a in
2323+ let b = Buffer.create len in
2424+ try
2525+ for i = 0 to len - 1 do
2626+ let c = CArray.get a i in
2727+ if c = '\x00' then raise Exit else Buffer.add_char b c
2828+ done;
2929+ Buffer.contents b
3030+ with Exit -> Buffer.contents b
3131+3232+(* Describe User callback event handler *)
3333+let handle_event _ctx data _sz =
3434+ let ev = !@(from_voidp event data) in
3535+ let pid = getf ev pid in
3636+ let ppid = getf ev ppid in
3737+ let exit_code = getf ev exit_code |> Unsigned.UInt.to_string in
3838+ let duration = getf ev duration |> Unsigned.ULLong.to_int64 in
3939+ let comm = getf ev comm |> char_array_as_string in
4040+ let filename = getf ev filename |> char_array_as_string in
4141+ let exit_event = getf ev exit_event in
4242+ let tm = Unix.time () |> Unix.localtime in
4343+ let ts = Printf.sprintf "%d:%d:%d" tm.tm_hour tm.tm_min tm.tm_sec in
4444+ if exit_event then (
4545+ Printf.printf "%-8s %-5s %-16s %-7d %-7d [%s]" ts "EXIT" comm pid ppid
4646+ exit_code;
4747+ if duration >= 0L then
4848+ Printf.printf " (%Lums)" (Int64.div duration 1000000L);
4949+ print_newline ())
5050+ else
5151+ Printf.printf "%-8s %-5s %-16s %-7d %-7d %s\n" ts "EXEC" comm pid ppid
5252+ filename;
5353+ 0
5454+5555+let () =
5656+ (* Set signal handlers *)
5757+ let exitting = ref true in
5858+ let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
5959+ Sys.(set_signal sigint sig_handler);
6060+ Sys.(set_signal sigterm sig_handler);
6161+6262+ (* Use auto open/load/link helper *)
6363+ with_bpf_object_open_load_link ~obj_path ~program_names (fun obj _links ->
6464+ (* Load ringbuffer map *)
6565+ let map = bpf_object_find_map_by_name obj rb_name in
6666+6767+ (* Set up ring buffer *)
6868+ RingBuffer.init map ~callback:handle_event (fun rb ->
6969+ Printf.printf "%-8s %-5s %-16s %-7s %-7s %s\n%!" "TIME" "EVENT" "COMM"
7070+ "PID" "PPID" "FILENAME/EXIT CODE";
7171+7272+ while !exitting do
7373+ ignore
7474+ (try RingBuffer.poll rb ~timeout:100
7575+ with _ ->
7676+ exitting := false;
7777+ -1)
7878+ done))
+109
vendor/ocaml-libbpf/examples/bootstrap_c.ml
···11+module F = Libbpf.C.Functions
22+module T = Libbpf.C.Types
33+44+let bpf_obj_path = "bootstrap.bpf.o"
55+let program_names = [ "handle_exec"; "handle_exit" ]
66+let rb_name = "rb"
77+88+exception Exit of int
99+1010+let main () =
1111+ (* Set signal handlers *)
1212+ let exitting = ref true in
1313+ let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
1414+ Sys.(set_signal sigint sig_handler);
1515+ Sys.(set_signal sigterm sig_handler);
1616+1717+ (* Read BPF object *)
1818+ let obj =
1919+ match F.bpf_object__open bpf_obj_path with
2020+ | None ->
2121+ Printf.eprintf "Failed to open BPF object\n";
2222+ raise (Exit 1)
2323+ | Some obj -> obj
2424+ in
2525+2626+ at_exit (fun () -> F.bpf_object__close obj);
2727+2828+ (* Load BPF object *)
2929+ if F.bpf_object__load obj = 1 then (
3030+ Printf.eprintf "Failed to load BPF object\n";
3131+ raise (Exit 1));
3232+3333+ let progs =
3434+ let find_exn name =
3535+ match F.bpf_object__find_program_by_name obj name with
3636+ | None ->
3737+ Printf.eprintf "Failed to find bpf program: %s\n" name;
3838+ raise (Exit 1)
3939+ | Some p -> p
4040+ in
4141+ List.map find_exn program_names
4242+ in
4343+4444+ (* Attach tracepoint *)
4545+ let links =
4646+ let attach_exn prog =
4747+ match F.bpf_program__attach prog with
4848+ | Some linkp -> linkp
4949+ | None ->
5050+ Printf.eprintf "Failed to attach BPF program\n";
5151+ raise (Exit 1)
5252+ in
5353+ List.map attach_exn progs
5454+ in
5555+5656+ at_exit (fun () ->
5757+ List.iter (fun link -> F.bpf_link__destroy link |> ignore) links);
5858+5959+ (* Load maps *)
6060+ let map =
6161+ match F.bpf_object__find_map_by_name obj rb_name with
6262+ | None ->
6363+ Printf.eprintf "Failed to find map\n";
6464+ raise (Exit 1)
6565+ | Some m -> m
6666+ in
6767+ let rb_fd = F.bpf_map__fd map in
6868+6969+ (* Describe event handler *)
7070+ let handle_event _ctx _data _sz =
7171+ Printf.printf "Handle_event called\n%!";
7272+ 0
7373+ in
7474+7575+ (* Coerce it to the static_funptr *)
7676+ let handle_event_f =
7777+ Ctypes.(
7878+ coerce
7979+ (Foreign.funptr ~runtime_lock:false ~check_errno:true
8080+ (ptr void @-> ptr void @-> size_t @-> returning int))
8181+ T.ring_buffer_sample_fn handle_event)
8282+ in
8383+8484+ (* Set up ring buffer polling *)
8585+ let rb =
8686+ match
8787+ F.ring_buffer__new rb_fd handle_event_f Ctypes.null
8888+ Ctypes.(from_voidp T.ring_buffer_opts null)
8989+ with
9090+ | None ->
9191+ Printf.eprintf "Failed to create ring buffer\n";
9292+ raise (Exit 1)
9393+ | Some rb -> rb
9494+ in
9595+9696+ at_exit (fun () -> F.ring_buffer__free rb);
9797+9898+ while !exitting do
9999+ Printf.printf "polling\n%!";
100100+ let err = F.ring_buffer__poll rb 100 in
101101+ match err with
102102+ | e when e = Sys.sighup -> raise (Exit 0)
103103+ | e when e < 0 ->
104104+ Printf.eprintf "Error polling ring buffer, %d\n" e;
105105+ raise (Exit 1)
106106+ | _ -> ()
107107+ done
108108+109109+let () = try main () with Exit i when i <> 0 -> Printf.eprintf "[Exit %d]" i
···11+(* This program monitors the traffic going through your loopback
22+ interface, once this program is run, check your trace pipe with
33+ `sudo cat /sys/kernel/debug/tracing/trace_pipe` and run `ping
44+ 127.0.0.1` to see the output *)
55+open Ctypes
66+open Libbpf
77+88+let obj_path = "tc.bpf.o"
99+let program_name = "tc_ingress"
1010+1111+let () =
1212+ (* Set signal handlers *)
1313+ let exitting = ref true in
1414+ let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
1515+ Sys.(set_signal sigint sig_handler);
1616+ Sys.(set_signal sigterm sig_handler);
1717+1818+ let hook_created = ref false in
1919+2020+ let tc_hook = make C.Types.Bpf_tc.hook in
2121+ setf tc_hook C.Types.Bpf_tc.ifindex 1;
2222+ setf tc_hook C.Types.Bpf_tc.attach_point `INGRESS;
2323+ let sz = Ctypes.sizeof C.Types.Bpf_tc.hook in
2424+ setf tc_hook C.Types.Bpf_tc.sz (Unsigned.Size_t.of_int sz);
2525+2626+ let tc_opts = make C.Types.Bpf_tc.Opts.t in
2727+ setf tc_opts C.Types.Bpf_tc.Opts.handle (Unsigned.UInt32.of_int 1);
2828+ setf tc_opts C.Types.Bpf_tc.Opts.priority (Unsigned.UInt32.of_int 1);
2929+ let sz = Ctypes.sizeof C.Types.Bpf_tc.Opts.t in
3030+ setf tc_opts C.Types.Bpf_tc.Opts.sz (Unsigned.Size_t.of_int sz);
3131+3232+ (* Open and load bpf object *)
3333+ let obj = bpf_object_open obj_path in
3434+ bpf_object_load obj;
3535+ let prog = bpf_object_find_program_by_name obj program_name in
3636+3737+ (* Try to create hook *)
3838+ (* The hook (i.e. qdisc) may already exists because: *)
3939+ (* 1. it is created by other processes or users *)
4040+ (* 2. or since we are attaching to the TC ingress ONLY, *)
4141+ (* bpf_tc_hook_destroy does NOT really remove the qdisc, *)
4242+ (* there may be an egress filter on the qdisc *)
4343+ let err = C.Functions.bpf_tc_hook_create (addr tc_hook) in
4444+ if err = 0 then hook_created := true;
4545+4646+ if err <> 0 && err <> -17 (*EEXIST*) then (
4747+ Printf.eprintf "Failed to create tc hook: %d\n" err;
4848+ exit 1);
4949+5050+ setf tc_opts C.Types.Bpf_tc.Opts.prog_fd prog.fd;
5151+ let err = C.Functions.bpf_tc_attach (addr tc_hook) (addr tc_opts) in
5252+ if err = 1 then (
5353+ Printf.eprintf "Failed to attach TC: %d\n" err;
5454+ C.Functions.bpf_tc_hook_destroy (addr tc_hook) |> ignore;
5555+ exit 1);
5656+5757+ Printf.printf
5858+ "Successfully started! Please run `sudo cat \
5959+ /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF program.\n\
6060+ %!";
6161+6262+ while !exitting do
6363+ Printf.eprintf ".%!";
6464+ Unix.sleepf 1.0
6565+ done;
6666+6767+ let err = C.Functions.bpf_tc_detach (addr tc_hook) (addr tc_opts) in
6868+ if err = 1 then Printf.eprintf "Failed to detach TC: %d\n" err;
6969+ C.Functions.bpf_tc_hook_destroy (addr tc_hook) |> ignore;
7070+ bpf_object_close obj
+44
vendor/ocaml-libbpf/libbpf.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "Libbpf bindings"
44+description: "Wrapped libbpf api's for writing BPF user programs in OCaml"
55+maintainer: ["Lee Koon Wen"]
66+authors: ["Lee Koon Wen"]
77+license: ["ISC" "BSD-3-Clause"]
88+tags: ["bindings" "bpf" "libbpf"]
99+homepage: "https://github.com/koonwen/ocaml-libbpf"
1010+doc: "https://koonwen.github.io/ocaml-libbpf"
1111+bug-reports: "https://github.com/koonwen/ocaml-libbpf/issues"
1212+depends: [
1313+ "ocaml" {>= "4.08"}
1414+ "dune" {>= "3.13"}
1515+ "ctypes" {>= "0.22.0"}
1616+ "ppx_deriving"
1717+ "ppx_expect"
1818+ "conf-libbpf"
1919+ "conf-bpftool"
2020+ "conf-clang"
2121+ "odoc" {with-doc}
2222+]
2323+build: [
2424+ ["dune" "subst"] {dev}
2525+ [
2626+ "dune"
2727+ "build"
2828+ "-p"
2929+ name
3030+ "-j"
3131+ jobs
3232+ "@install"
3333+ "@runtest" {with-test}
3434+ "@doc" {with-doc}
3535+ ]
3636+]
3737+dev-repo: "git+https://github.com/koonwen/ocaml-libbpf.git"
3838+# eBPF features by kernel version https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
3939+# Fix to kernel >= 6.1 to provide bound BPF map types
4040+available: [ os = "linux" &
4141+ (( os-distribution = "debian" & os-version >= "12" ) # Linux 6.1 & Libbpf 1.1.0
4242+ |( os-distribution = "ubuntu" & os-version >= "23.04" ) # Linux 6.2 & Libbpf 1.1.0
4343+ |( os-distribution = "fedora" & os-version >= "38" )) # Linux 6.2 & Libbpf 1.1.0
4444+ ]
+7
vendor/ocaml-libbpf/libbpf.opam.template
···11+# eBPF features by kernel version https://github.com/iovisor/bcc/blob/master/docs/kernel-versions.md
22+# Fix to kernel >= 6.1 to provide bound BPF map types
33+available: [ os = "linux" &
44+ (( os-distribution = "debian" & os-version >= "12" ) # Linux 6.1 & Libbpf 1.1.0
55+ |( os-distribution = "ubuntu" & os-version >= "23.04" ) # Linux 6.2 & Libbpf 1.1.0
66+ |( os-distribution = "fedora" & os-version >= "38" )) # Linux 6.2 & Libbpf 1.1.0
77+ ]
···11+open Ctypes
22+33+(** You probably don't mean to be looking into this section, it is
44+ part of the stub generation process of the bindings.*)
55+66+(* The C_types_generated module is generated by the build system to
77+ grab the type definitions from the header files in C to ensure
88+ that offsets and structs are aligned. *)
99+module Types = C_type_description.Types (C_types_generated)
1010+1111+module Functions (F : Ctypes.FOREIGN) = struct
1212+ open F
1313+1414+ (* ======================================== Generics ======================================== *)
1515+ let libbpf_major_version =
1616+ foreign "libbpf_major_version" (void @-> returning uint32_t)
1717+1818+ let libbpf_minor_version =
1919+ foreign "libbpf_minor_version" (void @-> returning uint32_t)
2020+2121+ let libbpf_version_string =
2222+ foreign "libbpf_version_string" (void @-> returning string)
2323+2424+ let libbpf_strerror =
2525+ foreign "libbpf_strerror" (int @-> ptr char @-> size_t @-> returning int)
2626+2727+ let libbpf_bpf_attach_type_str =
2828+ foreign "libbpf_bpf_attach_type_str"
2929+ (Types.Bpf_attach_type.t @-> returning string)
3030+3131+ let libbpf_bpf_link_type_str =
3232+ foreign "libbpf_bpf_link_type_str"
3333+ (Types.Bpf_link_type.t @-> returning string)
3434+3535+ let libbpf_bpf_map_type_str =
3636+ foreign "libbpf_bpf_map_type_str" (Types.Bpf_map_type.t @-> returning string)
3737+3838+ let libbpf_bpf_prog_type_str =
3939+ foreign "libbpf_bpf_prog_type_str"
4040+ (Types.Bpf_prog_type.t @-> returning string)
4141+4242+ let libbpf_set_print =
4343+ foreign "libbpf_set_print"
4444+ (Types.libbpf_print_fn_t @-> returning Types.libbpf_print_fn_t)
4545+4646+ (* ================================= Open / Load / Close =================================== *)
4747+4848+ (** [bpf_object__open path] creates a bpf_object by opening the BPF
4949+ ELF object file pointed to by the passed [path] and loading it
5050+ into memory.
5151+5252+ Returns pointer to the new bpf_object; or NULL is returned on
5353+ error, error code is stored in errno. *)
5454+ let bpf_object__open =
5555+ foreign "bpf_object__open" (string @-> returning (ptr_opt Types.bpf_object))
5656+5757+ (** [bpf_object__load obj_ptr] loads the BPF object into the
5858+ kernel. [obj_ptr] must be a valid BPF object instance returned
5959+ by a successful call to [bpf_object__open].
6060+6161+ Returns 0, on success; negative error code, otherwise, error code is stored in errno *)
6262+ let bpf_object__load =
6363+ foreign "bpf_object__load" (ptr Types.bpf_object @-> returning int)
6464+6565+ (** [bpf_object__find_program_by_name name] returns the BPF program
6666+ of the given [name], if it exists within the passed BPF object
6767+6868+ Returns the pointer to the BPF program instance, if such program
6969+ exists within the BPF object; or NULL otherwise. *)
7070+ let bpf_object__find_program_by_name =
7171+ foreign "bpf_object__find_program_by_name"
7272+ (ptr Types.bpf_object @-> string @-> returning (ptr_opt Types.bpf_program))
7373+7474+ (** [bpf_object__next_program obj_ptr prog_ptr] returns the next
7575+ program after [prog_ptr] found in the passed BPF object *)
7676+ let bpf_object__next_program =
7777+ foreign "bpf_object__next_program"
7878+ (ptr Types.bpf_object @-> ptr Types.bpf_program
7979+ @-> returning (ptr Types.bpf_program))
8080+8181+ (** [bpf_program__pin prog path] pins the BPF program to a file in
8282+ the BPF FS specified by a [path]. This increments the programs
8383+ reference count, allowing it to stay loaded after the process
8484+ which loaded it has exited.
8585+8686+ @param prog BPF program to pin, must already be loaded
8787+ @param path file path in a BPF file system
8888+ @return 0, on success; negative error code, otherwise *)
8989+ let bpf_program__pin =
9090+ foreign "bpf_program__pin"
9191+ (ptr Types.bpf_program @-> ptr char @-> returning int)
9292+9393+ (** [bpf_program__unpin prog path] unpins the BPF program from a file in the
9494+ BPFFS specified by a path. This decrements the programs
9595+ reference count. The file pinning the BPF program can also be
9696+ unlinked by a different process in which case this function will
9797+ return an error.
9898+9999+ @param prog BPF program to unpin
100100+ @param path file path to the pin in a BPF file system
101101+ @return 0, on success; negative error code, otherwise *)
102102+ let bpf_program__unpin =
103103+ foreign "bpf_program__unpin"
104104+ (ptr Types.bpf_program @-> ptr char @-> returning int)
105105+106106+ (** [bpf_program__attach prog] is a generic function for
107107+ attaching a BPF program based on auto-detection of program type,
108108+ attach type, and extra paremeters, where applicable.
109109+110110+ This is supported for:
111111+ - kprobe/kretprobe (depends on SEC() definition)
112112+ - uprobe/uretprobe (depends on SEC() definition)
113113+ - tracepoint
114114+ - raw tracepoint
115115+ - tracing programs (typed raw TP/fentry/fexit/fmod_ret)
116116+117117+ Returns pointer to the newly created BPF link; or NULL is
118118+ returned on error, error code is stored in errno *)
119119+ let bpf_program__attach =
120120+ foreign "bpf_program__attach"
121121+ (ptr Types.bpf_program @-> returning (ptr_opt Types.bpf_link))
122122+123123+ let bpf_program__fd =
124124+ foreign "bpf_program__fd" (ptr Types.bpf_program @-> returning int)
125125+126126+ (** [bpf_link__pin link path] pins the BPF link to a file in the
127127+ BPF FS specified by a path. This increments the links reference
128128+ count, allowing it to stay loaded after the process which loaded
129129+ it has exited.
130130+131131+ @param link BPF link to pin, must already be loaded
132132+ @param path file path in a BPF file system
133133+ @return 0, on success; negative error code, otherwise *)
134134+ let bpf_link__pin =
135135+ foreign "bpf_link__pin" (ptr Types.bpf_link @-> ptr char @-> returning int)
136136+137137+ (** [bpf_link__unpin link path] unpins the BPF link from a file in
138138+ the BPFFS specified by a path. This decrements the links
139139+ reference count. The file pinning the BPF link can also be
140140+ unlinked by a different process in which case this function will
141141+ return an error.
142142+143143+ @param prog BPF program to unpin
144144+ @param path file path to the pin in a BPF file system
145145+ @return 0, on success; negative error code, otherwise *)
146146+ let bpf_link__unpin =
147147+ foreign "bpf_link__unpin" (ptr Types.bpf_link @-> returning int)
148148+149149+ (** [bpf_link__destroy link_ptr] Removes the link to the BPF program.
150150+ Returns 0 on success or -errno *)
151151+ let bpf_link__destroy =
152152+ foreign "bpf_link__destroy" (ptr Types.bpf_link @-> returning int)
153153+154154+ (** [bpf_object__close obj_ptr] closes a BPF object and releases all
155155+ resources. *)
156156+ let bpf_object__close =
157157+ foreign "bpf_object__close" (ptr Types.bpf_object @-> returning void)
158158+159159+ (* ======================================== Maps ======================================== *)
160160+ (* Not explicitly mentioned in the documentation but keys and values look
161161+ like they're copied into the internal bpf map structure, so we
162162+ don't need to be worried about keeping references around. *)
163163+164164+ (** [bpf_object__find_map_by_name obj_ptr name] returns BPF map of the given
165165+ [name], if it exists within the passed BPF object.
166166+167167+ Returns the pointer to the BPF map instance, if such map exists
168168+ within the BPF object; or NULL otherwise. *)
169169+ let bpf_object__find_map_by_name =
170170+ foreign "bpf_object__find_map_by_name"
171171+ (ptr Types.bpf_object @-> string @-> returning (ptr_opt Types.bpf_map))
172172+173173+ (** [bpf_map__fd map_ptr] gets the file descriptor of the passed BPF
174174+ map
175175+176176+ Returns the file descriptor; or -EINVAL in case of an error *)
177177+ let bpf_map__fd = foreign "bpf_map__fd" (ptr Types.bpf_map @-> returning int)
178178+179179+ (** [bpf_map__lookup_elem map_ptr key_ptr key_sz val_ptr val_sz
180180+ flags] allows to lookup BPF map value corresponding to provided
181181+ key.
182182+183183+ [bpf_map__lookup_elem] is high-level equivalent of
184184+ [bpf_map_lookup_elem] API with added check for key and value
185185+ size.
186186+187187+ sizes are in bytes of key and value data. For per-CPU BPF maps
188188+ value size has to be a product of BPF map value size and number
189189+ of possible CPUs in the system (could be fetched with
190190+ libbpf_num_possible_cpus()). Note also that for per-CPU values
191191+ value size has to be aligned up to closest 8 bytes for alignment
192192+ reasons, so expected size is: round_up(value_size, 8)
193193+194194+ Returns 0, on success; negative error, otherwise *)
195195+ let bpf_map__lookup_elem =
196196+ foreign "bpf_map__lookup_elem"
197197+ (ptr Types.bpf_map @-> ptr void @-> size_t @-> ptr void @-> size_t
198198+ @-> uint64_t @-> returning int)
199199+200200+ (** [bpf_map__update_elem map_ptr key_ptr key_sz val_ptr val_sz
201201+ flags] allows to insert or update value in BPF map that
202202+ corresponds to provided key.
203203+204204+ [bpf_map__update_elem] is high-level equivalent of
205205+ [bpf_map_update_elem] API with added check for key and value
206206+ size.
207207+208208+ Check [bpf_map__lookup_elem] for details on sizes.
209209+ Returns 0, on success; negative error, otherwise *)
210210+ let bpf_map__update_elem =
211211+ foreign "bpf_map__update_elem"
212212+ (ptr Types.bpf_map @-> ptr void @-> size_t @-> ptr void @-> size_t
213213+ @-> uint64_t @-> returning int)
214214+215215+ (** [bpf_map__delete_elem map_ptr key_ptr key_sz flags] allows to
216216+ delete element in BPF map that corresponds to provided key.
217217+218218+ [bpf_map__delete_elem] is high-level equivalent of
219219+ [bpf_map_delete_elem] API with added check for key size.
220220+221221+ Returns 0, on success; negative error, otherwise *)
222222+ let bpf_map__delete_elem =
223223+ foreign "bpf_map__delete_elem"
224224+ (ptr Types.bpf_map @-> ptr void @-> size_t @-> uint64_t @-> returning int)
225225+226226+ (* ================================== Traffic control ================================== *)
227227+228228+ let bpf_tc_hook_create =
229229+ foreign "bpf_tc_hook_create" (ptr Types.Bpf_tc.hook @-> returning int)
230230+231231+ let bpf_tc_hook_destroy =
232232+ foreign "bpf_tc_hook_destroy" (ptr Types.Bpf_tc.hook @-> returning int)
233233+234234+ let bpf_tc_attach =
235235+ foreign "bpf_tc_attach"
236236+ (ptr Types.Bpf_tc.hook @-> ptr Types.Bpf_tc.Opts.t @-> returning int)
237237+238238+ let bpf_tc_detach =
239239+ foreign "bpf_tc_detach"
240240+ (ptr Types.Bpf_tc.hook @-> ptr Types.Bpf_tc.Opts.t @-> returning int)
241241+242242+ (* ====================================== RingBuf ===================================== *)
243243+244244+ (** [ring_buffer__new map_fd fn ctx_ptr opts] loads the callback
245245+ function [fn] into the ring buffer map provided by the file
246246+ descriptor [map_fd]. [ctx_ptr] allows the callback function to
247247+ access user provided context.
248248+249249+ Returns pointer to the ring_buffer manager instance or NULL
250250+ otherwise *)
251251+ let ring_buffer__new =
252252+ foreign "ring_buffer__new"
253253+ (int @-> Types.ring_buffer_sample_fn @-> ptr void
254254+ @-> ptr Types.ring_buffer_opts
255255+ @-> returning (ptr_opt Types.ring_buffer))
256256+257257+ (** [ring_buffer__poll ring_buf_ptr timeout] poll for available
258258+ data and consume records, if any are available.
259259+260260+ Returns number of records consumed (or INT_MAX, whichever is
261261+ less), or negative number, if any of the registered callbacks
262262+ returned error. *)
263263+ let ring_buffer__poll =
264264+ foreign "ring_buffer__poll" (ptr Types.ring_buffer @-> int @-> returning int)
265265+266266+ (** [ring_buffer__free ring_buf_ptr] Frees resources of the ring
267267+ buffer manager *)
268268+ let ring_buffer__free =
269269+ foreign "ring_buffer__free" (ptr Types.ring_buffer @-> returning void)
270270+271271+ (** [ring_buffer__consume ring_buf_ptr] Consume available ring
272272+ buffer(s) data without event polling.
273273+274274+ Returns number of records consumed across all registered ring
275275+ buffers (or INT_MAX, whichever is less), or negative number if
276276+ any of the callbacks return error. *)
277277+ let ring_buffer__consume =
278278+ foreign "ring_buffer__consume" (ptr Types.ring_buffer @-> returning int)
279279+280280+ (** [ring_buffer__epoll_fd ring_buf_ptr] Gets an fd that can be used
281281+ to sleep until data is available in the ring(s) *)
282282+ let ring_buffer__epoll_fd =
283283+ foreign "ring_buffer__epoll_fd" (ptr Types.ring_buffer @-> returning int)
284284+end
···11+{0 ocaml-libbpf}
22+33+OCaml bindings to {{:https://github.com/libbpf/libbpf}libbpf} C
44+library for loading eBPF programs into the linux kernel.
55+66+{1 Introduction}
77+88+Writing eBPF programs consist of two distinct parts. Implementing the
99+code that executes in-kernel and user-level code responsible for
1010+loading/initializing/linking/teardown of the in-kernel code. This
1111+OCaml library provides the latter via binding the C libbpf library. It
1212+exposes both the raw low-level bindings as well as a set of high-level
1313+API's for handling your eBPF objects. As of now, the kernel part must
1414+still be written in
1515+{{:https://stackoverflow.com/questions/57688344/what-is-not-allowed-in-restricted-c-for-ebpf}
1616+restricted C} and compiled with llvm to eBPF bytecode.
1717+1818+For the high-level APIs: {!Libbpf}
1919+2020+For the low-level bindings: {!Libbpf.C}.
2121+2222+{1:Tutorial Tutorial}
2323+2424+This example assumes the user has knowledge of how to implement the
2525+kernel part of a eBPF program. If not, you can check out this
2626+{{:https://nakryiko.com/posts/libbpf-bootstrap/#the-bpf-side}
2727+resource} first. Consider the following kernel eBPF program named {b
2828+minimal.bpf.c}:
2929+3030+{@c[
3131+// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
3232+/* Copyright (c) 2020 Facebook */
3333+#include <linux/bpf.h>
3434+#include "bpf/bpf_helpers.h" /* This is from our libbpf library */
3535+3636+char LICENSE[] SEC("license") = "Dual BSD/GPL";
3737+3838+/* Globals implemented as an array */
3939+struct {
4040+ __uint(type, BPF_MAP_TYPE_ARRAY);
4141+ __uint(max_entries, 1);
4242+ __type(key, int);
4343+ __type(value, long);
4444+} globals SEC(".maps");
4545+4646+int my_pid_index = 0;
4747+4848+SEC("tp/syscalls/sys_enter_write")
4949+int handle_tp(void *ctx) {
5050+ int pid = bpf_get_current_pid_tgid() >> 32;
5151+5252+ long *my_pid;
5353+ my_pid = bpf_map_lookup_elem(&globals, &my_pid_index);
5454+ if (my_pid == NULL) {
5555+ bpf_printk("Error got NULL");
5656+ return 1;
5757+ };
5858+5959+ if (pid != *my_pid)
6060+ return 0;
6161+6262+ bpf_printk("Hello, BPF triggered from PID %d", pid);
6363+6464+ return 0;
6565+}
6666+]}
6767+6868+After compilation to eBPF ELF file as {b "minimal.o"}. Users just need
6969+to provide the path to this ELF file along with the name of the
7070+program and optionally an initialization function. Note that the name
7171+of the program refers to the function identifier under the SEC(...)
7272+attribute, in this case it is {b "handle_tp"}.
7373+7474+{@ocaml[
7575+open Libbpf
7676+7777+let obj_path = "minimal.bpf.o"
7878+let program_names = [ "handle_tp" ]
7979+8080+let () =
8181+ with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
8282+ (fun obj link -> (* Do something *))
8383+]}
8484+8585+The context manager {{!Libbpf.with_bpf_object_open_load_link}
8686+with_bpf_object_open_load_link} is a convenience wrapper for all the
8787+neccessary steps to load up your eBPF program into the kernel.
8888+8989+If we don't specify anything in the body of the function marked with
9090+{b (* Do something *)}, our loaded kernel program will be unloaded
9191+immediately. In this case, we will add some looping logic to keep the
9292+program running in the kernel and add a set of signal handlers to
9393+escape the loop.
9494+9595+{@ocaml[
9696+let obj_path = "minimal.bpf.o"
9797+let program_names = [ "handle_tp" ]
9898+9999+let () =
100100+ with_bpf_object_open_load_link ~obj_path ~program_names ~before_link
101101+ (fun obj link ->
102102+103103+ (* Set up signal handlers *)
104104+ let exitting = ref true in
105105+ let sig_handler = Sys.Signal_handle (fun _ -> exitting := false) in
106106+ Sys.(set_signal sigint sig_handler);
107107+ Sys.(set_signal sigterm sig_handler);
108108+109109+ Printf.printf
110110+ "Successfully started! Please run `sudo cat \
111111+ /sys/kernel/debug/tracing/trace_pipe` to see output of the BPF \
112112+ programs.\n\
113113+ %!"
114114+115115+ (* Loop until Ctrl-C is called *)
116116+ while !exitting do
117117+ Printf.eprintf ".%!";
118118+ Unix.sleepf 1.0
119119+ done)
120120+]}
121121+122122+Our bpf program is now running in the kernel until we decide to
123123+interrupt it. However, it doesn't do exactly what we want. In
124124+particular, it doesn't filter for our process PID. This is because we
125125+haven't loaded our process PID into the BPF map. To do this, we need
126126+the name of the map we declared by our {b minimal.bpf.c} program. In
127127+this case, our BPF array map was named {b globals}.
128128+129129+{@ocaml[
130130+let map = "globals"
131131+132132+(* Load PID into BPF map *)
133133+let before_link obj =
134134+ let pid = Unix.getpid () |> Signed.Long.of_int in
135135+ let global_map = bpf_object_find_map_by_name obj map in
136136+ (* When updating an element, users need to specify the type of the key and value
137137+ declared by the map which checks that the key and value size are consistent. *)
138138+ bpf_map_update_elem ~key_ty:Ctypes.int ~val_ty:Ctypes.long global_map 0 pid
139139+]}
140140+141141+Now if we combine the two, we can run this program and see the output
142142+interactively being printed to the trace pipe.
143143+144144+{1 Notice!}
145145+146146+root permissions are required when you run eBPF programs. This is a
147147+consequence of the fact that they are loaded into the kernel. To offer
148148+some assurance though, eBPF programs always have to pass through a
149149+verifier before they can be loaded. This ensures that eBPF programs
150150+aren't able crash to crash the kernel. For more information, read
151151+{{:https://ebpf.io/what-is-ebpf/#ebpf-safety} here}.
+153
vendor/ocaml-libbpf/src/libbpf.ml
···11+open Ctypes
22+module C = C
33+44+let major_version =
55+ C.Functions.libbpf_major_version () |> Unsigned.UInt32.to_int
66+77+let minor_version =
88+ C.Functions.libbpf_minor_version () |> Unsigned.UInt32.to_int
99+1010+let version_string = C.Functions.libbpf_version_string ()
1111+1212+let bpf_attach_type_str attach_type =
1313+ C.Functions.libbpf_bpf_attach_type_str attach_type
1414+1515+let bpf_link_type_str link_type = C.Functions.libbpf_bpf_link_type_str link_type
1616+let bpf_map_type_str map_type = C.Functions.libbpf_bpf_map_type_str map_type
1717+let bpf_prog_type_str prog_type = C.Functions.libbpf_bpf_prog_type_str prog_type
1818+1919+type bpf_object = C.Types.bpf_object structure ptr
2020+2121+type bpf_program = {
2222+ name : string;
2323+ fd : int;
2424+ ptr : C.Types.bpf_program structure ptr;
2525+}
2626+2727+type bpf_map = { fd : int; ptr : C.Types.bpf_map structure ptr }
2828+type bpf_link = C.Types.bpf_link structure ptr
2929+3030+let failwith_f fmt =
3131+ let fails s = failwith s in
3232+ Printf.ksprintf fails fmt
3333+3434+let bpf_object_open obj_file =
3535+ match C.Functions.bpf_object__open obj_file with
3636+ | Some obj -> obj
3737+ | None -> failwith_f "Error opening object file at %s" obj_file
3838+3939+let bpf_object_load bpf_object =
4040+ let ret = C.Functions.bpf_object__load bpf_object in
4141+ if ret = 0 then ()
4242+ else failwith_f "Could not load bpf_object, got exit %d" ret
4343+4444+let bpf_object_find_program_by_name bpf_object name =
4545+ match C.Functions.bpf_object__find_program_by_name bpf_object name with
4646+ | Some prog -> { name; fd = C.Functions.bpf_program__fd prog; ptr = prog }
4747+ | None -> failwith_f "Program name %s not found" name
4848+4949+let bpf_program_attach ({ name; ptr; _ } : bpf_program) =
5050+ match C.Functions.bpf_program__attach ptr with
5151+ | Some link -> link
5252+ | None -> failwith_f "Error attaching program %s" name
5353+5454+let bpf_program_fd (prog : bpf_program) = prog.fd
5555+5656+let bpf_object_find_map_by_name bpf_object name =
5757+ match C.Functions.bpf_object__find_map_by_name bpf_object name with
5858+ | Some ptr -> { fd = C.Functions.bpf_map__fd ptr; ptr }
5959+ | None -> failwith_f "Map %s not found" name
6060+6161+let bpf_map_fd (map : bpf_map) = map.fd
6262+6363+let bpf_link_destroy bpf_link =
6464+ match C.Functions.bpf_link__destroy bpf_link with
6565+ | e when e <> 0 -> Printf.eprintf "Failed to destroy link %d\n" e
6666+ | _ -> ()
6767+6868+let bpf_object_close bpf_object = C.Functions.bpf_object__close bpf_object
6969+7070+let with_bpf_object_open_load_link ~obj_path ~program_names
7171+ ?(before_link = Stdlib.ignore) fn =
7272+ let obj = bpf_object_open obj_path in
7373+ bpf_object_load obj;
7474+7575+ let cleanup ?links obj =
7676+ Option.iter (List.iter bpf_link_destroy) links;
7777+ bpf_object_close obj
7878+ in
7979+8080+ (* Programs to load cannot be zero *)
8181+ if program_names = [] then (
8282+ cleanup obj;
8383+ failwith "Need to specify at least one program to load");
8484+8585+ (* Get list of programs *)
8686+ let programs, not_found =
8787+ List.fold_left
8888+ (fun (succ, fail) name ->
8989+ match C.Functions.bpf_object__find_program_by_name obj name with
9090+ | None -> (succ, name :: fail)
9191+ | Some prog -> ((prog, name) :: succ, fail))
9292+ ([], []) program_names
9393+ in
9494+ if not_found <> [] then (
9595+ cleanup obj;
9696+ failwith_f "Failed to find %s programs" (String.concat "," not_found));
9797+9898+ (* Run before_link user initialization code *)
9999+ (try before_link obj
100100+ with e ->
101101+ bpf_object_close obj;
102102+ raise e);
103103+104104+ (* Get list of links *)
105105+ let links, not_attached =
106106+ List.fold_left
107107+ (fun (succ, fail) (prog, name) ->
108108+ match C.Functions.bpf_program__attach prog with
109109+ | None -> (succ, name :: fail)
110110+ | Some prog -> (prog :: succ, fail))
111111+ ([], []) programs
112112+ in
113113+ if not_attached <> [] then (
114114+ (* Detached successfully attached before shutdown *)
115115+ cleanup ~links obj;
116116+ failwith_f "Failed to link %s programs" (String.concat "," not_attached));
117117+118118+ (* Run user program *)
119119+ (try fn obj links
120120+ with e ->
121121+ cleanup ~links obj;
122122+ raise e);
123123+124124+ (* Ensure proper shutdown *)
125125+ cleanup ~links obj
126126+127127+let bpf_map_lookup_value ~key_ty ~val_ty ~val_zero bpf_map key =
128128+ let key = allocate key_ty key in
129129+ let sz_key = sizeof key_ty |> Unsigned.Size_t.of_int in
130130+ let value = allocate val_ty val_zero in
131131+ let sz_val = sizeof val_ty |> Unsigned.Size_t.of_int in
132132+ let err =
133133+ C.Functions.bpf_map__lookup_elem bpf_map.ptr (to_voidp key) sz_key
134134+ (to_voidp value) sz_val Unsigned.UInt64.zero
135135+ in
136136+ if err = 0 then !@value
137137+ else
138138+ let err = Printf.sprintf "bpf_map_lookup_value got %d" err in
139139+ raise (Sys_error err)
140140+141141+let bpf_map_update_elem ~key_ty ~val_ty bpf_map key value =
142142+ let key = allocate key_ty key in
143143+ let sz_key = sizeof key_ty |> Unsigned.Size_t.of_int in
144144+ let value = allocate val_ty value in
145145+ let sz_val = sizeof val_ty |> Unsigned.Size_t.of_int in
146146+ let err =
147147+ C.Functions.bpf_map__update_elem bpf_map.ptr (to_voidp key) sz_key
148148+ (to_voidp value) sz_val Unsigned.UInt64.zero
149149+ in
150150+ if err = 0 then ()
151151+ else
152152+ let err = Printf.sprintf "bpf_map_update_value got %d" err in
153153+ raise (Sys_error err)
+114
vendor/ocaml-libbpf/src/libbpf.mli
···11+(** See {!page-index} for example usage *)
22+33+open Ctypes
44+55+module C : module type of C
66+(** Entry point for the underlying C primatives *)
77+88+val major_version : int
99+val minor_version : int
1010+val version_string : string
1111+val bpf_attach_type_str : C.Types.Bpf_attach_type.t -> string
1212+val bpf_link_type_str : C.Types.Bpf_link_type.t -> string
1313+val bpf_map_type_str : C.Types.Bpf_map_type.t -> string
1414+val bpf_prog_type_str : C.Types.Bpf_prog_type.t -> string
1515+1616+type bpf_object = C.Types.bpf_object structure ptr
1717+1818+type bpf_program = {
1919+ name : string;
2020+ fd : int;
2121+ ptr : C.Types.bpf_program structure ptr;
2222+}
2323+2424+type bpf_map = { fd : int; ptr : C.Types.bpf_map structure ptr }
2525+type bpf_link = C.Types.bpf_link structure ptr
2626+2727+val bpf_object_open : string -> bpf_object
2828+(** [bpf_object_open path] opens and tries to read the bpf_object
2929+ found at path [path] in the filesystem. Libbpf parses the BPF
3030+ object file and discovers BPF maps, BPF programs, and global
3131+ variables. After a BPF app is opened, user space apps can make
3232+ additional adjustments (setting BPF program types, if necessary;
3333+ pre-setting initial values for global variables, etc.) before all
3434+ the entities are created and loaded.
3535+3636+ Fails if object file is in invalid format or path does not exist *)
3737+3838+val bpf_object_load : bpf_object -> unit
3939+(** [bpf_object_load obj] tries to load [obj]. Libbpf parses
4040+ the BPF object file and discovers BPF maps, BPF programs, and
4141+ global variables. After a BPF app is opened, user space apps can
4242+ make additional adjustments (setting BPF program types, if
4343+ necessary; pre-setting initial values for global variables, etc.)
4444+ before all the entities are created and loaded. *)
4545+4646+val bpf_object_find_program_by_name : bpf_object -> string -> bpf_program
4747+(** [bpf_object_find_program_by_name obj name] locates the
4848+ program identifier [name] within the [obj].
4949+5050+ Fails if [name] is not found *)
5151+5252+val bpf_program_attach : bpf_program -> bpf_link
5353+(** [bpf_program_attach prog] attaches the [prog] in the
5454+ kernel to start respond to events. Libbpf attaches BPF programs to
5555+ various BPF hook points (e.g., tracepoints, kprobes, cgroup hooks,
5656+ network packet processing pipeline, etc.). During this phase, BPF
5757+ programs perform useful work such as processing packets, or
5858+ updating BPF maps and global variables that can be read from user
5959+ space
6060+6161+ Fails if link could not be attached *)
6262+6363+val bpf_program_fd : bpf_program -> int
6464+(** [bpf_map_fd prog] returns the fd of the [prog] *)
6565+6666+val bpf_object_find_map_by_name : bpf_object -> string -> bpf_map
6767+(** [bpf_object_find_map_by_name obj name] locates the bpf_map
6868+ identifier [name] within [obj].
6969+7070+ Fails if map is not found *)
7171+7272+val bpf_map_fd : bpf_map -> int
7373+(** [bpf_map_fd map] returns the fd of the [map] *)
7474+7575+val bpf_link_destroy : bpf_link -> unit
7676+(** [bpf_link_destroy link] detaches and unloads the bpf program
7777+ associated to [link] from the kernel *)
7878+7979+val bpf_object_close : bpf_object -> unit
8080+(** [bpf_object_close obj] tearsdown [obj]. BPF maps are destroyed,
8181+ and all the resources used by the BPF app are freed. *)
8282+8383+val with_bpf_object_open_load_link :
8484+ obj_path:string ->
8585+ program_names:string list ->
8686+ ?before_link:(bpf_object -> unit) ->
8787+ (bpf_object -> bpf_link list -> unit) ->
8888+ unit
8989+(** [with_bpf_object_open_load_link obj_path program_names
9090+ ?before_link fn] performs opening and loading of the provided
9191+ filesystem path to the bpf_object [obj_path]. This helper runs
9292+ [before_link] before the program attaches the bpf programs
9393+ specified in [program_names]. Initialization code should go
9494+ here. [fn] is passed the bpf_object and the list of program links
9595+ if all steps were successful. Ensures all the proper shutdown and
9696+ cleanup of bpf_object resources and links *)
9797+9898+val bpf_map_lookup_value :
9999+ key_ty:'a typ -> val_ty:'b typ -> val_zero:'b -> bpf_map -> 'a -> 'b
100100+(** [bpf_map_lookup_value key_ty val_ty val_zero map k flags] Looks
101101+ up the value associated with the key [k]. If key is invalid, no
102102+ value is found or the size of key/value is not in sync, it will
103103+ return an error. [bpf_map_lookup_value] expects [key_ty] and
104104+ [val_ty] to verify the types are coherent in your bpf map
105105+ declaration. [val_zero] is merely an initialization value that
106106+ will be overwritten. *)
107107+108108+val bpf_map_update_elem :
109109+ key_ty:'a typ -> val_ty:'b typ -> bpf_map -> 'a -> 'b (* -> flags *) -> unit
110110+(** [bpf_map_update_elem key_ty val_ty map k v flags] updates the
111111+ value associated the key [k] to [v]. If key is invalid or the
112112+ size of key/value is not in sync, it will return an
113113+ error. [bpf_map_update_elem] expects [key_ty] and [val_ty] to
114114+ verify the types are coherent in your bpf map declaration. *)
···11+open Libbpf
22+open Ctypes
33+44+module RingBuffer = struct
55+ type t = [ `Ring_buffer ] structure ptr
66+77+ type callback =
88+ unit Ctypes_static.ptr -> unit Ctypes_static.ptr -> Unsigned.size_t -> int
99+1010+ let init bpf_map ~callback f =
1111+ (* Coerce it to the static_funptr so it can be passed to the C function *)
1212+ let callback_c =
1313+ coerce
1414+ (Foreign.funptr ~runtime_lock:true ~check_errno:true
1515+ (ptr void @-> ptr void @-> size_t @-> returning int))
1616+ C.Types.ring_buffer_sample_fn callback
1717+ in
1818+ let rb =
1919+ match
2020+ C.Functions.ring_buffer__new bpf_map.fd callback_c null
2121+ (from_voidp C.Types.ring_buffer_opts null)
2222+ with
2323+ | None -> failwith "Failed to create ring buffer\n"
2424+ | Some rb -> rb
2525+ in
2626+ try f rb
2727+ with e ->
2828+ C.Functions.ring_buffer__free rb;
2929+ raise e
3030+3131+ let poll t ~timeout = C.Functions.ring_buffer__poll t timeout
3232+ let consume t = C.Functions.ring_buffer__consume t
3333+ let get_epoll_fd t = C.Functions.ring_buffer__epoll_fd t
3434+end
···11+open Libbpf
22+(** Libbpf_maps provide a convenient API's for handling maps,
33+ currently only Ringbuffers are supported *)
44+55+module RingBuffer : sig
66+ type t
77+88+ type callback =
99+ unit Ctypes_static.ptr -> unit Ctypes_static.ptr -> Unsigned.size_t -> int
1010+1111+ val init : bpf_map -> callback:callback -> (t -> unit) -> unit
1212+ (** [init bpf_map callback] loads [callback] into the ring buffer
1313+ map provided by [bpf_map]. bpf map is freed by default when
1414+ the OCaml process exits
1515+1616+ TO BE ADDED [ctx_ptr] allows the callback function to access
1717+ user provided context. *)
1818+1919+ val poll : t -> timeout:int -> int
2020+ (** [poll t timeout] polls the ringbuffer to execute the loaded
2121+ callbacks on any pending entries, The function returns if
2222+ there are no entries in the given timeout,
2323+2424+ Error code is returned if something went wrong, Ctrl-C will
2525+ cause -EINTR *)
2626+2727+ val consume : t -> int
2828+ (** [consume t] runs callbacks on all entries in the ringbuffer
2929+ without event polling. Use this only if trying to squeeze
3030+ extra performance with busy-waiting.
3131+3232+ Error code is returned if something went wrong Ctrl-C will
3333+ cause -EINTR *)
3434+3535+ val get_epoll_fd : t -> int
3636+ (** [get_epoll_fd t] returns a file descriptor that can be used
3737+ to sleep until data is available in the ring(s) *)
3838+end