1use std::fs::{File, OpenOptions};
5use std::io::{self, Write};
6use std::path::Path;
7
8pub fn create_new(path: &Path) -> io::Result<File> {
9 let mut options = OpenOptions::new();
10 options.write(true).create_new(true);
11 no_follow(&mut options);
12 options.open(path)
13}
14
15pub fn append(path: &Path) -> io::Result<File> {
16 reject_alias(path)?;
17 let mut options = OpenOptions::new();
18 options.create(true).append(true);
19 no_follow(&mut options);
20 options.open(path)
21}
22
23pub fn read_write(path: &Path) -> io::Result<File> {
24 reject_alias(path)?;
25 let mut options = OpenOptions::new();
26 options.create(true).read(true).write(true);
27 no_follow(&mut options);
28 options.open(path)
29}
30
31pub fn write_atomic(path: &Path, content: &[u8]) -> io::Result<()> {
32 reject_alias(path)?;
33 let parent = path
34 .parent()
35 .ok_or_else(|| io::Error::other("path has no parent"))?;
36 std::fs::create_dir_all(parent)?;
37 let mut tmp = tempfile::NamedTempFile::new_in(parent)?;
38 tmp.write_all(content)?;
39 tmp.as_file().sync_all().ok();
40 tmp.persist(path).map_err(|error| error.error)?;
41 Ok(())
42}
43
44pub fn reject_alias(path: &Path) -> io::Result<()> {
45 if std::fs::symlink_metadata(path).is_ok_and(|metadata| metadata.file_type().is_symlink()) {
46 return Err(io::Error::new(
47 io::ErrorKind::PermissionDenied,
48 "refusing to write through symlink",
49 ));
50 }
51 reject_hardlink(path)
52}
53
54#[cfg(unix)]
55pub fn reject_hardlink(path: &Path) -> io::Result<()> {
56 use std::os::unix::fs::MetadataExt;
57 match std::fs::symlink_metadata(path) {
58 Ok(metadata) if metadata.is_file() && metadata.nlink() > 1 => Err(io::Error::new(
59 io::ErrorKind::PermissionDenied,
60 "refusing to write through hard link",
61 )),
62 Ok(_) => Ok(()),
63 Err(error) if error.kind() == io::ErrorKind::NotFound => Ok(()),
64 Err(error) => Err(error),
65 }
66}
67
68#[cfg(not(unix))]
69pub fn reject_hardlink(_path: &Path) -> io::Result<()> {
70 Ok(())
71}
72
73#[cfg(unix)]
74fn no_follow(options: &mut OpenOptions) {
75 use std::os::unix::fs::OpenOptionsExt;
76 options.custom_flags(libc::O_NOFOLLOW);
77}
78
79#[cfg(not(unix))]
80fn no_follow(_options: &mut OpenOptions) {}