1use std::fs::File;
9use std::io::{Read, Seek, SeekFrom, Write};
10use std::path::Path;
11
12pub(crate) fn create(path: &Path, force: bool) -> Result<File, String> {
15 let mut options = std::fs::OpenOptions::new();
16 options.read(true).write(true);
17 if force {
18 options.create(true).truncate(true);
19 } else {
20 options.create_new(true);
21 }
22 options.open(path).map_err(|e| match e.kind() {
23 std::io::ErrorKind::AlreadyExists => refusal(path),
24 _ => format!("Could not create {}: {e}", path.display()),
25 })
26}
27
28pub fn refusal(path: &Path) -> String {
30 format!(
31 "{} is there already; --force overwrites it.",
32 path.display()
33 )
34}
35
36const STDOUT_IS_THE_SCREEN: &str = "--tee - passes standard input on to standard output, \
38 which is the screen here: send it on to a pipe or a file, as in: \
39 some_logger | datui -f --tee - | gzip > run1.csv.gz";
40
41#[cfg(unix)]
46pub(crate) fn pass_stdout_on() -> Result<File, String> {
47 use std::io::IsTerminal;
48 use std::os::fd::{AsRawFd, FromRawFd};
49 if std::io::stdout().is_terminal() {
50 return Err(STDOUT_IS_THE_SCREEN.to_string());
51 }
52 let tty = std::fs::OpenOptions::new()
53 .read(true)
54 .write(true)
55 .open("/dev/tty")
56 .map_err(|e| format!("--tee - draws on the terminal, /dev/tty, which did not open: {e}"))?;
57 let kept = unsafe { libc::fcntl(libc::STDOUT_FILENO, libc::F_DUPFD_CLOEXEC, 3) };
61 if kept < 0 {
62 return Err(format!(
63 "Could not keep standard output: {}",
64 std::io::Error::last_os_error()
65 ));
66 }
67 let kept = unsafe { File::from_raw_fd(kept) };
69 if unsafe { libc::dup2(tty.as_raw_fd(), libc::STDOUT_FILENO) } < 0 {
71 return Err(format!(
72 "Could not draw on the terminal: {}",
73 std::io::Error::last_os_error()
74 ));
75 }
76 Ok(kept)
77}
78
79#[cfg(windows)]
83pub(crate) fn pass_stdout_on() -> Result<File, String> {
84 use std::io::IsTerminal;
85 use std::os::windows::io::{FromRawHandle, IntoRawHandle};
86 use windows_sys::Win32::System::Console::{GetStdHandle, STD_OUTPUT_HANDLE, SetStdHandle};
87 if std::io::stdout().is_terminal() {
88 return Err(STDOUT_IS_THE_SCREEN.to_string());
89 }
90 let console = std::fs::OpenOptions::new()
91 .read(true)
92 .write(true)
93 .open("CONOUT$")
94 .map_err(|e| format!("--tee - draws on the console, which did not open: {e}"))?;
95 let kept = unsafe { GetStdHandle(STD_OUTPUT_HANDLE) };
97 if kept.is_null() || kept == windows_sys::Win32::Foundation::INVALID_HANDLE_VALUE {
98 return Err(
99 "--tee - passes the stream on to standard output, and there is none.".to_string(),
100 );
101 }
102 let console = console.into_raw_handle();
104 if unsafe { SetStdHandle(STD_OUTPUT_HANDLE, console) } == 0 {
106 return Err(format!(
107 "Could not draw on the console: {}",
108 std::io::Error::last_os_error()
109 ));
110 }
111 Ok(unsafe { File::from_raw_handle(kept) })
114}
115
116#[cfg(not(any(unix, windows)))]
117pub(crate) fn pass_stdout_on() -> Result<File, String> {
118 Err("--tee - is not supported on this platform.".to_string())
119}
120
121#[derive(Debug, PartialEq, Eq)]
123pub(crate) enum Fixed {
124 Nothing,
126 Riff,
128 Rf64,
131 TooLong,
133}
134
135pub(crate) fn fix_wav_sizes(file: &mut File) -> std::io::Result<Fixed> {
140 let len = file.metadata()?.len();
141 let mut head = [0u8; 12];
142 file.seek(SeekFrom::Start(0))?;
143 if len < 12 || file.read_exact(&mut head).is_err() {
144 return Ok(Fixed::Nothing);
145 }
146 let rf64 = &head[0..4] == b"RF64";
147 if !(&head[0..4] == b"RIFF" || rf64) || &head[8..12] != b"WAVE" {
148 return Ok(Fixed::Nothing);
149 }
150 let mut at = 12u64;
152 let mut junk = None;
153 let mut ds64 = None;
154 let data = loop {
155 if at + 8 > len {
156 return Ok(Fixed::Nothing);
157 }
158 let mut chunk = [0u8; 8];
159 file.seek(SeekFrom::Start(at))?;
160 file.read_exact(&mut chunk)?;
161 let size = u32::from_le_bytes([chunk[4], chunk[5], chunk[6], chunk[7]]) as u64;
162 match &chunk[0..4] {
163 b"data" => break at,
164 b"JUNK" if at == 12 => junk = Some(size),
165 b"ds64" => ds64 = Some(at),
166 _ => {}
167 }
168 at += 8 + size + (size & 1);
170 };
171 let data_size = len - (data + 8);
172 let riff_size = len - 8;
173 if let Some(ds64) = ds64.filter(|_| rf64) {
174 file.seek(SeekFrom::Start(ds64 + 8))?;
176 file.write_all(&riff_size.to_le_bytes())?;
177 file.write_all(&data_size.to_le_bytes())?;
178 return Ok(Fixed::Rf64);
179 }
180 if riff_size <= u32::MAX as u64 {
181 if read_u32(file, 4)? as u64 == riff_size && read_u32(file, data + 4)? as u64 == data_size {
182 return Ok(Fixed::Nothing);
183 }
184 write_u32(file, 4, riff_size as u32)?;
185 write_u32(file, data + 4, data_size as u32)?;
186 return Ok(Fixed::Riff);
187 }
188 match junk {
191 Some(size) if size >= 28 => {
192 file.seek(SeekFrom::Start(0))?;
193 file.write_all(b"RF64")?;
194 file.write_all(&u32::MAX.to_le_bytes())?;
195 file.seek(SeekFrom::Start(12))?;
196 file.write_all(b"ds64")?;
197 file.write_all(&(size as u32).to_le_bytes())?;
198 file.write_all(&riff_size.to_le_bytes())?;
199 file.write_all(&data_size.to_le_bytes())?;
200 file.write_all(&0u64.to_le_bytes())?;
202 file.write_all(&0u32.to_le_bytes())?;
203 write_u32(file, data + 4, u32::MAX)?;
204 Ok(Fixed::Rf64)
205 }
206 _ => Ok(Fixed::TooLong),
207 }
208}
209
210fn read_u32(file: &mut File, at: u64) -> std::io::Result<u32> {
211 let mut b = [0u8; 4];
212 file.seek(SeekFrom::Start(at))?;
213 file.read_exact(&mut b)?;
214 Ok(u32::from_le_bytes(b))
215}
216
217fn write_u32(file: &mut File, at: u64, value: u32) -> std::io::Result<()> {
218 file.seek(SeekFrom::Start(at))?;
219 file.write_all(&value.to_le_bytes())
220}
221
222#[cfg(test)]
223mod tests;