···11+## osrelease - detect system and distro information in OCaml
22+33+**Status: WIP**
44+55+This library is used to detect local information about
66+which operating system, architecture, distribution and
77+version the program is running under.
88+
+21
dune-project
···11+(lang dune 2.3)
22+(name osrelease)
33+(generate_opam_files true)
44+55+(source (github avsm/osrelease))
66+(license ISC)
77+(authors "Anil Madhavapeddy <anil@recoil.org>")
88+(maintainers "anil@recoil.org")
99+1010+(package
1111+ (name osrelease)
1212+ (synopsis "Detect operating system, distro and version information")
1313+ (description "
1414+This library is used to detect local information about
1515+which operating system, architecture, distribution and
1616+version the program is running under.")
1717+ (depends
1818+ (ocaml (>= 4.06.0))
1919+ bos
2020+ astring
2121+ ))
···11+(*
22+ * Copyright (c) 2020-2023 Anil Madhavapeddy <anil@recoil.org>
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+ *
1616+ *)
1717+1818+open Astring
1919+open Bos
2020+2121+let ( >>= ) v f = match v with Ok v -> f v | Error _ as e -> e
2222+let ( >>| ) v f = match v with Ok v -> Ok (f v) | Error _ as e -> e
2323+2424+let uname f =
2525+ let cmd = Cmd.(v "uname" % f) in
2626+ OS.Cmd.(
2727+ run_out cmd |> to_string |> function
2828+ | Ok v -> v
2929+ | Error _ -> failwith "error")
3030+3131+module Arch = struct
3232+ type t =
3333+ [ `X86_32
3434+ | `X86_64
3535+ | `Ppc32
3636+ | `Ppc64 of [ `Le | `Be ]
3737+ | `Arm32 of [ `Armv5 | `Armv6 | `Earmv6 | `Armv7 | `Earmv7 ]
3838+ | `Aarch64
3939+ | `Unknown of string ]
4040+4141+ let to_string (x : t) =
4242+ match x with
4343+ | `X86_32 -> "x86_32"
4444+ | `X86_64 -> "x86_64"
4545+ | `Ppc32 -> "ppc32"
4646+ | `Ppc64 `Be -> "ppc64"
4747+ | `Ppc64 `Le -> "ppc64le"
4848+ | `Arm32 _ -> "arm32"
4949+ | `Aarch64 -> "arm64"
5050+ | `Unknown v -> v
5151+5252+ let pp fmt v = Format.pp_print_string fmt (to_string v)
5353+5454+ let of_string v : t =
5555+ match String.Ascii.lowercase v with
5656+ | "x86" | "i386" | "i586" | "i686" -> `X86_32
5757+ | "powerpc" | "ppc" | "ppcle" -> `Ppc32
5858+ | "amd64" | "x86_64" -> `X86_64
5959+ | "ppc64" | "ppc64be" -> `Ppc64 `Be
6060+ | "ppc64le" -> `Ppc64 `Le
6161+ | "aarch64_be" | "aarch64" | "armv8b" | "armv8l" -> `Aarch64
6262+ | "armv5" -> `Arm32 `Armv5
6363+ | "armv6" -> `Arm32 `Armv6
6464+ | "earmv6" -> `Arm32 `Earmv6
6565+ | "armv7" -> `Arm32 `Earmv7
6666+ | v -> `Unknown v
6767+6868+ let v () =
6969+ match Sys.os_type with
7070+ | "Unix" | "Cygwin" -> uname "-m" |> of_string
7171+ | "Win32" when Sys.word_size = 32 (* TODO WoW64? *) -> `X86_32
7272+ | "Win32" -> `X86_64
7373+ | v -> `Unknown v
7474+end
7575+7676+module OS = struct
7777+ type t =
7878+ [ `Linux
7979+ | `MacOS
8080+ | `Win32
8181+ | `Cygwin
8282+ | `FreeBSD
8383+ | `OpenBSD
8484+ | `DragonFly
8585+ | `Unknown of string ]
8686+8787+ let to_string (v : t) =
8888+ match v with
8989+ | `Linux -> "linux"
9090+ | `MacOS -> "macos"
9191+ | `Win32 -> "win32"
9292+ | `Cygwin -> "cygwin"
9393+ | `FreeBSD -> "freebsd"
9494+ | `OpenBSD -> "openbsd"
9595+ | `DragonFly -> "dragonfly"
9696+ | `Unknown v -> v
9797+9898+ let of_string v : t =
9999+ match String.Ascii.lowercase v with
100100+ | "darwin" | "osx" -> `MacOS
101101+ | "linux" -> `Linux
102102+ | "win32" -> `Win32
103103+ | "cygwin" -> `Cygwin
104104+ | "freebsd" -> `FreeBSD
105105+ | "openbsd" -> `OpenBSD
106106+ | "dragonfly" -> `DragonFly
107107+ | v -> `Unknown v
108108+109109+ let pp fmt v = Format.pp_print_string fmt (to_string v)
110110+111111+ let v () =
112112+ match Sys.os_type with
113113+ | "Unix" -> uname "-s" |> of_string
114114+ | v -> of_string v
115115+end
116116+117117+module Distro = struct
118118+ type linux =
119119+ [ `Arch
120120+ | `Alpine
121121+ | `CentOS
122122+ | `Debian
123123+ | `Fedora
124124+ | `Gentoo
125125+ | `Mageia
126126+ | `NixOS
127127+ | `OracleLinux
128128+ | `RHEL
129129+ | `Ubuntu
130130+ | `OpenSUSE
131131+ | `Android
132132+ | `Other of string ]
133133+134134+ type macos = [ `Homebrew | `MacPorts | `None ]
135135+ type windows = [ `Cygwin | `None ]
136136+137137+ type t =
138138+ [ `Linux of linux
139139+ | `MacOS of macos
140140+ | `Windows of windows
141141+ | `Other of string ]
142142+143143+ let linux_to_string (x : linux) =
144144+ match x with
145145+ | `Alpine -> "alpine"
146146+ | `Android -> "android"
147147+ | `Arch -> "arch"
148148+ | `CentOS -> "centos"
149149+ | `Debian -> "debian"
150150+ | `Fedora -> "fedora"
151151+ | `Gentoo -> "gentoo"
152152+ | `Mageia -> "mageia"
153153+ | `NixOS -> "nixos"
154154+ | `OpenSUSE -> "opensuse"
155155+ | `OracleLinux -> "oraclelinux"
156156+ | `RHEL -> "rhel"
157157+ | `Ubuntu -> "ubuntu"
158158+ | `Other v -> v
159159+160160+ let linux_of_string = function
161161+ | "alpine" -> `Alpine
162162+ | "android" -> `Android
163163+ | "arch" -> `Arch
164164+ | "centos" -> `CentOS
165165+ | "debian" -> `Debian
166166+ | "fedora" -> `Fedora
167167+ | "gentoo" -> `Gentoo
168168+ | "mageia" -> `Mageia
169169+ | "nixos" -> `NixOS
170170+ | "opensuse" -> `OpenSUSE
171171+ | "oraclelinux" -> `OracleLinux
172172+ | "rhel" -> `RHEL
173173+ | "ubuntu" -> `Ubuntu
174174+ | v -> `Other v
175175+176176+ let macos_to_string (x : macos) =
177177+ match x with
178178+ | `Homebrew -> "homebrew"
179179+ | `MacPorts -> "macports"
180180+ | `None -> "macos"
181181+182182+ let macos_of_string = function
183183+ | "homebrew" -> `Homebrew
184184+ | "macports" -> `MacPorts
185185+ | _ -> `None
186186+187187+ let windows_to_string (x : windows) =
188188+ match x with `Cygwin -> "cygwin" | `None -> "windows"
189189+190190+ let windows_of_string = function "cygwin" -> `Cygwin | _ -> `None
191191+192192+ let to_string (x : t) =
193193+ match x with
194194+ | `Linux v -> "linux " ^ linux_to_string v
195195+ | `MacOS v -> "macos " ^ macos_to_string v
196196+ | `Windows v -> "windows " ^ windows_to_string v
197197+ | `Other v -> "unknown " ^ v
198198+199199+ let of_string x =
200200+ try
201201+ Scanf.sscanf x "%s %s" (fun a b ->
202202+ match a with
203203+ | "linux" -> `Linux (linux_of_string b)
204204+ | "macos" -> `MacOS (macos_of_string b)
205205+ | "windows" -> `Windows (windows_of_string b)
206206+ | _ -> `Other b)
207207+ with _ -> `Other ""
208208+209209+ let pp fmt v = Format.pp_print_string fmt (to_string v)
210210+211211+ let android_release =
212212+ lazy
213213+ (let open Bos in
214214+ let cmd = Cmd.(v "getprop" % "ro.build.version.release") in
215215+ match OS.Cmd.(run_out cmd |> to_string) with
216216+ | Ok "" -> None
217217+ | Ok out -> Some out
218218+ | Error _ -> None)
219219+220220+ let find_first_file files = List.find_opt Sys.file_exists files
221221+222222+ let os_release_fields =
223223+ lazy
224224+ (let os_release_file =
225225+ find_first_file [ "/etc/os-release"; "/usr/lib/os-release" ]
226226+ in
227227+ match os_release_file with
228228+ | None -> Error (`Msg "no os-release file found")
229229+ | Some file ->
230230+ Bos.OS.File.fold_lines
231231+ (fun acc line ->
232232+ let open Scanf in
233233+ try
234234+ sscanf line "%s@= %s" (fun k v ->
235235+ try sscanf v "\"%s@\"" (fun s -> (k, s) :: acc)
236236+ with Scan_failure _ | End_of_file -> acc)
237237+ with Scan_failure _ | End_of_file -> acc)
238238+ [] (Fpath.v file))
239239+240240+ let os_release_field f =
241241+ Lazy.force os_release_fields >>| List.assoc_opt f >>| function
242242+ | Some "" -> None
243243+ | v -> v
244244+245245+ let identify_linux () =
246246+ os_release_field "ID" >>= function
247247+ | Some v -> Ok (Some v)
248248+ | None -> (
249249+ let cmd = Cmd.(v "lsb_release" % "-i" % "-s") in
250250+ Bos.OS.Cmd.(run_out cmd |> to_string) |> function
251251+ | Ok v -> Ok (Some (String.Ascii.lowercase v))
252252+ | Error _ -> (
253253+ let issue =
254254+ find_first_file
255255+ [
256256+ "/etc/redhat-release";
257257+ "/etc/centos-release";
258258+ "/etc/gentoo-release";
259259+ "/etc/issue";
260260+ ]
261261+ in
262262+ match issue with
263263+ | None -> Ok None
264264+ | Some f -> (
265265+ Bos.OS.File.read (Fpath.v f) >>= fun v ->
266266+ match Scanf.sscanf v " %s " (fun x -> x) with
267267+ | "" -> Ok None
268268+ | v -> Ok (Some (String.Ascii.lowercase v))
269269+ | exception Not_found -> Ok None)))
270270+271271+ let v () : (t, [ `Msg of string ]) result =
272272+ match OS.v () with
273273+ | `MacOS -> (
274274+ Bos.OS.Cmd.exists (Cmd.v "brew") >>= function
275275+ | true -> Ok (`MacOS `Homebrew)
276276+ | false -> (
277277+ Bos.OS.Cmd.exists (Cmd.v "port") >>= function
278278+ | true -> Ok (`MacOS `MacPorts)
279279+ | false -> Ok (`MacOS `None)))
280280+ | `Linux -> (
281281+ match Lazy.force android_release with
282282+ | Some _ -> Ok (`Linux `Android)
283283+ | None -> (
284284+ identify_linux () >>= function
285285+ | None -> Ok (`Linux (`Other ""))
286286+ | Some v -> Ok (`Linux (`Other v))))
287287+ | _ -> Error (`Msg "foo")
288288+end
289289+290290+module Version = struct
291291+ let detect_linux_version () =
292292+ match Lazy.force Distro.android_release with
293293+ | Some r -> Ok (Some r)
294294+ | None -> (
295295+ let cmd = Cmd.(v "lsb_release" % "-r" % "-s") in
296296+ Bos.OS.Cmd.(run_out cmd |> to_string) |> function
297297+ | Ok v -> Ok (Some v)
298298+ | Error _ -> Distro.os_release_field "VERSION_ID")
299299+300300+ let detect_macos_version () =
301301+ let cmd = Cmd.(v "sw_vers" % "-productVersion") in
302302+ Bos.OS.Cmd.(run_out cmd |> to_string) >>| function
303303+ | "" -> None
304304+ | v -> Some v
305305+306306+ let detect_freebsd_version () =
307307+ let cmd = Cmd.(v "uname" % "-U") in
308308+ Bos.OS.Cmd.(run_out cmd |> to_string) >>| function
309309+ | "" -> None
310310+ | v -> Some v
311311+312312+ let v () =
313313+ match OS.v () with
314314+ | `Linux -> detect_linux_version ()
315315+ | `MacOS -> detect_macos_version ()
316316+ | `FreeBSD -> detect_freebsd_version ()
317317+ | `Win32 | `OpenBSD | `DragonFly | `Cygwin ->
318318+ Error (`Msg "Version detection on this platform not yet supported")
319319+ | `Unknown _ -> Ok None
320320+end
+92
lib/osrelease.mli
···11+(*
22+ * Copyright (c) 2020 Anil Madhavapeddy <anil@recoil.org>
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+ *
1616+ *)
1717+1818+module Arch : sig
1919+ type t =
2020+ [ `Aarch64
2121+ | `Arm32 of [ `Armv5 | `Armv6 | `Armv7 | `Earmv6 | `Earmv7 ]
2222+ | `Ppc32
2323+ | `Ppc64 of [ `Be | `Le ]
2424+ | `Unknown of string
2525+ | `X86_32
2626+ | `X86_64 ]
2727+2828+ val to_string : t -> string
2929+ val of_string : string -> t
3030+ val pp : Format.formatter -> t -> unit
3131+ val v : unit -> t
3232+end
3333+3434+module OS : sig
3535+ type t =
3636+ [ `Cygwin
3737+ | `DragonFly
3838+ | `FreeBSD
3939+ | `Linux
4040+ | `MacOS
4141+ | `OpenBSD
4242+ | `Unknown of string
4343+ | `Win32 ]
4444+4545+ val to_string : t -> string
4646+ val of_string : string -> t
4747+ val pp : Format.formatter -> t -> unit
4848+ val v : unit -> t
4949+end
5050+5151+module Distro : sig
5252+ type linux =
5353+ [ `Alpine
5454+ | `Android
5555+ | `Arch
5656+ | `CentOS
5757+ | `Debian
5858+ | `Fedora
5959+ | `Gentoo
6060+ | `Mageia
6161+ | `NixOS
6262+ | `OpenSUSE
6363+ | `OracleLinux
6464+ | `Other of string
6565+ | `RHEL
6666+ | `Ubuntu ]
6767+6868+ type macos = [ `Homebrew | `MacPorts | `None ]
6969+ type windows = [ `Cygwin | `None ]
7070+7171+ type t =
7272+ [ `Linux of linux
7373+ | `MacOS of macos
7474+ | `Other of string
7575+ | `Windows of windows ]
7676+7777+ val linux_to_string : linux -> string
7878+ val linux_of_string : string -> linux
7979+ val macos_to_string : macos -> string
8080+ val macos_of_string : string -> macos
8181+ val windows_to_string : windows -> string
8282+ val windows_of_string : string -> windows
8383+ val to_string : t -> string
8484+ val of_string : string -> t
8585+ val pp : Format.formatter -> t -> unit
8686+ val os_release_field : string -> (string option, [> `Msg of string ]) result
8787+ val v : unit -> (t, [ `Msg of string ]) result
8888+end
8989+9090+module Version : sig
9191+ val v : unit -> (string option, [> `Msg of string ]) result
9292+end
+34
osrelease.opam
···11+# This file is generated by dune, edit dune-project instead
22+opam-version: "2.0"
33+synopsis: "Detect operating system, distro and version information"
44+description: """
55+66+This library is used to detect local information about
77+which operating system, architecture, distribution and
88+version the program is running under."""
99+maintainer: ["anil@recoil.org"]
1010+authors: ["Anil Madhavapeddy <anil@recoil.org>"]
1111+license: "ISC"
1212+homepage: "https://github.com/avsm/osrelease"
1313+bug-reports: "https://github.com/avsm/osrelease/issues"
1414+depends: [
1515+ "dune" {>= "2.3"}
1616+ "ocaml" {>= "4.06.0"}
1717+ "bos"
1818+ "astring"
1919+]
2020+build: [
2121+ ["dune" "subst"] {pinned}
2222+ [
2323+ "dune"
2424+ "build"
2525+ "-p"
2626+ name
2727+ "-j"
2828+ jobs
2929+ "@install"
3030+ "@runtest" {with-test}
3131+ "@doc" {with-doc}
3232+ ]
3333+]
3434+dev-repo: "git+https://github.com/avsm/osrelease.git"