1#[cfg(unix)]
4use crate::io_buffers::IoBuffer;
5use crate::io_buffers::{IoVector, IoVectorMut};
6#[cfg(unix)]
7use crate::misc_helpers::while_eintr;
8use crate::misc_helpers::ResultErrorContext;
9use crate::storage::drivers::CommonStorageHelper;
10use crate::storage::ext::write_full_zeroes;
11use crate::storage::PreallocateMode;
12use crate::{Storage, StorageCreateOptions, StorageOpenOptions};
13use cfg_if::cfg_if;
14use maybe_async::maybe_async;
15use std::fmt::{self, Display, Formatter};
16use std::io::{self, Write};
17#[cfg(any(target_os = "linux", target_os = "macos"))]
18use std::os::fd::AsRawFd;
19#[cfg(unix)]
20use std::os::unix::fs::FileTypeExt;
21#[cfg(all(unix, not(target_os = "macos")))]
22use std::os::unix::fs::OpenOptionsExt;
23#[cfg(windows)]
24use std::os::windows::fs::{FileExt, OpenOptionsExt};
25#[cfg(windows)]
26use std::os::windows::io::AsRawHandle;
27use std::path::{Path, PathBuf};
28use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
29use std::sync::RwLock;
30use std::{cmp, fs};
31#[cfg(unix)]
32use tracing::{debug, warn};
33#[cfg(windows)]
34use windows_sys::Win32::System::Ioctl::{FILE_ZERO_DATA_INFORMATION, FSCTL_SET_ZERO_DATA};
35#[cfg(windows)]
36use windows_sys::Win32::System::IO::DeviceIoControl;
37
38#[derive(Debug)]
40pub struct File {
41 file: RwLock<fs::File>,
43
44 filename: Option<PathBuf>,
46
47 req_align: usize,
49
50 mem_align: usize,
52
53 zero_align: usize,
55
56 discard_align: usize,
58
59 size: AtomicU64,
63
64 common_storage_helper: CommonStorageHelper,
66
67 #[cfg(target_os = "macos")]
69 relaxed_sync: bool,
70
71 discard_unsupported: AtomicBool,
73}
74
75impl TryFrom<fs::File> for File {
76 type Error = io::Error;
77
78 fn try_from(file: fs::File) -> io::Result<Self> {
85 Self::new(
86 file,
87 None,
88 false,
89 #[cfg(target_os = "macos")]
90 false,
91 )
92 }
93}
94
95#[maybe_async(AFIT)]
96impl Storage for File {
97 async fn open(opts: StorageOpenOptions) -> io::Result<Self> {
98 Self::do_open_sync(opts, fs::OpenOptions::new())
99 }
100
101 #[cfg(feature = "sync-wrappers")]
102 fn open_sync(opts: StorageOpenOptions) -> io::Result<Self> {
103 Self::do_open_sync(opts, fs::OpenOptions::new())
104 }
105
106 async fn create_open(opts: StorageCreateOptions) -> io::Result<Self> {
107 let opts = opts.modify_open_opts(|o| o.write(true));
109 let size = opts.size;
110 let prealloc_mode = opts.prealloc_mode;
111
112 let mut file_opts = fs::OpenOptions::new();
113 if opts.overwrite {
114 file_opts.create(true).truncate(true);
115 } else {
116 file_opts.create_new(true);
117 };
118
119 let file = Self::do_open_sync(opts.get_open_options(), file_opts)?;
120 if size > 0 {
121 file.resize(size, prealloc_mode)
122 .await
123 .err_context(|| "Resizing file")?;
124 }
125
126 Ok(file)
127 }
128
129 fn mem_align(&self) -> usize {
130 self.mem_align
131 }
132
133 fn req_align(&self) -> usize {
134 self.req_align
135 }
136
137 fn zero_align(&self) -> usize {
138 cmp::max(self.zero_align, self.req_align)
139 }
140
141 fn discard_align(&self) -> usize {
142 cmp::max(self.discard_align, self.req_align)
143 }
144
145 fn size(&self) -> io::Result<u64> {
146 Ok(self.size.load(Ordering::Relaxed))
147 }
148
149 fn resolve_relative_path<P: AsRef<Path>>(&self, relative: P) -> io::Result<PathBuf> {
150 let relative = relative.as_ref();
151
152 if relative.is_absolute() {
153 return Ok(relative.to_path_buf());
154 }
155
156 let filename = self
157 .filename
158 .as_ref()
159 .ok_or_else(|| io::Error::other("No filename set for base image"))?;
160
161 let dirname = filename
162 .parent()
163 .ok_or_else(|| io::Error::other("Invalid base image filename set"))?;
164
165 Ok(dirname.join(relative))
166 }
167
168 fn get_filename(&self) -> Option<PathBuf> {
169 self.filename.as_ref().cloned()
170 }
171
172 #[cfg(unix)]
173 async unsafe fn pure_readv(
174 &self,
175 mut bufv: IoVectorMut<'_>,
176 mut offset: u64,
177 ) -> io::Result<()> {
178 while !bufv.is_empty() {
179 let iovec = unsafe { bufv.as_iovec() };
180 let preadv_offset = offset
181 .try_into()
182 .map_err(|_| io::Error::other("Read offset overflow"))?;
183
184 let len = while_eintr(|| unsafe {
185 libc::preadv(
186 self.file.read().unwrap().as_raw_fd(),
187 iovec.as_ptr(),
188 iovec.len() as libc::c_int,
189 preadv_offset,
190 )
191 })? as u64;
192
193 if len == 0 {
194 bufv.fill(0);
196 break;
197 }
198
199 bufv = bufv.split_tail_at(len);
200 offset = offset
201 .checked_add(len)
202 .ok_or_else(|| io::Error::other("Read offset overflow"))?;
203 }
204
205 Ok(())
206 }
207
208 #[cfg(windows)]
209 async unsafe fn pure_readv(&self, bufv: IoVectorMut<'_>, mut offset: u64) -> io::Result<()> {
210 for mut buffer in bufv.into_inner() {
211 let mut buffer: &mut [u8] = &mut buffer;
212 while !buffer.is_empty() {
213 let len = if offset >= self.size.load(Ordering::Relaxed) {
214 buffer.fill(0);
215 buffer.len()
216 } else {
217 self.file.write().unwrap().seek_read(buffer, offset)?
218 };
219 offset = offset
220 .checked_add(len as u64)
221 .ok_or_else(|| io::Error::other("Read offset overflow"))?;
222 buffer = buffer.split_at_mut(len).1;
223 }
224 }
225 Ok(())
226 }
227
228 #[cfg(unix)]
229 async unsafe fn pure_writev(&self, mut bufv: IoVector<'_>, mut offset: u64) -> io::Result<()> {
230 while !bufv.is_empty() {
231 let iovec = unsafe { bufv.as_iovec() };
232 let pwritev_offset = offset
233 .try_into()
234 .map_err(|_| io::Error::other("Write offset overflow"))?;
235
236 let len = while_eintr(|| unsafe {
237 libc::pwritev(
238 self.file.read().unwrap().as_raw_fd(),
239 iovec.as_ptr(),
240 iovec.len() as libc::c_int,
241 pwritev_offset,
242 )
243 })? as u64;
244
245 if len == 0 {
246 return Err(io::ErrorKind::WriteZero.into());
248 }
249
250 bufv = bufv.split_tail_at(len);
251 offset = offset
252 .checked_add(len)
253 .ok_or_else(|| io::Error::other("Write offset overflow"))?;
254 self.size.fetch_max(offset, Ordering::Relaxed);
255 }
256
257 Ok(())
258 }
259
260 #[cfg(windows)]
261 async unsafe fn pure_writev(&self, bufv: IoVector<'_>, mut offset: u64) -> io::Result<()> {
262 for buffer in bufv.into_inner() {
263 let mut buffer: &[u8] = &buffer;
264 while !buffer.is_empty() {
265 let len = self.file.write().unwrap().seek_write(buffer, offset)?;
266 offset = offset
267 .checked_add(len as u64)
268 .ok_or_else(|| io::Error::other("Write offset overflow"))?;
269 self.size.fetch_max(offset, Ordering::Relaxed);
270 buffer = buffer.split_at(len).1;
271 }
272 }
273 Ok(())
274 }
275
276 async unsafe fn pure_write_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
277 self.discard_to_zero(offset, length).await
278 }
279
280 #[cfg(target_os = "linux")]
281 async unsafe fn pure_write_allocated_zeroes(&self, offset: u64, length: u64) -> io::Result<()> {
282 let offset: libc::off_t = offset
283 .try_into()
284 .map_err(|e| io::Error::other(format!("Discard/write-zeroes offset error: {e}")))?;
285 let length: libc::off_t = length
286 .try_into()
287 .map_err(|e| io::Error::other(format!("Discard/write-zeroes length error: {e}")))?;
288
289 let file = self.file.read().unwrap();
290 while_eintr(|| unsafe {
292 libc::fallocate(file.as_raw_fd(), libc::FALLOC_FL_ZERO_RANGE, offset, length)
293 })
294 .map_err(Self::map_os_err)?;
295
296 Ok(())
297 }
298
299 async unsafe fn pure_discard(&self, offset: u64, length: u64) -> io::Result<()> {
300 if let Err(err) = self.discard_to_zero(offset, length).await {
301 if err.kind() == io::ErrorKind::Unsupported {
305 Ok(())
306 } else {
307 Err(err)
308 }
309 } else {
310 Ok(())
311 }
312 }
313
314 async fn flush(&self) -> io::Result<()> {
315 self.file.write().unwrap().flush()
316 }
317
318 async fn sync(&self) -> io::Result<()> {
319 #[cfg(target_os = "macos")]
320 if self.relaxed_sync {
321 while_eintr(|| unsafe { libc::fsync(self.file.write().unwrap().as_raw_fd()) })?;
323 return Ok(());
324 }
325 self.file.write().unwrap().sync_all()
326 }
327
328 async unsafe fn invalidate_cache(&self) -> io::Result<()> {
329 Ok(())
335 }
336
337 fn get_storage_helper(&self) -> &CommonStorageHelper {
338 &self.common_storage_helper
339 }
340
341 async fn resize(&self, new_size: u64, prealloc_mode: PreallocateMode) -> io::Result<()> {
342 let file = self.file.write().unwrap();
343 let current_size = self.size.load(Ordering::Relaxed);
344
345 match new_size.cmp(¤t_size) {
346 std::cmp::Ordering::Equal => return Ok(()),
347 std::cmp::Ordering::Less => {
348 file.set_len(new_size)?;
349 self.size.fetch_min(new_size, Ordering::Relaxed);
350 return Ok(());
351 }
352 std::cmp::Ordering::Greater => (), }
354
355 match prealloc_mode {
356 PreallocateMode::None | PreallocateMode::Zero => file.set_len(new_size)?,
357 PreallocateMode::Allocate => {
358 #[cfg(not(unix))]
359 return Err(io::ErrorKind::Unsupported.into());
360
361 #[cfg(all(unix, not(target_os = "macos")))]
362 {
363 let ofs = current_size.try_into().map_err(io::Error::other)?;
364 let len = (new_size - current_size)
365 .try_into()
366 .map_err(io::Error::other)?;
367 while_eintr(|| unsafe { libc::fallocate(file.as_raw_fd(), 0, ofs, len) })
368 .map_err(Self::map_os_err)?;
369 }
370
371 #[cfg(target_os = "macos")]
372 {
373 let mut params = libc::fstore_t {
378 fst_flags: libc::F_ALLOCATEALL,
379 fst_posmode: libc::F_PEOFPOSMODE,
380 fst_offset: 0,
381 fst_length: (new_size - current_size)
382 .try_into()
383 .map_err(io::Error::other)?,
384 fst_bytesalloc: 0, };
386 while_eintr(|| unsafe {
387 libc::fcntl(file.as_raw_fd(), libc::F_PREALLOCATE, &mut params)
388 })
389 .map_err(Self::map_os_err)?;
390
391 file.set_len(new_size)?;
392 }
393 }
394 PreallocateMode::WriteData => {
395 drop(file);
398 write_full_zeroes(self, current_size, new_size - current_size).await?;
399 }
400 }
401
402 self.size.fetch_max(new_size, Ordering::Relaxed);
403 Ok(())
404 }
405}
406
407#[maybe_async]
408impl File {
409 fn new(
414 mut file: fs::File,
415 filename: Option<PathBuf>,
416 direct_io: bool,
417 #[cfg(target_os = "macos")] relaxed_sync: bool,
418 ) -> io::Result<Self> {
419 let size = get_file_size(&file).err_context(|| "Failed to determine file size")?;
420
421 #[cfg(all(unix, not(target_os = "macos")))]
422 let direct_io = direct_io || {
423 let res = unsafe { libc::fcntl(file.as_raw_fd(), libc::F_GETFL) };
425 res > 0 && (res & libc::O_DIRECT) != 0
426 };
427
428 let (min_req_align, min_mem_align) = if direct_io {
429 #[cfg(unix)]
430 {
431 (
432 Self::get_min_dio_req_align(&file),
433 Self::get_min_dio_mem_align(&file),
434 )
435 }
436
437 #[cfg(not(unix))]
438 {
439 (1, 1)
440 } } else {
442 (1, 1)
443 };
444
445 let (req_align, mem_align, zero_align, discard_align) =
446 Self::probe_alignments(&mut file, min_req_align, min_mem_align);
447 assert!(req_align.is_power_of_two());
448 assert!(mem_align.is_power_of_two());
449
450 Ok(File {
451 file: RwLock::new(file),
452 filename,
453 req_align,
454 mem_align,
455 zero_align,
456 discard_align,
457 size: size.into(),
458 common_storage_helper: Default::default(),
459 #[cfg(target_os = "macos")]
460 relaxed_sync,
461 discard_unsupported: AtomicBool::new(false),
462 })
463 }
464
465 #[cfg(unix)]
469 fn probe_alignments(
470 file: &mut fs::File,
471 min_req_align: usize,
472 min_mem_align: usize,
473 ) -> (usize, usize, usize, usize) {
474 let mut page_size = page_size::get();
475 if !page_size.is_power_of_two() {
476 let assume = page_size.checked_next_power_of_two().unwrap_or(4096);
477 let assume = cmp::max(4096, assume);
478 warn!("Reported page size of {page_size} is not a power of two, assuming {assume}");
479 page_size = assume;
480 }
481
482 #[cfg(any(target_os = "linux", target_os = "macos"))]
483 let (mut zero_align, mut discard_align) = {
484 let mut statfs: libc::statfs = unsafe { std::mem::zeroed() };
485 match while_eintr(|| unsafe { libc::fstatfs(file.as_raw_fd(), &mut statfs) }) {
487 #[cfg(target_os = "linux")]
490 Ok(_) => (statfs.f_frsize as usize, statfs.f_frsize as usize),
491 #[cfg(target_os = "macos")]
492 Ok(_) => (statfs.f_bsize as usize, statfs.f_bsize as usize),
493
494 Err(_) => (page_size, page_size),
495 }
496 };
497
498 #[cfg(not(any(target_os = "linux", target_os = "macos")))]
499 let (mut zero_align, mut discard_align) = (page_size, page_size);
500
501 if !zero_align.is_power_of_two() {
503 zero_align = page_size;
504 }
505 if !discard_align.is_power_of_two() {
506 discard_align = page_size;
507 }
508
509 let mut writable = true;
510
511 let max_req_align = 65536;
512 let max_mem_align = cmp::max(page_size, max_req_align);
513
514 let safe_req_align = 4096;
516 let safe_mem_align = cmp::max(page_size, safe_req_align);
517
518 let mut test_buf = match IoBuffer::new(max_mem_align, max_mem_align) {
519 Ok(buf) => buf,
520 Err(err) => {
521 warn!(
522 "Failed to allocate memory to probe request alignment ({err}), \
523 falling back to {safe_req_align}/{safe_mem_align}"
524 );
525 return (safe_req_align, safe_mem_align, zero_align, discard_align);
526 }
527 };
528
529 let mut req_align: usize = min_req_align;
530 let result = loop {
531 assert!(req_align <= max_mem_align);
532 match Self::probe_access(
533 file,
534 test_buf.as_mut_range(0..req_align).into_slice(),
535 req_align.try_into().unwrap(),
536 &mut writable,
537 ) {
538 Ok(true) => break Ok(req_align),
539 Ok(false) => {
540 if req_align >= max_req_align {
541 break Err(io::Error::other(format!(
542 "Maximum I/O alignment ({max_req_align}) exceeded"
543 )));
544 }
545 if req_align == min_req_align {
547 req_align = cmp::max(min_req_align << 1, 512);
548 } else {
549 req_align <<= 1;
550 }
551 }
552 Err(err) => break Err(err),
553 }
554 };
555
556 let req_align = match result {
557 Ok(align) => {
558 debug!("Probed request alignment: {align}");
559 align
560 }
561 Err(err) => {
562 let align = cmp::max(req_align, safe_req_align);
564 warn!(
565 "Failed to probe request alignment ({err}; {}), falling back to {align} bytes",
566 err.kind(),
567 );
568 align
569 }
570 };
571
572 let mut mem_align: usize = min_mem_align;
573 let result = loop {
574 assert!(mem_align <= max_mem_align);
575 let range = (max_mem_align - mem_align)..max_mem_align;
576 match Self::probe_access(
577 file,
578 test_buf.as_mut_range(range).into_slice(),
579 0,
580 &mut writable,
581 ) {
582 Ok(true) => break Ok(mem_align),
583 Ok(false) => {
584 if mem_align >= max_mem_align {
586 break Err(io::Error::other(format!(
587 "Maximum memory alignment ({max_mem_align}) exceeded"
588 )));
589 }
590 if mem_align == min_mem_align {
592 mem_align = cmp::max(min_mem_align << 1, cmp::min(page_size, 4096));
593 } else {
594 mem_align <<= 1;
595 }
596 }
597 Err(err) => break Err(err),
598 }
599 };
600
601 let mem_align = match result {
602 Ok(align) => {
603 debug!("Probed memory alignment: {align}");
604 align
605 }
606 Err(err) => {
607 let align = cmp::max(mem_align, safe_mem_align);
609 warn!(
610 "Failed to probe memory alignment ({err}; {}), falling back to {align} bytes",
611 err.kind(),
612 );
613 align
614 }
615 };
616
617 (req_align, mem_align, zero_align, discard_align)
618 }
619
620 #[cfg(unix)]
629 fn probe_access(
630 file: &mut fs::File,
631 slice: &mut [u8],
632 offset: libc::off_t,
633 may_write: &mut bool,
634 ) -> io::Result<bool> {
635 let ret = while_eintr(|| unsafe {
638 libc::pread(
639 file.as_raw_fd(),
640 slice.as_mut_ptr() as *mut libc::c_void,
641 slice.len(),
642 offset,
643 )
644 });
645
646 if let Err(err) = ret {
647 if err.raw_os_error() == Some(libc::EINVAL) {
648 return Ok(false);
649 } else {
650 return Err(err);
651 }
652 }
653
654 if !*may_write {
655 return Ok(true);
656 }
657
658 let ret = while_eintr(|| unsafe {
660 libc::pwrite(
661 file.as_raw_fd(),
662 slice.as_ptr() as *const libc::c_void,
663 slice.len(),
664 offset,
665 )
666 });
667
668 if let Err(err) = ret {
669 if err.raw_os_error() == Some(libc::EINVAL) {
670 Ok(false)
671 } else if err.raw_os_error() == Some(libc::EBADF) {
672 *may_write = false;
673 Ok(true)
674 } else {
675 Err(err)
676 }
677 } else {
678 Ok(true)
679 }
680 }
681
682 #[cfg(unix)]
684 fn get_min_dio_req_align(file: &fs::File) -> usize {
685 #[cfg(target_os = "linux")]
686 {
687 let mut alignment = 0;
688 let res = unsafe { ioctl::blksszget(file.as_raw_fd(), &mut alignment) };
689 if res.is_ok() && alignment > 0 {
690 let alignment = alignment as usize;
691 if alignment.is_power_of_two() {
692 return alignment;
693 }
694 }
695 }
696
697 #[cfg(target_os = "macos")]
698 {
699 let mut alignment = 0;
700 let res = unsafe { ioctl::dkiocgetblocksize(file.as_raw_fd(), &mut alignment) };
701 if res.is_ok() && alignment.is_power_of_two() {
702 return alignment as usize;
703 }
704 }
705
706 #[cfg(target_os = "freebsd")]
707 {
708 let mut alignment = 0;
709 let res = unsafe { ioctl::diocgsectorsize(file.as_raw_fd(), &mut alignment) };
710 if res.is_ok() && alignment.is_power_of_two() {
711 return alignment as usize;
712 }
713 }
714
715 1
717 }
718
719 #[cfg(unix)]
721 fn get_min_dio_mem_align(_file: &fs::File) -> usize {
722 1
724 }
725
726 #[cfg(windows)]
730 fn probe_alignments(
731 _file: &mut fs::File,
732 min_req_align: usize,
733 min_mem_align: usize,
734 ) -> (usize, usize, usize, usize) {
735 (
737 cmp::max(min_req_align, 4096),
738 cmp::max(min_mem_align, 4096),
739 1,
740 1,
741 )
742 }
743
744 fn do_open_sync(opts: StorageOpenOptions, base_fs_opts: fs::OpenOptions) -> io::Result<Self> {
746 let Some(filename) = opts.filename else {
747 return Err(io::Error::new(
748 io::ErrorKind::InvalidInput,
749 "Filename required",
750 ));
751 };
752
753 let mut file_opts = base_fs_opts;
754 file_opts.read(true).write(opts.writable);
755 #[cfg(not(target_os = "macos"))]
756 if opts.direct {
757 file_opts.custom_flags(
758 #[cfg(unix)]
759 libc::O_DIRECT,
760 #[cfg(windows)]
761 windows_sys::Win32::Storage::FileSystem::FILE_FLAG_NO_BUFFERING,
762 );
763 }
764
765 let filename_owned = filename.to_owned();
766 let file = file_opts.open(filename)?;
767
768 #[cfg(target_os = "macos")]
769 if opts.direct {
770 while_eintr(|| unsafe { libc::fcntl(file.as_raw_fd(), libc::F_NOCACHE, 1) })
772 .err_context(|| "Failed to disable host cache")?;
773 }
774
775 Self::new(
776 file,
777 Some(filename_owned),
778 opts.direct,
779 #[cfg(target_os = "macos")]
780 opts.relaxed_sync,
781 )
782 }
783
784 #[cfg(unix)]
800 fn map_os_err(err: io::Error) -> io::Error {
801 let Some(raw) = err.raw_os_error() else {
802 return err;
803 };
804
805 let has_kind = err.kind();
806 let want_kind = match raw {
807 #[allow(unreachable_patterns)] libc::ENOTSUP | libc::EOPNOTSUPP | libc::ENODEV | libc::ENXIO | libc::ENOTTY => {
809 io::ErrorKind::Unsupported
810 }
811 _ => has_kind,
812 };
813
814 if has_kind != want_kind {
815 io::Error::new(want_kind, err)
816 } else {
817 err
818 }
819 }
820
821 #[cfg(not(unix))]
825 fn map_os_err(err: io::Error) -> io::Error {
826 err
827 }
828
829 fn try_discard_by_truncate(&self, offset: u64, length: u64) -> io::Result<bool> {
837 #[allow(clippy::readonly_write_lock)]
839 let file = self.file.write().unwrap();
840
841 let size = self.size.load(Ordering::Relaxed);
842 if offset >= size {
843 return Ok(true);
845 }
846
847 let end = offset.checked_add(length).unwrap_or(size);
850 if end < size {
851 return Ok(false);
852 }
853
854 file.set_len(offset)?;
855 Ok(true)
856 }
857
858 async fn discard_to_zero(&self, offset: u64, length: u64) -> io::Result<()> {
860 if self.try_discard_by_truncate(offset, length)? {
861 return Ok(());
862 }
863
864 if self.discard_unsupported.load(Ordering::Relaxed) {
865 Err(io::ErrorKind::Unsupported.into())
866 } else if let Err(err) = self.discard_to_zero_os_specific(offset, length).await {
867 if err.kind() == io::ErrorKind::Unsupported {
868 self.discard_unsupported.store(true, Ordering::Relaxed);
869 }
870 Err(err)
871 } else {
872 Ok(())
873 }
874 }
875
876 #[cfg(target_os = "linux")]
878 async fn discard_to_zero_os_specific(&self, offset: u64, length: u64) -> io::Result<()> {
879 let offset: libc::off_t = offset
880 .try_into()
881 .map_err(|e| io::Error::other(format!("Discard/write-zeroes offset error: {e}")))?;
882 let length: libc::off_t = length
883 .try_into()
884 .map_err(|e| io::Error::other(format!("Discard/write-zeroes length error: {e}")))?;
885
886 let file = self.file.read().unwrap();
887 while_eintr(|| unsafe {
889 libc::fallocate(
890 file.as_raw_fd(),
891 libc::FALLOC_FL_PUNCH_HOLE | libc::FALLOC_FL_KEEP_SIZE,
892 offset,
893 length,
894 )
895 })
896 .map_err(Self::map_os_err)?;
897
898 Ok(())
899 }
900
901 #[cfg(windows)]
903 async fn discard_to_zero_os_specific(&self, offset: u64, length: u64) -> io::Result<()> {
904 let offset: i64 = offset
905 .try_into()
906 .map_err(|e| io::Error::other(format!("Discard/write-zeroes offset error: {e}")))?;
907 let length: i64 = length
908 .try_into()
909 .map_err(|e| io::Error::other(format!("Discard/write-zeroes length error: {e}")))?;
910
911 let end = offset.saturating_add(length).saturating_add(1);
912 let params = FILE_ZERO_DATA_INFORMATION {
913 FileOffset: offset,
914 BeyondFinalZero: end,
915 };
916 let mut _returned = 0;
917 let file = self.file.read().unwrap();
918 let ret = unsafe {
922 DeviceIoControl(
923 file.as_raw_handle(),
924 FSCTL_SET_ZERO_DATA,
925 (¶ms as *const FILE_ZERO_DATA_INFORMATION).cast::<std::ffi::c_void>(),
926 size_of_val(¶ms) as u32,
927 std::ptr::null_mut(),
928 0,
929 &mut _returned,
930 std::ptr::null_mut(),
931 )
932 };
933 if ret == 0 {
934 return Err(Self::map_os_err(io::Error::last_os_error()));
935 }
936
937 Ok(())
938 }
939
940 #[cfg(target_os = "macos")]
942 async fn discard_to_zero_os_specific(&self, offset: u64, length: u64) -> io::Result<()> {
943 let offset: libc::off_t = offset
944 .try_into()
945 .map_err(|e| io::Error::other(format!("Discard/write-zeroes offset error: {e}")))?;
946 let length: libc::off_t = length
947 .try_into()
948 .map_err(|e| io::Error::other(format!("Discard/write-zeroes length error: {e}")))?;
949
950 let params = libc::fpunchhole_t {
951 fp_flags: 0,
952 reserved: 0,
953 fp_offset: offset,
954 fp_length: length,
955 };
956 let file = self.file.read().unwrap();
957 while_eintr(|| unsafe { libc::fcntl(file.as_raw_fd(), libc::F_PUNCHHOLE, ¶ms) })
959 .map_err(Self::map_os_err)?;
960
961 Ok(())
962 }
963
964 #[cfg(not(any(target_os = "linux", target_os = "macos", windows)))]
966 async fn discard_to_zero_os_specific(&self, offset: u64, length: u64) -> io::Result<()> {
967 Err(io::ErrorKind::Unsupported.into())
968 }
969}
970
971impl Display for File {
972 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
973 if let Some(filename) = self.filename.as_ref() {
974 write!(f, "file:{filename:?}")
975 } else {
976 write!(f, "file:<unknown path>")
977 }
978 }
979}
980
981fn get_file_size(file: &fs::File) -> io::Result<u64> {
986 #[allow(clippy::bind_instead_of_map)]
987 file.metadata().and_then(|m| {
988 #[cfg(unix)]
989 if m.file_type().is_block_device() || m.file_type().is_char_device() {
990 return get_device_size(file);
991 }
992 Ok(m.len())
993 })
994}
995
996cfg_if! {
997 if #[cfg(target_os = "linux")] {
998 fn get_device_size(file: &fs::File) -> io::Result<u64> {
1000 let mut size = 0;
1001 unsafe { ioctl::blkgetsize64(file.as_raw_fd(), &mut size) }?;
1002 Ok(size)
1003 }
1004 } else if #[cfg(target_os = "macos")] {
1005 fn get_device_size(file: &fs::File) -> io::Result<u64> {
1007 let mut block_size = 0;
1008 unsafe { ioctl::dkiocgetblocksize(file.as_raw_fd(), &mut block_size) }?;
1009 let mut block_count = 0;
1010 unsafe { ioctl::dkiocgetblockcount(file.as_raw_fd(), &mut block_count) }?;
1011 Ok(u64::from(block_size) * block_count)
1012 }
1013 } else if #[cfg(target_os = "freebsd")] {
1014 fn get_device_size(file: &fs::File) -> io::Result<u64> {
1016 let mut size = 0;
1017 unsafe { ioctl::diocgmediasize(file.as_raw_fd(), &mut size) }?;
1018 Ok(size as u64)
1019 }
1020 } else if #[cfg(unix)] {
1021 fn get_device_size(_file: &fs::File) -> io::Result<u64> {
1023 Err(io::ErrorKind::Unsupported.into())
1024 }
1025 }
1026}
1027
1028mod ioctl {
1030 #[cfg(unix)]
1031 use nix::ioctl_read;
1032 #[cfg(target_os = "linux")]
1033 use nix::ioctl_read_bad;
1034
1035 #[cfg(target_os = "linux")]
1038 ioctl_read!(blkgetsize64, 0x12, 114, u64);
1039
1040 #[cfg(target_os = "linux")]
1041 ioctl_read_bad!(blksszget, libc::BLKSSZGET, libc::c_int);
1042
1043 #[cfg(target_os = "macos")]
1046 ioctl_read!(dkiocgetblocksize, 'd', 24, u32);
1047
1048 #[cfg(target_os = "macos")]
1049 ioctl_read!(dkiocgetblockcount, 'd', 25, u64);
1050
1051 #[cfg(target_os = "freebsd")]
1054 ioctl_read!(diocgsectorsize, 'd', 128, libc::c_uint);
1055
1056 #[cfg(target_os = "freebsd")]
1057 ioctl_read!(diocgmediasize, 'd', 129, libc::off_t);
1058}