1use std::{
4 future::Future,
5 io,
6 mem::MaybeUninit,
7 pin::Pin,
8 task::{Context, Poll},
9};
10
11use bytes::{Bytes, BytesMut};
12use dolang_winterop::security::SecDesc;
13use serde::{Deserialize, Serialize};
14use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite, ReadBuf};
15
16use crate::{
17 client, direct,
18 error::{Error, ErrorKind, HandoffError, Result},
19 metadata::{FsMetadata, Metadata},
20 path,
21 process::{StdioRecv, StdioSend},
22 security::{Acl, AclKind},
23};
24
25mod copy;
26
27pub(crate) use copy::{COPY_LIMIT, FileId};
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum XattrNamespace<'a> {
32 Default,
34 Named(&'a str),
36 Any,
38}
39
40#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
42pub struct XattrEntry {
43 pub(crate) name: String,
45 pub(crate) namespace: Option<String>,
47 pub(crate) size: Option<u64>,
49 pub(crate) flags: Option<u8>,
51}
52
53impl XattrEntry {
54 pub fn name(&self) -> &str {
56 &self.name
57 }
58 pub fn namespace(&self) -> Option<&str> {
60 self.namespace.as_deref()
61 }
62 pub const fn size(&self) -> Option<u64> {
64 self.size
65 }
66 pub const fn flags(&self) -> Option<u8> {
68 self.flags
69 }
70}
71
72#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
74pub struct StreamEntry {
75 pub(crate) name: String,
77 pub(crate) r#type: String,
79 pub(crate) size: u64,
81 pub(crate) alloc_size: u64,
83}
84
85impl StreamEntry {
86 pub fn name(&self) -> &str {
88 &self.name
89 }
90 pub fn stream_type(&self) -> &str {
92 &self.r#type
93 }
94 pub const fn size(&self) -> u64 {
96 self.size
97 }
98 pub const fn alloc_size(&self) -> u64 {
100 self.alloc_size
101 }
102}
103
104bitflags::bitflags! {
105 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
107 pub struct AccessFlags: i32 {
108 const X_OK = 1;
110 const W_OK = 2;
112 const R_OK = 4;
114 const F_OK = 0;
116 }
117}
118
119#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
121pub enum FileLockMode {
122 Exclusive,
124 Shared,
126}
127
128#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
130pub enum FileLockBehavior {
131 Blocking,
133 Try,
135}
136
137#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
139pub struct FileLockRange {
140 pub(crate) start: u64,
142 pub(crate) end: Option<u64>,
144}
145
146impl FileLockRange {
147 pub fn new(start: u64, end: Option<u64>) -> Result<Self> {
151 if end.is_some_and(|end| end < start) {
152 return Err(crate::error::Error::new(
153 crate::error::ErrorKind::InvalidInput,
154 "lock range end precedes its start",
155 ));
156 }
157 Ok(Self { start, end })
158 }
159
160 pub const fn to_eof(start: u64) -> Self {
162 Self { start, end: None }
163 }
164 pub const fn start(self) -> u64 {
166 self.start
167 }
168 pub const fn end(self) -> Option<u64> {
170 self.end
171 }
172 pub fn is_empty(self) -> bool {
174 self.end == Some(self.start)
175 }
176
177 pub(crate) fn conflicts(self, other: Self) -> bool {
178 match (self.is_empty(), other.is_empty()) {
179 (true, true) => return false,
180 (true, false) => {
181 return other.start < self.start && self.start < other.end.unwrap_or(u64::MAX);
182 }
183 (false, true) => {
184 return self.start < other.start && other.start < self.end.unwrap_or(u64::MAX);
185 }
186 (false, false) => {}
187 }
188 self.start < other.end.unwrap_or(u64::MAX) && other.start < self.end.unwrap_or(u64::MAX)
189 }
190}
191
192#[derive(Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize)]
194pub(crate) struct FileLockRequest {
195 pub(crate) range: FileLockRange,
197 pub(crate) mode: FileLockMode,
199 pub(crate) behavior: FileLockBehavior,
201}
202
203impl FileLockRequest {
204 pub(crate) const fn new(
205 range: FileLockRange,
206 mode: FileLockMode,
207 behavior: FileLockBehavior,
208 ) -> Self {
209 Self {
210 range,
211 mode,
212 behavior,
213 }
214 }
215}
216
217pub struct FileLock {
219 inner: Option<FileLockInner>,
220}
221
222enum FileLockInner {
223 Direct(direct::FileLock),
224 Remote(client::FileLock),
225}
226
227impl FileLock {
228 pub(crate) fn direct(lock: direct::FileLock) -> Self {
229 Self {
230 inner: Some(FileLockInner::Direct(lock)),
231 }
232 }
233
234 pub(crate) fn remote(lock: client::FileLock) -> Self {
235 Self {
236 inner: Some(FileLockInner::Remote(lock)),
237 }
238 }
239
240 pub async fn release(&mut self) -> Result<()> {
242 let Some(lock) = self.inner.as_mut() else {
243 return Ok(());
244 };
245 let result = match lock {
246 FileLockInner::Direct(lock) => lock.release().await,
247 FileLockInner::Remote(lock) => lock.release().await,
248 };
249 if result.is_ok() {
250 self.inner = None;
251 }
252 result
253 }
254}
255
256impl std::fmt::Debug for FileLock {
257 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
258 f.debug_struct("FileLock")
259 .field("released", &self.inner.is_none())
260 .finish()
261 }
262}
263#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
270pub enum CopyMode {
271 Auto,
274 Require,
276 Never,
278}
279
280#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
287pub enum CopyDest {
288 At(u64),
290 Append,
295}
296
297#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
299pub struct CopyDataResult {
300 pub count: u64,
302 pub destination_end: Option<u64>,
304}
305
306#[derive(Debug)]
310pub(crate) enum FileInner {
311 Client(client::File),
312 Direct(direct::File),
313}
314
315#[derive(Debug)]
316pub struct File {
317 pub(crate) inner: FileInner,
318}
319
320impl File {
321 pub(crate) fn client(file: client::File) -> Self {
322 Self {
323 inner: FileInner::Client(file),
324 }
325 }
326
327 pub(crate) fn direct(file: direct::File) -> Self {
328 Self {
329 inner: FileInner::Direct(file),
330 }
331 }
332}
333
334pub(crate) enum EitherFuture<L, R> {
342 Left(L),
343 Right(R),
344}
345
346impl<T, L: Future<Output = T>, R: Future<Output = T>> Future for EitherFuture<L, R> {
347 type Output = T;
348
349 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
350 unsafe {
354 match self.get_unchecked_mut() {
355 Self::Left(future) => Pin::new_unchecked(future).poll(cx),
356 Self::Right(future) => Pin::new_unchecked(future).poll(cx),
357 }
358 }
359 }
360}
361
362macro_rules! dispatch_file_mut {
363 ($self:expr, $method:ident($($arg:expr),* $(,)?)) => {{
364 match &mut $self.inner {
365 FileInner::Client(file) => Pin::new(file).$method($($arg),*),
366 FileInner::Direct(file) => Pin::new(file).$method($($arg),*),
367 }
368 }};
369}
370
371macro_rules! match_file {
372 (move $self:expr, $file:ident => $body:expr) => {{
373 match $self.inner {
374 FileInner::Client($file) => $body,
375 FileInner::Direct($file) => $body,
376 }
377 }};
378 ($self:expr, $file:ident => $body:expr) => {{
379 match &$self.inner {
380 FileInner::Client($file) => $body,
381 FileInner::Direct($file) => $body,
382 }
383 }};
384}
385
386impl AsyncRead for File {
387 fn poll_read(
388 mut self: Pin<&mut Self>,
389 cx: &mut Context<'_>,
390 buf: &mut ReadBuf<'_>,
391 ) -> Poll<io::Result<()>> {
392 dispatch_file_mut!(self.as_mut().get_mut(), poll_read(cx, buf))
393 }
394}
395
396impl AsyncWrite for File {
397 fn poll_write(
398 mut self: Pin<&mut Self>,
399 cx: &mut Context<'_>,
400 buf: &[u8],
401 ) -> Poll<io::Result<usize>> {
402 dispatch_file_mut!(self.as_mut().get_mut(), poll_write(cx, buf))
403 }
404
405 fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
406 dispatch_file_mut!(self.as_mut().get_mut(), poll_flush(cx))
407 }
408
409 fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> {
410 dispatch_file_mut!(self.as_mut().get_mut(), poll_shutdown(cx))
411 }
412}
413
414impl AsyncSeek for File {
415 fn start_seek(mut self: Pin<&mut Self>, position: io::SeekFrom) -> io::Result<()> {
416 dispatch_file_mut!(self.as_mut().get_mut(), start_seek(position))
417 }
418
419 fn poll_complete(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
420 dispatch_file_mut!(self.as_mut().get_mut(), poll_complete(cx))
421 }
422}
423
424pub(crate) fn rewrap<I, O>(error: HandoffError<I>, wrap: impl FnOnce(I) -> O) -> HandoffError<O> {
427 let (handle, error) = error.into_parts();
428 HandoffError::new(wrap(handle), error)
429}
430
431impl File {
432 pub async fn into_stdio_send(
452 self,
453 offset: u64,
454 ) -> std::result::Result<StdioSend, HandoffError<Self>> {
455 match self.inner {
456 FileInner::Client(file) => file
457 .into_stdio_send(offset)
458 .await
459 .map_err(|error| rewrap(error, Self::client)),
460 FileInner::Direct(file) => file
461 .into_stdio_send(offset)
462 .await
463 .map_err(|error| rewrap(error, Self::direct)),
464 }
465 }
466
467 pub async fn into_stdio_recv(
470 self,
471 offset: u64,
472 ) -> std::result::Result<StdioRecv, HandoffError<Self>> {
473 match self.inner {
474 FileInner::Client(file) => file
475 .into_stdio_recv(offset)
476 .await
477 .map_err(|error| rewrap(error, Self::client)),
478 FileInner::Direct(file) => file
479 .into_stdio_recv(offset)
480 .await
481 .map_err(|error| rewrap(error, Self::direct)),
482 }
483 }
484
485 pub async fn close(self) -> Result<()> {
487 match_file!(move self, file => file.close().await)
488 }
489
490 pub fn read_at<'b>(
498 &self,
499 buf: &'b mut BytesMut,
500 offset: u64,
501 ) -> impl Future<Output = Result<usize>> + Send + use<'b> {
502 match &self.inner {
503 FileInner::Client(file) => EitherFuture::Left(file.read_at(buf, offset)),
504 FileInner::Direct(file) => EitherFuture::Right(file.read_at(buf, offset)),
505 }
506 }
507
508 pub fn write_at(
513 &self,
514 data: Bytes,
515 offset: u64,
516 ) -> impl Future<Output = Result<usize>> + Send + use<> {
517 match &self.inner {
518 FileInner::Client(file) => EitherFuture::Left(file.write_at(data, offset)),
519 FileInner::Direct(file) => EitherFuture::Right(file.write_at(data, offset)),
520 }
521 }
522
523 pub fn append(&self, data: Bytes) -> impl Future<Output = Result<(usize, u64)>> + Send + use<> {
527 match &self.inner {
528 FileInner::Client(file) => EitherFuture::Left(file.append(data)),
529 FileInner::Direct(file) => EitherFuture::Right(file.append(data)),
530 }
531 }
532
533 pub fn read_at_into<'b>(
536 &self,
537 buf: &'b mut [MaybeUninit<u8>],
538 offset: u64,
539 ) -> impl Future<Output = Result<usize>> + Send + use<'b> {
540 match &self.inner {
541 FileInner::Client(file) => EitherFuture::Left(file.read_at_into(buf, offset)),
542 FileInner::Direct(file) => EitherFuture::Right(file.read_at_into(buf, offset)),
543 }
544 }
545
546 pub fn write_at_from<'b>(
552 &self,
553 data: &'b [u8],
554 offset: u64,
555 ) -> impl Future<Output = Result<usize>> + Send + use<'b> {
556 match &self.inner {
557 FileInner::Client(file) => EitherFuture::Left(file.write_at_from(data, offset)),
558 FileInner::Direct(file) => EitherFuture::Right(file.write_at_from(data, offset)),
559 }
560 }
561
562 pub async fn copy_data(
571 &self,
572 dst: &File,
573 src_offset: u64,
574 target: CopyDest,
575 len: Option<u64>,
576 mode: CopyMode,
577 ) -> Result<CopyDataResult> {
578 self.check_overlap(dst, src_offset, target, len).await?;
579 let len = len.map(|len| len.min(copy::COPY_LIMIT));
580 match (&self.inner, &dst.inner) {
581 (FileInner::Client(src), FileInner::Client(dst)) if src.can_copy_data_with(dst) => {
584 src.copy_data(dst, src_offset, target, len, mode).await
585 }
586 #[cfg(any(
587 target_os = "linux",
588 target_os = "freebsd",
589 target_os = "macos",
590 windows
591 ))]
592 (FileInner::Direct(src_direct), FileInner::Direct(dst_direct))
593 if matches!(target, CopyDest::At(_)) =>
594 {
595 if src_direct.is_regular().await? && dst_direct.is_regular().await? {
596 src_direct
597 .copy_data(dst_direct, src_offset, target, len, mode)
598 .await
599 } else if mode == CopyMode::Require {
600 Err(Error::new(
601 ErrorKind::Unsupported,
602 "block sharing is not supported for this copy",
603 ))
604 } else {
605 copy::copy_chunked(self, dst, src_offset, target, len).await
606 }
607 }
608 _ => {
609 if mode == CopyMode::Require {
615 return Err(Error::new(
616 ErrorKind::Unsupported,
617 "block sharing is not supported for this copy",
618 ));
619 }
620 copy::copy_chunked(self, dst, src_offset, target, len).await
621 }
622 }
623 }
624
625 async fn check_overlap(
636 &self,
637 dst: &File,
638 src_offset: u64,
639 target: CopyDest,
640 len: Option<u64>,
641 ) -> Result<()> {
642 let same = copy::same_opaque(self, dst)
643 || match (copy::identity(self).await, copy::identity(dst).await) {
644 (Some(src), Some(dst)) => src == dst,
645 _ => false,
646 };
647 if !same {
648 return Ok(());
649 }
650 let size = match (len, target) {
654 (Some(_), CopyDest::At(_)) => 0,
655 _ => self.metadata().await?.len(),
656 };
657 let src_end = match len {
658 Some(len) => src_offset.saturating_add(len),
659 None => size.max(src_offset),
660 };
661 let dst_start = match target {
662 CopyDest::At(offset) => offset,
663 CopyDest::Append => size,
664 };
665 let dst_end = dst_start.saturating_add(src_end - src_offset);
666 if dst_start < src_end && src_offset < dst_end {
667 return Err(Error::new(
668 ErrorKind::InvalidInput,
669 "source and destination regions of the same file overlap",
670 ));
671 }
672 Ok(())
673 }
674
675 pub async fn set_size(&self, size: u64) -> Result<()> {
677 match_file!(self, file => file.set_size(size).await)
678 }
679
680 pub async fn sync(&self, data: bool) -> Result<()> {
692 match_file!(self, file => file.sync(data).await)
693 }
694
695 pub async fn metadata(&self) -> Result<Metadata> {
697 match_file!(self, file => file.metadata().await)
698 }
699
700 pub async fn fs_metadata(&self) -> Result<FsMetadata> {
702 match_file!(self, file => file.fs_metadata().await)
703 }
704
705 pub async fn acl(&self, kind: AclKind, default: bool) -> Result<Option<Acl>> {
709 match_file!(self, file => file.acl(kind, default).await)
710 }
711
712 pub async fn set_acl(&self, kind: AclKind, acl: Option<&Acl>, default: bool) -> Result<()> {
716 match_file!(self, file => file.set_acl(kind, acl, default).await)
717 }
718
719 pub async fn sec_desc(&self, mask: dolang_winterop::security::SecInfo) -> Result<SecDesc> {
721 match_file!(self, file => file.sec_desc(mask).await)
722 }
723
724 pub async fn update_sec_desc(&self, sec_desc: &SecDesc) -> Result<()> {
726 match_file!(self, file => file.update_sec_desc(sec_desc).await)
727 }
728
729 pub async fn xattrs(&self, namespace: XattrNamespace<'_>) -> Result<Vec<XattrEntry>> {
731 match_file!(self, file => file.xattrs(namespace).await)
732 }
733
734 pub async fn xattr(&self, name: &str, namespace: Option<&str>) -> Result<Vec<u8>> {
736 match_file!(self, file => file.xattr(name, namespace).await)
737 }
738
739 pub async fn streams(&self) -> Result<Vec<StreamEntry>> {
741 match_file!(self, file => file.streams().await)
742 }
743
744 pub async fn set_xattr(&self, name: &str, namespace: Option<&str>, value: &[u8]) -> Result<()> {
746 match_file!(self, file => file.set_xattr(name, namespace, value).await)
747 }
748
749 pub async fn remove_xattr(&self, name: &str, namespace: Option<&str>) -> Result<()> {
751 match_file!(self, file => file.remove_xattr(name, namespace).await)
752 }
753
754 pub async fn lock(
756 &self,
757 range: FileLockRange,
758 mode: FileLockMode,
759 behavior: FileLockBehavior,
760 ) -> Result<Option<FileLock>> {
761 let request = FileLockRequest::new(range, mode, behavior);
762 match_file!(self, file => file.lock(request).await)
763 }
764
765 pub async fn try_into_std(self) -> std::result::Result<std::fs::File, Self> {
767 match self.inner {
768 FileInner::Client(file) => file.try_into_std().await.map_err(Self::client),
769 FileInner::Direct(file) => file.try_into_std().await.map_err(Self::direct),
770 }
771 }
772}
773
774enum OpenOptionsInner<'a> {
776 Client(client::OpenOptions<'a>),
777 Direct(direct::OpenOptions),
778}
779
780pub struct OpenOptions<'a> {
781 inner: OpenOptionsInner<'a>,
782}
783
784impl<'a> OpenOptions<'a> {
785 pub(crate) fn client(options: client::OpenOptions<'a>) -> Self {
786 Self {
787 inner: OpenOptionsInner::Client(options),
788 }
789 }
790
791 pub(crate) fn direct(options: direct::OpenOptions) -> Self {
792 Self {
793 inner: OpenOptionsInner::Direct(options),
794 }
795 }
796}
797
798impl OpenOptions<'_> {
799 pub fn read(&mut self, read: bool) -> &mut Self {
801 match &mut self.inner {
802 OpenOptionsInner::Client(opts) => {
803 opts.read(read);
804 }
805 OpenOptionsInner::Direct(opts) => {
806 opts.read(read);
807 }
808 }
809 self
810 }
811
812 pub fn write(&mut self, write: bool) -> &mut Self {
814 match &mut self.inner {
815 OpenOptionsInner::Client(opts) => {
816 opts.write(write);
817 }
818 OpenOptionsInner::Direct(opts) => {
819 opts.write(write);
820 }
821 }
822 self
823 }
824
825 pub fn append(&mut self, append: bool) -> &mut Self {
827 match &mut self.inner {
828 OpenOptionsInner::Client(opts) => {
829 opts.append(append);
830 }
831 OpenOptionsInner::Direct(opts) => {
832 opts.append(append);
833 }
834 }
835 self
836 }
837
838 pub fn create(&mut self, create: bool) -> &mut Self {
840 match &mut self.inner {
841 OpenOptionsInner::Client(opts) => {
842 opts.create(create);
843 }
844 OpenOptionsInner::Direct(opts) => {
845 opts.create(create);
846 }
847 }
848 self
849 }
850
851 pub fn create_new(&mut self, create_new: bool) -> &mut Self {
853 match &mut self.inner {
854 OpenOptionsInner::Client(opts) => {
855 opts.create_new(create_new);
856 }
857 OpenOptionsInner::Direct(opts) => {
858 opts.create_new(create_new);
859 }
860 }
861 self
862 }
863
864 pub fn truncate(&mut self, truncate: bool) -> &mut Self {
866 match &mut self.inner {
867 OpenOptionsInner::Client(opts) => {
868 opts.truncate(truncate);
869 }
870 OpenOptionsInner::Direct(opts) => {
871 opts.truncate(truncate);
872 }
873 }
874 self
875 }
876
877 pub fn no_follow(&mut self, no_follow: bool) -> &mut Self {
879 match &mut self.inner {
880 OpenOptionsInner::Client(opts) => {
881 opts.no_follow(no_follow);
882 }
883 OpenOptionsInner::Direct(opts) => {
884 opts.no_follow(no_follow);
885 }
886 }
887 self
888 }
889
890 pub async fn open(&self, path: path::Path<'_>) -> Result<File> {
892 match &self.inner {
893 OpenOptionsInner::Client(opts) => client::OpenOptions::open(opts, path).await,
894 OpenOptionsInner::Direct(opts) => direct::OpenOptions::open(opts, path)
895 .await
896 .map(File::direct),
897 }
898 }
899}
900
901#[cfg(test)]
902mod tests {
903 use super::FileLockRange;
904 use crate::error::ErrorKind;
905
906 #[test]
907 fn lock_range_construction_validates_order() {
908 let range = FileLockRange::new(4, Some(8)).unwrap();
909 assert_eq!(range.start(), 4);
910 assert_eq!(range.end(), Some(8));
911 assert!(!range.is_empty());
912 assert!(
913 FileLockRange::new(8, Some(4))
914 .is_err_and(|error| error.kind() == ErrorKind::InvalidInput)
915 );
916 assert_eq!(FileLockRange::to_eof(4).end(), None);
917 }
918}