Skip to main content

pray_core/
render_write.rs

1use crate::lockfile::Lockfile;
2use crate::paths::validate_destination_path;
3use crate::render::RenderedTarget;
4use crate::render_file::{
5    create_regular_bytes, destination_kind, open_regular, read_destination_bytes,
6    read_regular_bytes, symlink_error, DestinationKind,
7};
8use crate::render_path_guard::ensure_safe_destination_ancestors;
9use crate::resolve::ResolvedProject;
10use crate::{PrayError, PrayResult};
11use std::fs;
12use std::io::{Seek, Write};
13use std::path::Path;
14
15fn layout_rendered_content(path: &Path, display: &str, rendered: &str) -> PrayResult<String> {
16    match destination_kind(path)? {
17        DestinationKind::Missing => Ok(rendered.to_string()),
18        DestinationKind::Regular => {
19            let bytes = read_regular_bytes(path, display)?;
20            let existing =
21                String::from_utf8(bytes).map_err(|error| PrayError::Render(error.to_string()))?;
22            Ok(crate::render_patch::patch_rendered_content(
23                &existing, rendered,
24            ))
25        }
26        DestinationKind::Symlink => Err(symlink_error(display)),
27        DestinationKind::Other => Err(PrayError::Render(format!(
28            "refusing to write `{display}`; destination is not a regular file"
29        ))),
30    }
31}
32
33fn write_rendered_content(path: &Path, display: &str, rendered: &str) -> PrayResult<()> {
34    match destination_kind(path)? {
35        DestinationKind::Missing => create_regular_bytes(path, display, rendered.as_bytes()),
36        DestinationKind::Regular => {
37            let mut file = open_regular(path, display, true)?;
38            let existing = String::from_utf8(read_destination_bytes(&mut file, display)?)
39                .map_err(|error| PrayError::Render(error.to_string()))?;
40            let content = crate::render_patch::patch_rendered_content(&existing, rendered);
41            if crate::transaction::replace(
42                path,
43                Some(existing.as_bytes()),
44                Some(content.as_bytes()),
45            )? {
46                return Ok(());
47            }
48            file.rewind()?;
49            file.set_len(0)?;
50            file.write_all(content.as_bytes())?;
51            Ok(())
52        }
53        DestinationKind::Symlink => Err(symlink_error(display)),
54        DestinationKind::Other => Err(PrayError::Render(format!(
55            "refusing to write `{display}`; destination is not a regular file"
56        ))),
57    }
58}
59
60pub fn layout_rendered_targets(
61    project: &ResolvedProject,
62    rendered: &[RenderedTarget],
63) -> PrayResult<Vec<RenderedTarget>> {
64    let mut laid_out = Vec::with_capacity(rendered.len());
65    for target in rendered {
66        let relative = validate_destination_path(&target.path.to_string_lossy())?;
67        ensure_safe_destination_ancestors(
68            &project.project_root,
69            relative.as_path(),
70            relative.as_str(),
71        )?;
72        let path = relative.join_root(&project.project_root);
73        let content = layout_rendered_content(&path, relative.as_str(), &target.content)?;
74        let managed_spans =
75            crate::render_relocate::relocate_managed_spans(&content, &target.managed_spans);
76        laid_out.push(RenderedTarget {
77            path: target.path.clone(),
78            content,
79            managed_spans,
80        });
81    }
82    Ok(laid_out)
83}
84
85pub fn write_rendered_targets(
86    project: &ResolvedProject,
87    rendered: &[RenderedTarget],
88) -> PrayResult<()> {
89    write_rendered_targets_with_previous_lockfile(project, rendered, None)
90}
91
92pub fn write_rendered_targets_with_previous_lockfile(
93    project: &ResolvedProject,
94    rendered: &[RenderedTarget],
95    previous_lockfile: Option<&Lockfile>,
96) -> PrayResult<()> {
97    crate::transaction::run(&project.project_root, || {
98        write_project_files(project, rendered, previous_lockfile)
99    })
100}
101
102fn write_project_files(
103    project: &ResolvedProject,
104    rendered: &[RenderedTarget],
105    previous_lockfile: Option<&Lockfile>,
106) -> PrayResult<()> {
107    crate::render_dest::provisioned_destination_statuses(project, previous_lockfile)?;
108    if project.manifest.render.conflict == "fail" {
109        if let Some(lockfile) = previous_lockfile {
110            crate::render_conflict::reject_managed_span_conflicts(&project.project_root, lockfile)?;
111        }
112    }
113    for target in rendered {
114        let relative = validate_destination_path(&target.path.to_string_lossy())?;
115        ensure_safe_destination_ancestors(
116            &project.project_root,
117            relative.as_path(),
118            relative.as_str(),
119        )?;
120        let path = relative.join_root(&project.project_root);
121        if let Some(parent) = path.parent() {
122            fs::create_dir_all(parent)?;
123        }
124        ensure_safe_destination_ancestors(
125            &project.project_root,
126            relative.as_path(),
127            relative.as_str(),
128        )?;
129        write_rendered_content(&path, relative.as_str(), &target.content)?;
130    }
131    crate::render::materialize_provisioned_exports(project, previous_lockfile)?;
132    Ok(())
133}