1use crate::{PrayError, PrayResult};
2use std::fs::{self, File, OpenOptions};
3use std::io::{ErrorKind, Read, Write};
4use std::path::Path;
5
6pub(crate) fn create_regular_bytes(path: &Path, display: &str, bytes: &[u8]) -> PrayResult<()> {
7 if crate::transaction::replace(path, None, Some(bytes))? {
8 return Ok(());
9 }
10 let mut options = OpenOptions::new();
11 options.write(true).create_new(true);
12 add_no_follow(&mut options);
13 let mut file = options
14 .open(path)
15 .map_err(|error| map_open_error(error, display))?;
16 file.write_all(bytes)?;
17 Ok(())
18}
19
20pub(crate) fn read_regular_bytes(path: &Path, display: &str) -> PrayResult<Vec<u8>> {
21 let mut file = open_regular(path, display, false)?;
22 read_destination_bytes(&mut file, display)
23}
24
25pub fn read_destination_text(path: &Path) -> PrayResult<String> {
26 String::from_utf8(read_regular_bytes(path, &path.display().to_string())?)
27 .map_err(|error| PrayError::Render(error.to_string()))
28}
29
30pub(crate) const MAX_DESTINATION_BYTES: u64 = 32 * 1024 * 1024;
31
32pub(crate) fn read_destination_bytes(file: &mut File, display: &str) -> PrayResult<Vec<u8>> {
33 if file.metadata()?.len() > MAX_DESTINATION_BYTES {
34 return Err(destination_size_error(display));
35 }
36 let mut bytes = Vec::new();
37 file.take(MAX_DESTINATION_BYTES + 1)
38 .read_to_end(&mut bytes)?;
39 if bytes.len() as u64 > MAX_DESTINATION_BYTES {
40 return Err(destination_size_error(display));
41 }
42 Ok(bytes)
43}
44
45fn destination_size_error(display: &str) -> PrayError {
46 PrayError::Render(format!(
47 "refusing to read `{display}`; destination exceeds the 32 MiB limit"
48 ))
49}
50
51pub(crate) fn open_regular(path: &Path, display: &str, writable: bool) -> PrayResult<File> {
52 let mut options = OpenOptions::new();
53 options.read(true).write(writable);
54 add_no_follow(&mut options);
55 let file = options
56 .open(path)
57 .map_err(|error| map_open_error(error, display))?;
58 if !file.metadata()?.is_file() {
59 return Err(PrayError::Render(format!(
60 "refusing to write `{display}`; destination is not a regular file"
61 )));
62 }
63 Ok(file)
64}
65
66#[cfg(unix)]
67fn add_no_follow(options: &mut OpenOptions) {
68 use std::os::unix::fs::OpenOptionsExt;
69 options.custom_flags(libc::O_NOFOLLOW);
70}
71
72#[cfg(not(unix))]
73fn add_no_follow(_options: &mut OpenOptions) {}
74
75fn map_open_error(error: std::io::Error, display: &str) -> PrayError {
76 #[cfg(unix)]
77 if error.raw_os_error() == Some(libc::ELOOP) {
78 return symlink_error(display);
79 }
80 error.into()
81}
82
83pub(crate) fn symlink_error(display: &str) -> PrayError {
84 PrayError::Render(format!(
85 "refusing to write `{display}` because it is a symbolic link"
86 ))
87}
88
89pub(crate) enum DestinationKind {
90 Missing,
91 Regular,
92 Symlink,
93 Other,
94}
95
96pub(crate) fn destination_kind(path: &Path) -> PrayResult<DestinationKind> {
97 match fs::symlink_metadata(path) {
98 Ok(metadata) if metadata.file_type().is_symlink() => Ok(DestinationKind::Symlink),
99 Ok(metadata) if metadata.is_file() => Ok(DestinationKind::Regular),
100 Ok(_) => Ok(DestinationKind::Other),
101 Err(error) if error.kind() == ErrorKind::NotFound => Ok(DestinationKind::Missing),
102 Err(error) => Err(error.into()),
103 }
104}