1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
(* src/pipeline/builder_copy.ml *)
open Ast
open Builder_utils
open Builder_logs
let pipeline_copy ?(node_name=None) ?(target_dir="pipeline-output") ?(dir_mode="0755") ?(file_mode="0644") () =
let is_valid_mode mode =
let len = String.length mode in
let rec all_octal idx =
if idx >= len then true
else
let c = mode.[idx] in
c >= '0' && c <= '7' && all_octal (idx + 1)
in
(* Accept 4 or 5 chars so common forms like 0755 and 00755 both validate,
while still restricting input to safe octal chmod modes. *)
(len = 4 || len = 5) && mode.[0] = '0' && all_octal 1
in
if not (is_valid_mode dir_mode && is_valid_mode file_mode) then
Error.make_error GenericError
"Invalid file or directory mode: expected octal string like 0755 or 0644."
else
let logs = get_logs () in
match logs with
| [] -> Error.make_error FileError "No build logs found in `_pipeline/`. Run `populate_pipeline(p, build=true)` first."
| latest_log :: _ ->
match read_log (Filename.concat pipeline_dir latest_log) with
| Error msg -> Error.make_error FileError (Printf.sprintf "Failed to read log `%s`: %s" latest_log msg)
| Ok entries ->
let () = if not (Sys.file_exists target_dir) then Unix.mkdir target_dir 0o755 in
let copy_item src dest =
let argv = [| "cp"; "-RP"; src; dest |] in
match run_command_argv_exit argv with
| Ok code -> code
| Error _ -> 1
in
let apply_perms path =
(* Use argv-based find+chmod to avoid shell injection via path *)
ignore (run_command_argv_exit [| "find"; path; "-type"; "d"; "-exec"; "chmod"; dir_mode; "{}"; "+" |]);
ignore (run_command_argv_exit [| "find"; path; "-type"; "f"; "-exec"; "chmod"; file_mode; "{}"; "+" |])
in
let nodes_to_copy = match node_name with
| None -> entries
| Some name ->
(match List.assoc_opt name entries with
| Some cn -> [(name, cn)]
| None -> [])
in
if nodes_to_copy = [] then
match node_name with
| Some name -> Error.make_error KeyError (Printf.sprintf "Node `%s` not found in build log." name)
| None -> Error.make_error GenericError "No nodes found to copy."
else
let errors = ref [] in
let success_count = ref 0 in
List.iter (fun (name, cn) ->
let src_node_dir = Filename.dirname cn.cn_path in
let dest_node_dir = Filename.concat target_dir name in
if not (Sys.file_exists src_node_dir) then
errors := (Printf.sprintf "Source path `%s` for node `%s` does not exist." src_node_dir name) :: !errors
else begin
if Sys.file_exists dest_node_dir then begin
ignore (run_command_argv_exit [| "rm"; "-rf"; dest_node_dir |])
end;
let exit_code = copy_item src_node_dir dest_node_dir in
if exit_code <> 0 then
errors := (Printf.sprintf "Failed to copy node `%s` (exit code %d)." name exit_code) :: !errors
else begin
apply_perms dest_node_dir;
incr success_count
end
end
) nodes_to_copy;
if !errors <> [] && !success_count = 0 then
Error.make_error FileError (String.concat "; " !errors)
else
let msg = Printf.sprintf "Successfully copied %d node(s) to `%s`." !success_count target_dir in
if !errors <> [] then
VString (msg ^ " Warning: " ^ (String.concat "; " !errors))
else
VString msg