1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
//! syscall for filesystem.

use std::{
    fs::{FileType, Metadata, Permissions},
    io::{self, SeekFrom},
    sync::OnceLock,
    task::Waker,
};

use bitmask_enum::bitmask;

use crate::path::{Path, PathBuf};
use crate::{CancelablePoll, Handle};

/// A bitmask for open file.
///
/// See [`open_file`](FileSystem::open_file) for more information.
#[bitmask(u8)]
pub enum FileOpenMode {
    /// Configures the option for append mode.
    ///
    /// When set to true, this option means the file will be writable after opening
    /// and the file cursor will be moved to the end of file before every write operaiton.
    Append,
    /// Configures the option for write mode.
    /// If the file already exists, write calls on it will overwrite the previous contents without truncating it.
    Writable,
    /// Configures the option for read mode.
    /// When set to true, this option means the file will be readable after opening.
    Readable,

    /// Configures the option for creating a new file if it doesn’t exist.
    /// When set to true, this option means a new file will be created if it doesn’t exist.
    /// The file must be opened in [`Writable`](FileOpenMode::Writable)
    /// or [`Append`](FileOpenMode::Append) mode for file creation to work.
    Create,

    /// Configures the option for creating a new file or failing if it already exists.
    /// When set to true, this option means a new file will be created, or the open
    /// operation will fail if the file already exists.
    /// The file must be opened in [`Writable`](FileOpenMode::Writable)
    /// or [`Append`](FileOpenMode::Append) mode for file creation to work.
    CreateNew,

    /// Configures the option for truncating the previous file.
    /// When set to true, the file will be truncated to the length of 0 bytes.
    /// The file must be opened in [`Writable`](FileOpenMode::Writable)
    /// or [`Append`](FileOpenMode::Append) mode for file creation to work.
    Truncate,
}

/// Filesystem-related system call interface
pub trait FileSystem: Sync + Send {
    /// Opens a file with [`FileOpenMode`]
    fn open_file(
        &self,
        waker: Waker,
        path: &Path,
        open_mode: &FileOpenMode,
    ) -> CancelablePoll<io::Result<Handle>>;

    /// Write a buffer into this writer, returning how many bytes were written
    fn file_write(
        &self,
        waker: Waker,
        file: &Handle,
        buf: &[u8],
    ) -> CancelablePoll<io::Result<usize>>;

    /// Pull some bytes from this source into the specified buffer, returning how many bytes were read.
    fn file_read(
        &self,
        waker: Waker,
        file: &Handle,
        buf: &mut [u8],
    ) -> CancelablePoll<io::Result<usize>>;

    /// Attempts to sync all OS-internal metadata to disk.
    ///
    /// This function will attempt to ensure that all in-memory data reaches the filesystem before returning.
    ///
    /// This can be used to handle errors that would otherwise only be caught when the File is closed.
    /// Dropping a file will ignore errors in synchronizing this in-memory data.
    fn file_flush(&self, waker: Waker, file: &Handle) -> CancelablePoll<io::Result<()>>;

    /// Seek to an offset, in bytes, in a stream.
    ///
    /// A seek beyond the end of a stream is allowed, but behavior is defined by the implementation.
    ///
    /// If the seek operation completed successfully, this method returns the new position from the
    /// start of the stream. That position can be used later with [`SeekFrom::Start`].
    ///
    /// # Errors
    /// Seeking can fail, for example because it might involve flushing a buffer.
    ///
    /// Seeking to a negative offset is considered an error.
    fn file_seek(
        &self,
        waker: Waker,
        file: &Handle,
        pos: SeekFrom,
    ) -> CancelablePoll<io::Result<u64>>;

    ///  Reads the file's metadata.
    fn file_meta(&self, waker: Waker, file: &Handle) -> CancelablePoll<io::Result<Metadata>>;

    /// Changes the permissions on the file.
    fn file_set_permissions(
        &self,
        waker: Waker,
        file: &Handle,
        perm: &Permissions,
    ) -> CancelablePoll<io::Result<()>>;

    /// Truncates or extends the file.
    ///
    /// If `size` is less than the current file size, then the file will be truncated. If it is
    /// greater than the current file size, then the file will be extended to `size` and have all
    /// intermediate data filled with zeros.
    ///
    /// The file's cursor stays at the same position, even if the cursor ends up being past the end
    /// of the file after this operation.
    ///
    fn file_set_len(
        &self,
        waker: Waker,
        file: &Handle,
        size: u64,
    ) -> CancelablePoll<io::Result<()>>;

    /// Returns the canonical form of a path.
    /// The returned path is in absolute form with all intermediate components
    /// normalized and symbolic links resolved.
    /// This function is an async version of [`std::fs::canonicalize`].
    fn canonicalize(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<PathBuf>>;

    /// Copies the contents and permissions of a file to a new location.
    /// On success, the total number of bytes copied is returned and equals
    /// the length of the to file after this operation.
    /// The old contents of to will be overwritten. If from and to both point
    /// to the same file, then the file will likely get truncated as a result of this operation.
    fn copy(&self, waker: Waker, from: &Path, to: &Path) -> CancelablePoll<io::Result<u64>>;

    /// Creates a new directory.
    /// Note that this function will only create the final directory in path.
    /// If you want to create all of its missing parent directories too, use
    /// the [`create_dir_all`](FileSystem::create_dir_all) function instead.
    ///
    /// This function is an async version of [`std::fs::create_dir`].
    fn create_dir(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<()>>;

    /// Creates a new directory and all of its parents if they are missing.
    /// This function is an async version of [`std::fs::create_dir_all`].
    fn create_dir_all(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<()>>;

    /// Creates a hard link on the filesystem.
    /// The dst path will be a link pointing to the src path. Note that operating
    /// systems often require these two paths to be located on the same filesystem.
    ///
    /// This function is an async version of [`std::fs::hard_link`].
    fn hard_link(&self, waker: Waker, from: &Path, to: &Path) -> CancelablePoll<io::Result<()>>;

    /// Reads metadata for a path.
    /// This function will traverse symbolic links to read metadata for the target
    /// file or directory. If you want to read metadata without following symbolic
    /// links, use symlink_metadata instead.
    ///
    /// This function is an async version of [`std::fs::metadata`].
    fn metadata(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<Metadata>>;

    /// Returns a iterator handle of entries in a directory.
    ///
    /// See [`dir_entry_next`](FileSystem::dir_entry_next) for more information about iteration.
    fn read_dir(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<Handle>>;

    /// Advances the directory entry iterator and returns the next value.
    ///
    /// Returns None when iteration is finished.
    ///
    /// You can create a directory entry iterator by call function [`read_dir`](FileSystem::read_dir)
    fn dir_entry_next(
        &self,
        waker: Waker,
        read_dir_handle: &Handle,
    ) -> CancelablePoll<io::Result<Option<Handle>>>;

    /// Returns the bare name of this entry without the leading path.
    fn dir_entry_file_name(&self, entry: &Handle) -> String;

    /// Returns the full path to this entry.
    /// The full path is created by joining the original path passed to [`read_dir`](FileSystem::read_dir) with the name of this entry.
    fn dir_entry_path(&self, entry: &Handle) -> PathBuf;

    /// Reads the metadata for this entry.
    ///
    /// This function will traverse symbolic links to read the metadata.
    fn dir_entry_metadata(
        &self,
        waker: Waker,
        entry: &Handle,
    ) -> CancelablePoll<io::Result<Metadata>>;

    /// eads the file type for this entry.
    /// This function will not traverse symbolic links if this entry points at one.
    /// If you want to read metadata with following symbolic links, use [`dir_entry_metadata`](FileSystem::dir_entry_metadata) instead.
    fn dir_entry_file_type(
        &self,
        waker: Waker,
        entry: &Handle,
    ) -> CancelablePoll<io::Result<FileType>>;

    /// Reads a symbolic link and returns the path it points to.
    ///
    /// This function is an async version of [`std::fs::read_link`].
    fn read_link(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<PathBuf>>;

    /// Removes an empty directory,
    /// if the `path` is not an empty directory, use the function
    /// [`remove_dir_all`](FileSystem::remove_dir_all) instead.
    ///
    /// This function is an async version of std::fs::remove_dir.
    fn remove_dir(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<()>>;

    /// Removes a directory and all of its contents.
    ///
    /// This function is an async version of [`std::fs::remove_dir_all`].
    fn remove_dir_all(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<()>>;

    /// Removes a file.
    /// This function is an async version of [`std::fs::remove_file`].
    fn remove_file(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<()>>;

    /// Renames a file or directory to a new location.
    /// If a file or directory already exists at the target location, it will be overwritten by this operation.
    /// This function is an async version of std::fs::rename.
    fn rename(&self, waker: Waker, from: &Path, to: &Path) -> CancelablePoll<io::Result<()>>;

    /// Changes the permissions of a file or directory.
    /// This function is an async version of [`std::fs::set_permissions`].
    fn set_permissions(
        &self,
        waker: Waker,
        path: &Path,
        perm: &Permissions,
    ) -> CancelablePoll<io::Result<()>>;

    /// Reads metadata for a path without following symbolic links.
    /// If you want to follow symbolic links before reading metadata of the target file or directory,
    /// use [`metadata`](FileSystem::metadata) instead.
    ///
    /// This function is an async version of [`std::fs::symlink_metadata`].
    fn symlink_metadata(&self, waker: Waker, path: &Path) -> CancelablePoll<io::Result<Metadata>>;
}

static GLOBAL_FILESYSTEM: OnceLock<Box<dyn FileSystem>> = OnceLock::new();

/// Register provided [`FileSystem`] as global filesystem implementation.
///
/// # Panic
///
/// Multiple calls to this function are not permitted!!!
pub fn register_global_filesystem<FS: FileSystem + 'static>(fs: FS) {
    if GLOBAL_FILESYSTEM.set(Box::new(fs)).is_err() {
        panic!("Multiple calls to register_global_filesystem are not permitted!!!");
    }
}

/// Get the globally registered instance of [`FileSystem`].
///
/// # Panic
///
/// You should call [`register_global_filesystem`] first to register implementation,
/// otherwise this function will cause a panic with `Call register_global_filesystem first`
pub fn global_filesystem() -> &'static dyn FileSystem {
    GLOBAL_FILESYSTEM
        .get()
        .expect("Call register_global_filesystem first")
        .as_ref()
}

#[cfg(all(windows, feature = "windows_named_pipe"))]

mod windows {
    use super::*;

    pub trait NamedPipe: Send + Sync {
        /// Opens the named pipe identified by `addr`.
        fn client_open(
            &self,
            waker: Waker,
            addr: &std::ffi::OsStr,
        ) -> CancelablePoll<io::Result<Handle>>;

        /// Creates the named pipe identified by `addr` for use as a server.
        ///
        /// This uses the [`CreateNamedPipe`] function.
        fn server_create(
            &self,
            waker: Waker,
            addr: &std::ffi::OsStr,
        ) -> CancelablePoll<io::Result<Handle>>;

        /// Enables a named pipe server process to wait for a client process to
        /// connect to an instance of a named pipe. A client process connects by
        /// creating a named pipe with the same name.
        fn server_accept(&self, waker: Waker, file: &Handle) -> CancelablePoll<io::Result<()>>;

        /// Disconnects the server end of a named pipe instance from a client
        /// process.
        fn server_disconnect(&self, file: &Handle) -> io::Result<()>;

        /// Write a buffer into this writer, returning how many bytes were written
        fn write(
            &self,
            waker: Waker,
            file: &Handle,
            buf: &[u8],
        ) -> CancelablePoll<io::Result<usize>>;

        /// Pull some bytes from this source into the specified buffer, returning how many bytes were read.
        fn read(
            &self,
            waker: Waker,
            file: &Handle,
            buf: &mut [u8],
        ) -> CancelablePoll<io::Result<usize>>;
    }

    static GLOBAL_NAMED_PIPE: OnceLock<Box<dyn NamedPipe>> = OnceLock::new();

    /// Register provided [`NamedPipe`] as global named pipe implementation.
    ///
    /// # Panic
    ///
    /// Multiple calls to this function are not permitted!!!
    pub fn register_global_named_pipe<N: NamedPipe + 'static>(fs: N) {
        if GLOBAL_NAMED_PIPE.set(Box::new(fs)).is_err() {
            panic!("Multiple calls to register_global_filesystem are not permitted!!!");
        }
    }

    /// Get the globally registered instance of [`NamedPipe`].
    ///
    /// # Panic
    ///
    /// You should call [`register_global_named_pipe`] first to register implementation,
    /// otherwise this function will cause a panic with `Call register_global_filesystem first`
    pub fn global_named_pipe() -> &'static dyn NamedPipe {
        GLOBAL_NAMED_PIPE
            .get()
            .expect("Call register_global_named_pipe first")
            .as_ref()
    }
}

#[cfg(all(windows, feature = "windows_named_pipe"))]
pub use windows::*;