···
; data_dirs : Eio.Fs.dir_ty Eio.Path.t list
22
-
let ensure_dir path = Eio.Path.mkdirs ~exists_ok:true ~perm:0o755 path
22
+
let ensure_dir ?(perm = 0o755) path = Eio.Path.mkdirs ~exists_ok:true ~perm path
24
+
let validate_runtime_base_dir base_path =
25
+
(* Validate the base XDG_RUNTIME_DIR has correct permissions per spec *)
27
+
let path_str = Eio.Path.native_exn base_path in
28
+
let stat = Eio.Path.stat ~follow:true base_path in
29
+
let current_perm = stat.perm land 0o777 in
30
+
if current_perm <> 0o700 then
33
+
"XDG_RUNTIME_DIR base directory %s has incorrect permissions: %o (must be 0700)"
36
+
(* Check ownership - directory should be owned by current user *)
37
+
let uid = Unix.getuid () in
38
+
if stat.uid <> Int64.of_int uid then
41
+
"XDG_RUNTIME_DIR base directory %s not owned by current user (uid %d, owner %Ld)"
45
+
(* TODO: Check that directory is on local filesystem (not networked).
46
+
This would require filesystem type detection which is OS-specific. *)
48
+
| exn -> failwith (Printf.sprintf "Cannot validate XDG_RUNTIME_DIR: %s" (Printexc.to_string exn))
50
+
let ensure_runtime_dir _fs app_runtime_path =
51
+
(* Base directory validation is done in resolve_runtime_dir,
52
+
so we just create the app subdirectory *)
53
+
ensure_dir app_runtime_path
···
| _ -> failwith "Cannot determine home directory"))
let make_env_var_name app_name suffix = String.uppercase_ascii app_name ^ "_" ^ suffix
73
+
exception Invalid_xdg_path of string
75
+
let validate_absolute_path context path =
76
+
if Filename.is_relative path then
77
+
raise (Invalid_xdg_path
78
+
(Printf.sprintf "%s must be an absolute path, got: %s" context path))
let resolve_path fs home_path base_path =
if Filename.is_relative base_path
then Eio.Path.(home_path / base_path)
else Eio.Path.(fs / base_path)
(* Helper to resolve system directories (config_dirs or data_dirs) *)
50
-
let resolve_system_dirs fs home_path app_name override_suffix xdg_var default_paths =
86
+
let resolve_system_dirs fs _home_path app_name override_suffix xdg_var default_paths =
let override_var = make_env_var_name app_name override_suffix in
match Sys.getenv_opt override_var with
| Some dirs when dirs <> "" ->
String.split_on_char ':' dirs
|> List.filter (fun s -> s <> "")
56
-
|> List.map (fun path -> Eio.Path.(resolve_path fs home_path path / app_name))
92
+
|> List.filter_map (fun path ->
94
+
validate_absolute_path override_var path;
95
+
Some (Eio.Path.(fs / path / app_name))
96
+
with Invalid_xdg_path _ -> None)
(match Sys.getenv_opt xdg_var with
| Some dirs when dirs <> "" ->
String.split_on_char ':' dirs
|> List.filter (fun s -> s <> "")
62
-
|> List.map (fun path -> Eio.Path.(resolve_path fs home_path path / app_name))
102
+
|> List.filter_map (fun path ->
104
+
validate_absolute_path xdg_var path;
105
+
Some (Eio.Path.(fs / path / app_name))
106
+
with Invalid_xdg_path _ -> None)
List.map (fun path -> Eio.Path.(fs / path / app_name)) default_paths)
(* Helper to resolve a user directory with override precedence *)
68
-
let resolve_user_dir fs home_path app_name xdg_ctx xdg_getter override_suffix =
111
+
let resolve_user_dir fs _home_path app_name xdg_ctx xdg_getter override_suffix =
let override_var = make_env_var_name app_name override_suffix in
match Sys.getenv_opt override_var with
71
-
| Some dir when dir <> "" -> resolve_path fs home_path dir, Env override_var
114
+
| Some dir when dir <> "" ->
115
+
validate_absolute_path override_var dir;
116
+
Eio.Path.(fs / dir / app_name), Env override_var
| Some _ | None -> Eio.Path.(fs / xdg_getter xdg_ctx / app_name), Default
(* Helper to resolve runtime directory (special case since it can be None) *)
76
-
let resolve_runtime_dir fs home_path app_name xdg_ctx =
120
+
let resolve_runtime_dir fs _home_path app_name xdg_ctx =
let override_var = make_env_var_name app_name "RUNTIME_DIR" in
match Sys.getenv_opt override_var with
79
-
| Some dir when dir <> "" -> Some (resolve_path fs home_path dir), Env override_var
123
+
| Some dir when dir <> "" ->
124
+
validate_absolute_path override_var dir;
125
+
(* Validate the base runtime directory has correct permissions *)
126
+
let base_runtime_dir = Eio.Path.(fs / dir) in
127
+
validate_runtime_base_dir base_runtime_dir;
128
+
Some (Eio.Path.(fs / dir / app_name)), Env override_var
81
-
( Option.map (fun base -> Eio.Path.(fs / base / app_name)) (Xdg.runtime_dir xdg_ctx)
130
+
(match Xdg.runtime_dir xdg_ctx with
132
+
(* Validate the base runtime directory has correct permissions *)
133
+
let base_runtime_dir = Eio.Path.(fs / base) in
134
+
validate_runtime_base_dir base_runtime_dir;
135
+
Some (Eio.Path.(fs / base / app_name))
136
+
| None -> None), Default
138
+
let validate_standard_xdg_vars () =
139
+
(* Validate standard XDG environment variables for absolute paths *)
149
+
List.iter (fun var ->
150
+
match Sys.getenv_opt var with
151
+
| Some value when value <> "" ->
152
+
if String.contains value ':' then
153
+
(* Colon-separated list - validate each part *)
154
+
String.split_on_char ':' value
155
+
|> List.filter (fun s -> s <> "")
156
+
|> List.iter (fun path -> validate_absolute_path var path)
159
+
validate_absolute_path var value
let home_path = get_home_dir fs in
166
+
(* First validate all standard XDG environment variables *)
167
+
validate_standard_xdg_vars ();
let xdg_ctx = Xdg.create ~env:Sys.getenv_opt () in
let config_dir, config_dir_source =
···
129
-
Option.iter ensure_dir runtime_dir;
209
+
Option.iter (ensure_runtime_dir fs) runtime_dir;
···
let app_name t = t.app_name
let config_dir t = t.config_dir
···
let config_dirs t = t.config_dirs
let data_dirs t = t.data_dirs
234
+
(* File search following XDG specification *)
235
+
let find_file_in_dirs dirs filename =
236
+
let rec search_dirs = function
238
+
| dir :: remaining_dirs ->
239
+
let file_path = Eio.Path.(dir / filename) in
241
+
(* Try to check if file exists and is readable *)
242
+
let _ = Eio.Path.stat ~follow:true file_path in
246
+
(* File is inaccessible (non-existent, permissions, etc.)
247
+
Skip and continue with next directory per XDG spec *)
248
+
search_dirs remaining_dirs)
252
+
let find_config_file t filename =
253
+
(* Search user config dir first, then system config dirs *)
254
+
find_file_in_dirs (t.config_dir :: t.config_dirs) filename
256
+
let find_data_file t filename =
257
+
(* Search user data dir first, then system data dirs *)
258
+
find_file_in_dirs (t.data_dir :: t.data_dirs) filename
let pp ?(brief = false) ?(sources = false) ppf t =
let pp_source ppf = function
···
···
402
-
Option.iter ensure_dir runtime_dir;
505
+
Option.iter (ensure_runtime_dir fs) runtime_dir;
···
let app_upper = String.uppercase_ascii app_name in
···
let pp_source ppf = function
···
(pp_with_source "runtime_dir")