kobako 0.28.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.release-please-manifest.json +1 -1
- data/CHANGELOG.md +377 -0
- data/Cargo.lock +181 -154
- data/README.md +99 -459
- data/ROADMAP.md +7 -1
- data/SECURITY.md +1 -1
- data/crates/kobako-runtime/CHANGELOG.md +354 -0
- data/crates/kobako-runtime/Cargo.toml +38 -7
- data/crates/kobako-runtime/README.md +17 -8
- data/crates/kobako-runtime/src/dispatch.rs +9 -1
- data/crates/kobako-runtime/src/endpoint/buffer.rs +192 -0
- data/crates/kobako-runtime/src/endpoint/capture.rs +115 -0
- data/crates/kobako-runtime/src/endpoint/dispatch.rs +253 -0
- data/crates/kobako-runtime/src/endpoint/invoke.rs +194 -0
- data/crates/kobako-runtime/src/endpoint/memory_cap.rs +238 -0
- data/crates/kobako-runtime/src/endpoint.rs +166 -0
- data/crates/kobako-runtime/src/filesystem/bind/fid.rs +82 -0
- data/crates/kobako-runtime/src/filesystem/bind/lookup.rs +236 -0
- data/crates/kobako-runtime/src/filesystem/bind/walk.rs +249 -0
- data/crates/kobako-runtime/src/filesystem/bind.rs +2665 -0
- data/crates/kobako-runtime/src/filesystem/overlay/file.rs +89 -0
- data/crates/kobako-runtime/src/filesystem/overlay/inode.rs +106 -0
- data/crates/kobako-runtime/src/filesystem/overlay/path.rs +285 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/copy_up.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/dir.rs +263 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/namei.rs +135 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack/readdir.rs +57 -0
- data/crates/kobako-runtime/src/filesystem/overlay/stack.rs +301 -0
- data/crates/kobako-runtime/src/filesystem/overlay.rs +3469 -0
- data/crates/kobako-runtime/src/filesystem/path.rs +21 -0
- data/crates/kobako-runtime/src/filesystem/pipe.rs +221 -0
- data/crates/kobako-runtime/src/filesystem/rdir.rs +101 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/tmpfile.rs +32 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/whiteout.rs +85 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs/xattr.rs +22 -0
- data/crates/kobako-runtime/src/filesystem/tmpfs.rs +1985 -0
- data/crates/kobako-runtime/src/filesystem/wrap.rs +224 -0
- data/crates/kobako-runtime/src/filesystem.rs +26 -0
- data/crates/kobako-runtime/src/grant.rs +20 -0
- data/crates/kobako-runtime/src/hashtable.rs +44 -0
- data/crates/kobako-runtime/src/lib.rs +16 -1
- data/crates/kobako-runtime/src/locking/seqlock.rs +66 -0
- data/crates/kobako-runtime/src/locking.rs +218 -0
- data/crates/kobako-runtime/src/mount.rs +105 -0
- data/crates/kobako-runtime/src/profile.rs +140 -4
- data/crates/kobako-runtime/src/runtime.rs +12 -0
- data/crates/kobako-runtime/src/sched.rs +210 -0
- data/crates/kobako-runtime/src/security.rs +696 -0
- data/crates/kobako-runtime/src/snapshot.rs +20 -5
- data/crates/kobako-runtime/src/vfs/attr.rs +162 -0
- data/crates/kobako-runtime/src/vfs/cred.rs +334 -0
- data/crates/kobako-runtime/src/vfs/dentry.rs +1412 -0
- data/crates/kobako-runtime/src/vfs/errno.rs +153 -0
- data/crates/kobako-runtime/src/vfs/file.rs +1915 -0
- data/crates/kobako-runtime/src/vfs/inode.rs +429 -0
- data/crates/kobako-runtime/src/vfs/kern_path.rs +217 -0
- data/crates/kobako-runtime/src/vfs/mount.rs +59 -0
- data/crates/kobako-runtime/src/vfs/namei.rs +5708 -0
- data/crates/kobako-runtime/src/vfs/openat2.rs +116 -0
- data/crates/kobako-runtime/src/vfs/path.rs +64 -0
- data/crates/kobako-runtime/src/vfs/readdir.rs +163 -0
- data/crates/kobako-runtime/src/vfs/stat.rs +157 -0
- data/crates/kobako-runtime/src/vfs/super_block.rs +122 -0
- data/crates/kobako-runtime/src/vfs/utimes.rs +289 -0
- data/crates/kobako-runtime/src/vfs.rs +60 -0
- data/crates/kobako-runtime/src/yielder.rs +11 -6
- data/crates/kobako-runtime/tests/linux_baseline/calls.rs +171 -0
- data/crates/kobako-runtime/tests/linux_baseline/concurrent.rs +276 -0
- data/crates/kobako-runtime/tests/linux_baseline/kobako.rs +323 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux-baseline-expected.txt +16 -0
- data/crates/kobako-runtime/tests/linux_baseline/linux.rs +449 -0
- data/crates/kobako-runtime/tests/linux_baseline/main.rs +611 -0
- data/crates/kobako-runtime/tests/linux_baseline/permissions.rs +236 -0
- data/crates/kobako-transport/CHANGELOG.md +23 -0
- data/crates/kobako-transport/Cargo.toml +3 -3
- data/crates/kobako-transport/README.md +6 -5
- data/crates/kobako-transport/src/abi.rs +1 -1
- data/crates/kobako-transport/src/envelope/bytes.rs +33 -25
- data/crates/kobako-transport/src/envelope/call.rs +5 -5
- data/crates/kobako-transport/src/envelope/compile_reply.rs +2 -2
- data/crates/kobako-transport/src/envelope/error_record.rs +3 -3
- data/crates/kobako-transport/src/envelope/fault.rs +4 -3
- data/crates/kobako-transport/src/envelope/outcome.rs +3 -3
- data/crates/kobako-transport/src/envelope/preamble.rs +24 -24
- data/crates/kobako-transport/src/envelope/reply.rs +34 -16
- data/crates/kobako-transport/src/envelope/request.rs +19 -9
- data/crates/kobako-transport/src/envelope/run.rs +1 -1
- data/crates/kobako-transport/src/envelope/unsupported.rs +2 -2
- data/crates/kobako-transport/src/envelope.rs +2 -2
- data/crates/kobako-wasmtime/CHANGELOG.md +265 -0
- data/crates/kobako-wasmtime/Cargo.toml +25 -19
- data/crates/kobako-wasmtime/README.md +13 -7
- data/crates/kobako-wasmtime/src/abi.rs +75 -49
- data/crates/kobako-wasmtime/src/ambient.rs +64 -6
- data/crates/kobako-wasmtime/src/cache.rs +188 -77
- data/crates/kobako-wasmtime/src/config.rs +77 -5
- data/crates/kobako-wasmtime/src/descriptors.rs +200 -0
- data/crates/kobako-wasmtime/src/driver.rs +70 -135
- data/crates/kobako-wasmtime/src/exports.rs +5 -16
- data/crates/kobako-wasmtime/src/guest.rs +38 -0
- data/crates/kobako-wasmtime/src/imports.rs +52 -0
- data/crates/kobako-wasmtime/src/instance_pre.rs +18 -46
- data/crates/kobako-wasmtime/src/invocation.rs +43 -74
- data/crates/kobako-wasmtime/src/lib.rs +5 -4
- data/crates/kobako-wasmtime/src/limiter.rs +6 -230
- data/crates/kobako-wasmtime/src/preview1/differential/mount.rs +632 -0
- data/crates/kobako-wasmtime/src/preview1/differential.rs +486 -0
- data/crates/kobako-wasmtime/src/preview1/errno.rs +135 -0
- data/crates/kobako-wasmtime/src/preview1/fd.rs +1420 -0
- data/crates/kobako-wasmtime/src/preview1/memory.rs +172 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view/wrap.rs +211 -0
- data/crates/kobako-wasmtime/src/preview1/mount_view.rs +3766 -0
- data/crates/kobako-wasmtime/src/preview1/path.rs +302 -0
- data/crates/kobako-wasmtime/src/preview1/poll.rs +381 -0
- data/crates/kobako-wasmtime/src/preview1/sock.rs +13 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite-expected.txt +6 -0
- data/crates/kobako-wasmtime/src/preview1/testsuite.rs +157 -0
- data/crates/kobako-wasmtime/src/preview1.rs +512 -0
- data/crates/kobako-wasmtime/src/trap.rs +45 -25
- data/crates/kobako-wasmtime/src/wasi.rs +333 -0
- data/crates/kobako-wasmtime/tests/integration/compile_posture.rs +43 -0
- data/crates/kobako-wasmtime/tests/integration/guest_file.rs +1085 -0
- data/crates/kobako-wasmtime/tests/integration/main.rs +29 -0
- data/crates/kobako-wasmtime/tests/{preamble_flags.rs → integration/preamble_flags.rs} +4 -3
- data/crates/kobako-wasmtime/tests/{snippet_compile.rs → integration/snippet_compile.rs} +5 -4
- data/crates/kobako-wasmtime/tests/{support → integration/support}/mod.rs +9 -13
- data/crates/kobako-wasmtime/tests/integration/unanswered_call.rs +86 -0
- data/crates/kobako-wasmtime/tests/{unsupported_call.rs → integration/unsupported_call.rs} +2 -1
- data/crates/kobako-wasmtime/witx/typenames.witx +750 -0
- data/crates/kobako-wasmtime/witx/wasi_snapshot_preview1.witx +441 -0
- data/data/kobako.wasm +0 -0
- data/ext/kobako/Cargo.toml +4 -2
- data/ext/kobako/src/runtime/bridge.rs +154 -81
- data/ext/kobako/src/runtime/errors.rs +1 -1
- data/ext/kobako/src/runtime/escape.rs +67 -0
- data/ext/kobako/src/runtime/filesystem/grants.rs +107 -0
- data/ext/kobako/src/runtime/filesystem/ruby.rs +708 -0
- data/ext/kobako/src/runtime/filesystem.rs +208 -0
- data/ext/kobako/src/runtime/gvl.rs +49 -8
- data/ext/kobako/src/runtime/pinned_value.rs +31 -0
- data/ext/kobako/src/runtime/snapshot.rs +127 -0
- data/ext/kobako/src/runtime.rs +125 -199
- data/lib/kobako/catalog/extensions.rb +5 -5
- data/lib/kobako/catalog/flags.rb +2 -14
- data/lib/kobako/catalog/grants.rb +30 -0
- data/lib/kobako/catalog/handles.rb +6 -6
- data/lib/kobako/catalog/mount.rb +19 -0
- data/lib/kobako/catalog/services.rb +13 -2
- data/lib/kobako/catalog/snippets.rb +11 -15
- data/lib/kobako/catalog.rb +11 -0
- data/lib/kobako/codec/decoder.rb +10 -9
- data/lib/kobako/codec/ext_types.rb +4 -6
- data/lib/kobako/codec/handle_walk.rb +4 -6
- data/lib/kobako/codec/nesting.rb +7 -7
- data/lib/kobako/codec/state.rb +19 -3
- data/lib/kobako/codec/utils.rb +1 -1
- data/lib/kobako/codec/value_budget.rb +86 -0
- data/lib/kobako/codec.rb +7 -0
- data/lib/kobako/context.rb +40 -13
- data/lib/kobako/errors.rb +15 -14
- data/lib/kobako/filesystem.rb +69 -0
- data/lib/kobako/payload/arguments.rb +2 -2
- data/lib/kobako/pool.rb +6 -6
- data/lib/kobako/sandbox.rb +55 -27
- data/lib/kobako/sandbox_options.rb +39 -13
- data/lib/kobako/snippet/{binary.rb → bytecode.rb} +2 -2
- data/lib/kobako/snippet/source.rb +1 -1
- data/lib/kobako/snippet.rb +1 -1
- data/lib/kobako/transport/block_failure.rb +35 -0
- data/lib/kobako/transport/dispatcher.rb +33 -29
- data/lib/kobako/transport/exposure.rb +7 -9
- data/lib/kobako/transport/reflection.rb +79 -23
- data/lib/kobako/transport/run.rb +5 -5
- data/lib/kobako/transport/yielder.rb +9 -31
- data/lib/kobako/transport.rb +1 -0
- data/lib/kobako/version.rb +1 -1
- data/lib/kobako.rb +35 -0
- data/release-please-config.json +1 -0
- data/sig/kobako/catalog/extensions.rbs +3 -3
- data/sig/kobako/catalog/flags.rbs +0 -4
- data/sig/kobako/catalog/grants.rbs +13 -0
- data/sig/kobako/catalog/handles.rbs +3 -3
- data/sig/kobako/catalog/mount.rbs +24 -0
- data/sig/kobako/catalog/services.rbs +5 -1
- data/sig/kobako/catalog/snippets.rbs +4 -4
- data/sig/kobako/catalog.rbs +1 -0
- data/sig/kobako/codec/decoder.rbs +1 -1
- data/sig/kobako/codec/handle_walk.rbs +1 -1
- data/sig/kobako/codec/nesting.rbs +2 -2
- data/sig/kobako/codec/state.rbs +7 -1
- data/sig/kobako/codec/utils.rbs +1 -1
- data/sig/kobako/codec/value_budget.rbs +23 -0
- data/sig/kobako/codec.rbs +3 -2
- data/sig/kobako/context.rbs +14 -6
- data/sig/kobako/errors.rbs +4 -4
- data/sig/kobako/filesystem.rbs +93 -0
- data/sig/kobako/guest_yielder.rbs +9 -0
- data/sig/kobako/payload/arguments.rbs +1 -1
- data/sig/kobako/pool.rbs +3 -3
- data/sig/kobako/runtime.rbs +21 -49
- data/sig/kobako/sandbox.rbs +9 -5
- data/sig/kobako/sandbox_options.rbs +25 -3
- data/sig/kobako/snapshot.rbs +20 -0
- data/sig/kobako/snippet/bytecode.rbs +12 -0
- data/sig/kobako/transport/block_failure.rbs +13 -0
- data/sig/kobako/transport/dispatcher.rbs +4 -4
- data/sig/kobako/transport/exposure.rbs +2 -2
- data/sig/kobako/transport/reflection.rbs +14 -1
- data/sig/kobako/transport/run.rbs +3 -3
- data/sig/kobako/transport/yielder.rbs +3 -7
- data/sig/kobako/transport.rbs +3 -3
- metadata +116 -15
- data/crates/kobako-wasmtime/src/capture.rs +0 -96
- data/crates/kobako-wasmtime/src/dispatch.rs +0 -107
- data/crates/kobako-wasmtime/src/guest_mem.rs +0 -265
- data/crates/kobako-wasmtime/src/io.rs +0 -286
- data/sig/kobako/snippet/binary.rbs +0 -12
|
@@ -0,0 +1,1915 @@
|
|
|
1
|
+
//! An open file, as Linux's `struct file`: where it was opened, what answers
|
|
2
|
+
//! for it, and what each descriptor of it shares, so any ABI above holds the
|
|
3
|
+
//! same open file.
|
|
4
|
+
|
|
5
|
+
use std::any::Any;
|
|
6
|
+
use std::ops::BitOr;
|
|
7
|
+
use std::sync::Arc;
|
|
8
|
+
|
|
9
|
+
use super::attr::notify_change;
|
|
10
|
+
use super::cred::{on_path, Cred, FileSecurity, SecurityPath};
|
|
11
|
+
use super::dentry::Dentry;
|
|
12
|
+
use super::inode::{EmptyFops, Inode};
|
|
13
|
+
use super::mount::VfsMount;
|
|
14
|
+
use super::openat2::OpenHow;
|
|
15
|
+
use super::path::Path;
|
|
16
|
+
use super::readdir::DirContext;
|
|
17
|
+
#[cfg(test)]
|
|
18
|
+
use super::readdir::{DirEmit, DirEntry};
|
|
19
|
+
use super::stat::special_file;
|
|
20
|
+
use super::{FileType, FilesystemError, Iattr, Kstat};
|
|
21
|
+
|
|
22
|
+
/// The largest offset a file holds, as Linux's `OFFSET_MAX` ends its signed
|
|
23
|
+
/// `loff_t`.
|
|
24
|
+
const OFFSET_MAX: u64 = i64::MAX as u64;
|
|
25
|
+
|
|
26
|
+
/// The most one read or write takes, as Linux's `MAX_RW_COUNT`, the largest
|
|
27
|
+
/// signed 32-bit count rounded down to a page.
|
|
28
|
+
const MAX_RW_COUNT: usize = i32::MAX as usize & !4095;
|
|
29
|
+
|
|
30
|
+
/// Where one read or write lands, and where the file left off once it did,
|
|
31
|
+
/// as Linux's `struct kiocb`.
|
|
32
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
33
|
+
#[non_exhaustive]
|
|
34
|
+
pub struct Kiocb {
|
|
35
|
+
/// Where the call lands; the file moves it past what it took.
|
|
36
|
+
pub pos: u64,
|
|
37
|
+
/// Whether a write lands at the file's end as it stands then, as
|
|
38
|
+
/// `IOCB_APPEND` asks.
|
|
39
|
+
pub append: bool,
|
|
40
|
+
/// Whether what a write needs read back is durable before it answers, as
|
|
41
|
+
/// `IOCB_DSYNC` asks.
|
|
42
|
+
pub dsync: bool,
|
|
43
|
+
/// Whether everything a write changed is durable before it answers, as
|
|
44
|
+
/// `IOCB_SYNC` asks along with `IOCB_DSYNC`.
|
|
45
|
+
pub sync: bool,
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
impl Kiocb {
|
|
49
|
+
/// A call landing at `pos`.
|
|
50
|
+
pub fn at(pos: u64) -> Self {
|
|
51
|
+
Self {
|
|
52
|
+
pos,
|
|
53
|
+
append: false,
|
|
54
|
+
dsync: false,
|
|
55
|
+
sync: false,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// This call, landing at the file's end as it stands when the write
|
|
60
|
+
/// lands.
|
|
61
|
+
pub fn with_append(self) -> Self {
|
|
62
|
+
Self {
|
|
63
|
+
append: true,
|
|
64
|
+
..self
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// Make what `written` bytes changed durable through `file` where `iocb` asks, as
|
|
70
|
+
/// Linux's `generic_write_sync` is called by a Filesystem's `write_iter` once a
|
|
71
|
+
/// write landed: data alone unless `IOCB_SYNC` asks for everything.
|
|
72
|
+
pub fn generic_write_sync<F: FileOperations + ?Sized>(
|
|
73
|
+
file: &mut F,
|
|
74
|
+
iocb: &Kiocb,
|
|
75
|
+
written: usize,
|
|
76
|
+
) -> Result<usize, FilesystemError> {
|
|
77
|
+
if iocb.dsync && written > 0 {
|
|
78
|
+
file.fsync(!iocb.sync)?;
|
|
79
|
+
}
|
|
80
|
+
Ok(written)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// One open file. The runtime keeps its position and hands each call where it
|
|
84
|
+
/// lands. Its Filesystem reaches what it holds through a change made through it,
|
|
85
|
+
/// as Linux's `ia_file->private_data`, and any thread may hold it, as a
|
|
86
|
+
/// `struct file` is shared.
|
|
87
|
+
pub trait FileOperations: Any + Send + Sync {
|
|
88
|
+
/// Where a seek by `offset` from `whence` leaves this file, which `dentry`
|
|
89
|
+
/// names and whose position stands at `pos`, as `f_op->llseek` answers. A file
|
|
90
|
+
/// without it takes no seek, as Linux clears `FMODE_LSEEK` for one; one that
|
|
91
|
+
/// seeks as any file does answers through `generic_file_llseek`.
|
|
92
|
+
fn llseek(
|
|
93
|
+
&mut self,
|
|
94
|
+
_dentry: &Dentry,
|
|
95
|
+
_pos: u64,
|
|
96
|
+
_offset: i64,
|
|
97
|
+
_whence: Whence,
|
|
98
|
+
) -> Result<u64, FilesystemError> {
|
|
99
|
+
Err(FilesystemError::NotSeekable)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/// Read into `buf` from where `iocb` lands, moving it past what was read
|
|
103
|
+
/// unless the file has no positions. A file without it refuses as Linux's
|
|
104
|
+
/// `vfs_read` refuses one without `f_op->read_iter`. A directory answers
|
|
105
|
+
/// through `generic_read_dir`, so its read is refused as a directory's.
|
|
106
|
+
fn read_iter(&mut self, _iocb: &mut Kiocb, _buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
107
|
+
Err(FilesystemError::InvalidInput)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Write `buf` where `iocb` lands, moving it past what was written unless
|
|
111
|
+
/// the file has no positions. A file without it refuses as Linux's
|
|
112
|
+
/// `vfs_write` refuses one without `f_op->write_iter`.
|
|
113
|
+
///
|
|
114
|
+
/// A call landing at the end, as `iocb.append` asks, must keep another
|
|
115
|
+
/// writer's from landing over it, as `generic_file_write_iter` reads the
|
|
116
|
+
/// end under the inode's lock, so a file that cannot read its end and
|
|
117
|
+
/// write there in one step answers `Unsupported`.
|
|
118
|
+
fn write_iter(&mut self, _iocb: &mut Kiocb, _buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
119
|
+
Err(FilesystemError::InvalidInput)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/// Hand `ctx` the entries of this directory, which `dentry` names, from
|
|
123
|
+
/// `ctx`'s position until its actor stops, as `iterate_shared` does through
|
|
124
|
+
/// `dir_emit_dots` and `dir_emit`. One reading the whole directory at once
|
|
125
|
+
/// answers through the `Rdir` that `filesystem` provides. A file without
|
|
126
|
+
/// it answers `NotADirectory`, as Linux's `iterate_dir` does.
|
|
127
|
+
fn iterate_shared(
|
|
128
|
+
&mut self,
|
|
129
|
+
_dentry: &Dentry,
|
|
130
|
+
_ctx: &mut DirContext<'_>,
|
|
131
|
+
) -> Result<(), FilesystemError> {
|
|
132
|
+
Err(FilesystemError::NotADirectory)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/// Reserve `len` bytes from `offset`, as `f_op->fallocate` does. A file
|
|
136
|
+
/// without it refuses as Linux's `vfs_fallocate` refuses one without it.
|
|
137
|
+
fn fallocate(&mut self, _offset: u64, _len: u64) -> Result<(), FilesystemError> {
|
|
138
|
+
Err(FilesystemError::Unsupported)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/// What a call would find ready on this end, opened for `mode`, as
|
|
142
|
+
/// `f_op->poll` answers. A file without it is always ready, as Linux's
|
|
143
|
+
/// `vfs_poll` answers `DEFAULT_POLLMASK`.
|
|
144
|
+
fn poll(&self, _mode: FMode) -> PollMask {
|
|
145
|
+
PollMask::IN | PollMask::OUT
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/// Make what was written durable, only what reading it back needs where
|
|
149
|
+
/// `datasync` asks, as `f_op->fsync` takes it. A file with nothing to flush
|
|
150
|
+
/// answers success itself, as tmpfs names `noop_fsync`; one without it refuses
|
|
151
|
+
/// as Linux's `vfs_fsync_range` refuses one without `f_op->fsync`.
|
|
152
|
+
fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
|
|
153
|
+
Err(FilesystemError::InvalidInput)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/// Answer, as the file is closed, whether what it held was kept, as
|
|
157
|
+
/// `f_op->flush` answers `close(2)`. The default has nothing to flush.
|
|
158
|
+
/// What the file holds goes when it is dropped, as `->release` runs and
|
|
159
|
+
/// no caller reads its answer, so a file never closed still lets go of it.
|
|
160
|
+
fn flush(&mut self) -> Result<(), FilesystemError> {
|
|
161
|
+
Ok(())
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/// Whether this file reads and writes what comes next, with no position,
|
|
165
|
+
/// as an `->open` calling `stream_open` marks one. The default takes a
|
|
166
|
+
/// position.
|
|
167
|
+
fn is_stream(&self) -> bool {
|
|
168
|
+
false
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// Where a seek by `offset` from `whence` leaves a file `dentry` names, whose
|
|
173
|
+
/// position stands at `pos`, refused past the largest offset, as Linux's
|
|
174
|
+
/// `generic_file_llseek` counts from the position, or the end its inode reports.
|
|
175
|
+
pub fn generic_file_llseek(
|
|
176
|
+
dentry: &Dentry,
|
|
177
|
+
pos: u64,
|
|
178
|
+
offset: i64,
|
|
179
|
+
whence: Whence,
|
|
180
|
+
) -> Result<u64, FilesystemError> {
|
|
181
|
+
let base = match whence {
|
|
182
|
+
Whence::Set => 0,
|
|
183
|
+
Whence::Cur => pos,
|
|
184
|
+
Whence::End => dentry.getattr()?.size,
|
|
185
|
+
};
|
|
186
|
+
base.checked_add_signed(offset)
|
|
187
|
+
.filter(|pos| *pos <= OFFSET_MAX)
|
|
188
|
+
.ok_or(FilesystemError::InvalidInput)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/// What an open file was opened for and what it can do, as Linux's `fmode_t`.
|
|
192
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
193
|
+
pub struct FMode(u8);
|
|
194
|
+
|
|
195
|
+
impl FMode {
|
|
196
|
+
/// Opened to read, as `FMODE_READ`.
|
|
197
|
+
pub const READ: Self = Self(1);
|
|
198
|
+
/// Opened to write, as `FMODE_WRITE`.
|
|
199
|
+
pub const WRITE: Self = Self(1 << 1);
|
|
200
|
+
/// Takes a position to seek, as `FMODE_LSEEK`.
|
|
201
|
+
pub const LSEEK: Self = Self(1 << 2);
|
|
202
|
+
/// Reads from a named offset, as `FMODE_PREAD`.
|
|
203
|
+
pub const PREAD: Self = Self(1 << 3);
|
|
204
|
+
/// Writes at a named offset, as `FMODE_PWRITE`.
|
|
205
|
+
pub const PWRITE: Self = Self(1 << 4);
|
|
206
|
+
/// Reads and writes what comes next, with no position, as `FMODE_STREAM`.
|
|
207
|
+
pub const STREAM: Self = Self(1 << 5);
|
|
208
|
+
/// Made by the open that opened it, as `FMODE_CREATED`.
|
|
209
|
+
pub const CREATED: Self = Self(1 << 6);
|
|
210
|
+
/// Stands only for where it is, as `FMODE_PATH`.
|
|
211
|
+
pub const PATH: Self = Self(1 << 7);
|
|
212
|
+
|
|
213
|
+
/// What an open of `how` is for, as `do_dentry_open` sets `f_mode`. An
|
|
214
|
+
/// open for neither reading nor writing stands only for its path, as only
|
|
215
|
+
/// `O_PATH` opens one on Linux, whose `O_RDONLY` always reads.
|
|
216
|
+
pub(crate) fn of(how: &OpenHow) -> Self {
|
|
217
|
+
if !how.read && !how.write {
|
|
218
|
+
return Self::PATH;
|
|
219
|
+
}
|
|
220
|
+
Self::open_fmode(how).with(Self(Self::PREAD.0 | Self::PWRITE.0))
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/// What a file `alloc_file` makes is for, as Linux's `OPEN_FMODE` sets
|
|
224
|
+
/// `f_mode` from `how` and `file_init_path` adds `FMODE_LSEEK`: only
|
|
225
|
+
/// `do_dentry_open` lets a file read or write at a position.
|
|
226
|
+
fn open_fmode(how: &OpenHow) -> Self {
|
|
227
|
+
let mut mode = Self::LSEEK.0;
|
|
228
|
+
if how.read {
|
|
229
|
+
mode |= Self::READ.0;
|
|
230
|
+
}
|
|
231
|
+
if how.write {
|
|
232
|
+
mode |= Self::WRITE.0;
|
|
233
|
+
}
|
|
234
|
+
Self(mode)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// This mode with no position, as `stream_open` clears `FMODE_LSEEK`,
|
|
238
|
+
/// `FMODE_PREAD`, and `FMODE_PWRITE` and sets `FMODE_STREAM`.
|
|
239
|
+
fn stream_open(self) -> Self {
|
|
240
|
+
Self(self.0 & !(Self::LSEEK.0 | Self::PREAD.0 | Self::PWRITE.0) | Self::STREAM.0)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/// This mode with `mode` added.
|
|
244
|
+
pub(crate) fn with(self, mode: Self) -> Self {
|
|
245
|
+
Self(self.0 | mode.0)
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/// Whether this holds all of `mode`.
|
|
249
|
+
pub fn contains(self, mode: Self) -> bool {
|
|
250
|
+
self.0 & mode.0 == mode.0
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/// What a file would find ready, as Linux's `__poll_t`.
|
|
255
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
256
|
+
pub struct PollMask(u8);
|
|
257
|
+
|
|
258
|
+
impl PollMask {
|
|
259
|
+
/// A read would not wait, as `EPOLLIN`.
|
|
260
|
+
pub const IN: Self = Self(1);
|
|
261
|
+
/// A write would not wait, as `EPOLLOUT`.
|
|
262
|
+
pub const OUT: Self = Self(1 << 1);
|
|
263
|
+
|
|
264
|
+
/// Whether this holds all of `mask`.
|
|
265
|
+
pub fn contains(self, mask: Self) -> bool {
|
|
266
|
+
self.0 & mask.0 == mask.0
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
impl BitOr for PollMask {
|
|
271
|
+
type Output = Self;
|
|
272
|
+
|
|
273
|
+
fn bitor(self, other: Self) -> Self {
|
|
274
|
+
Self(self.0 | other.0)
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/// Where a seek counts from, as Linux's `SEEK_*`.
|
|
279
|
+
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
280
|
+
pub enum Whence {
|
|
281
|
+
Set,
|
|
282
|
+
Cur,
|
|
283
|
+
End,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/// Open what stands at `at` as `mode` and `how` ask for `cred`, as Linux's
|
|
287
|
+
/// `do_dentry_open` takes write access to the mount for anything but a special
|
|
288
|
+
/// file, then asks the security modules before `open`. A file that stands only
|
|
289
|
+
/// for its path asks nothing and holds no operations, as `do_dentry_open`
|
|
290
|
+
/// hands an `O_PATH` file `empty_fops`.
|
|
291
|
+
pub(super) fn do_dentry_open(
|
|
292
|
+
cred: &Cred,
|
|
293
|
+
at: SecurityPath<'_>,
|
|
294
|
+
kind: FileType,
|
|
295
|
+
mode: FMode,
|
|
296
|
+
how: &OpenHow,
|
|
297
|
+
open: impl FnOnce() -> Result<Box<dyn FileOperations>, FilesystemError>,
|
|
298
|
+
) -> Result<(Box<dyn FileOperations>, Vec<FileSecurity>), FilesystemError> {
|
|
299
|
+
if mode.contains(FMode::PATH) {
|
|
300
|
+
return Ok((Box::new(EmptyFops), cred.security_file_alloc()));
|
|
301
|
+
}
|
|
302
|
+
if mode.contains(FMode::WRITE) && !special_file(kind) {
|
|
303
|
+
at.mnt.mnt_want_write()?;
|
|
304
|
+
}
|
|
305
|
+
let security = cred.security_file_open(at, kind, mode, how)?;
|
|
306
|
+
Ok((open()?, security))
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// One open file or directory, as a descriptor refers to it.
|
|
310
|
+
pub struct File {
|
|
311
|
+
/// Where it was opened, as `f_path`; it follows the name wherever it
|
|
312
|
+
/// moves.
|
|
313
|
+
path: Path,
|
|
314
|
+
/// What it was opened on, as `f_inode`.
|
|
315
|
+
inode: Arc<Inode>,
|
|
316
|
+
/// What answers for it, as `f_op`.
|
|
317
|
+
ops: Box<dyn FileOperations>,
|
|
318
|
+
/// What it was opened for, as `f_mode`.
|
|
319
|
+
mode: FMode,
|
|
320
|
+
/// Whether each write lands at the end, as `O_APPEND` in `f_flags`.
|
|
321
|
+
append: bool,
|
|
322
|
+
/// Whether it asked not to wait, as `O_NONBLOCK` in `f_flags`. Nothing
|
|
323
|
+
/// here waits, so it only reports back.
|
|
324
|
+
nonblock: bool,
|
|
325
|
+
/// Whether what each write needs read back is durable before it answers, as
|
|
326
|
+
/// `O_DSYNC` in `f_flags`.
|
|
327
|
+
dsync: bool,
|
|
328
|
+
/// Whether everything each write changed is durable before it answers, as
|
|
329
|
+
/// `O_SYNC` in `f_flags`.
|
|
330
|
+
sync: bool,
|
|
331
|
+
/// Where the next read or write lands, as `f_pos`.
|
|
332
|
+
pos: u64,
|
|
333
|
+
/// Who opened it, as `f_cred`.
|
|
334
|
+
cred: Arc<Cred>,
|
|
335
|
+
/// What each of `cred`'s security modules kept on it as it opened, as
|
|
336
|
+
/// `f_security`.
|
|
337
|
+
security: Vec<FileSecurity>,
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/// A file `ops` answers for, opened as `how` asks on a dentry of `mnt` that no
|
|
341
|
+
/// walk reaches and stands for `inode`, holding what each of `cred`'s security
|
|
342
|
+
/// modules keeps on it, as Linux's `alloc_file_pseudo` makes one for what is
|
|
343
|
+
/// only ever open.
|
|
344
|
+
pub(crate) fn alloc_file_pseudo(
|
|
345
|
+
inode: Arc<Inode>,
|
|
346
|
+
mnt: &Arc<VfsMount>,
|
|
347
|
+
how: &OpenHow,
|
|
348
|
+
ops: Box<dyn FileOperations>,
|
|
349
|
+
cred: &Arc<Cred>,
|
|
350
|
+
) -> File {
|
|
351
|
+
let dentry = mnt.sb().dentries().alloc_pseudo();
|
|
352
|
+
dentry.instantiate(inode);
|
|
353
|
+
File::alloc_file(Path::root(mnt).with_dentry(dentry), how, ops, cred)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
impl File {
|
|
357
|
+
/// A file `ops` answers for, opened as `how` asks at `path`, as Linux's
|
|
358
|
+
/// `alloc_file` takes an empty file and fills in its path.
|
|
359
|
+
fn alloc_file(
|
|
360
|
+
path: Path,
|
|
361
|
+
how: &OpenHow,
|
|
362
|
+
ops: Box<dyn FileOperations>,
|
|
363
|
+
cred: &Arc<Cred>,
|
|
364
|
+
) -> Self {
|
|
365
|
+
File::new(
|
|
366
|
+
path,
|
|
367
|
+
ops,
|
|
368
|
+
FMode::open_fmode(how),
|
|
369
|
+
how,
|
|
370
|
+
cred,
|
|
371
|
+
cred.security_file_alloc(),
|
|
372
|
+
)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/// Another file `ops` answers for, opened as `how` asks where this one
|
|
376
|
+
/// stands, as Linux's `alloc_file_clone` opens a pipe's other end.
|
|
377
|
+
pub(crate) fn alloc_file_clone(&self, how: &OpenHow, ops: Box<dyn FileOperations>) -> Self {
|
|
378
|
+
File::alloc_file(self.path.clone(), how, ops, &self.cred)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/// The file `ops` answers for, opened at `path` for `mode` as `how` asks,
|
|
382
|
+
/// with no position where `ops` is a stream, holding what each of `cred`'s
|
|
383
|
+
/// security modules kept on it. It keeps the inode `path` names, as
|
|
384
|
+
/// `do_dentry_open` sets `f_inode` once.
|
|
385
|
+
pub(crate) fn new(
|
|
386
|
+
path: Path,
|
|
387
|
+
ops: Box<dyn FileOperations>,
|
|
388
|
+
mode: FMode,
|
|
389
|
+
how: &OpenHow,
|
|
390
|
+
cred: &Arc<Cred>,
|
|
391
|
+
security: Vec<FileSecurity>,
|
|
392
|
+
) -> Self {
|
|
393
|
+
let inode = path
|
|
394
|
+
.dentry()
|
|
395
|
+
.inode()
|
|
396
|
+
.expect("a file opens only on a dentry naming an inode");
|
|
397
|
+
let mode = match ops.is_stream() {
|
|
398
|
+
true => mode.stream_open(),
|
|
399
|
+
false => mode,
|
|
400
|
+
};
|
|
401
|
+
Self {
|
|
402
|
+
path,
|
|
403
|
+
inode,
|
|
404
|
+
ops,
|
|
405
|
+
mode,
|
|
406
|
+
append: how.append,
|
|
407
|
+
nonblock: how.nonblock,
|
|
408
|
+
dsync: how.dsync,
|
|
409
|
+
sync: how.sync,
|
|
410
|
+
pos: 0,
|
|
411
|
+
cred: cred.clone(),
|
|
412
|
+
security,
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
/// What stands at `path`, opened as `how` asks for `cred`, as Linux's
|
|
417
|
+
/// `dentry_open` asks the security modules before `f_op->open` and walks no
|
|
418
|
+
/// path.
|
|
419
|
+
pub(super) fn dentry_open(
|
|
420
|
+
path: Path,
|
|
421
|
+
how: &OpenHow,
|
|
422
|
+
cred: &Arc<Cred>,
|
|
423
|
+
) -> Result<Self, FilesystemError> {
|
|
424
|
+
let kind = path.dentry().kind()?;
|
|
425
|
+
let mode = FMode::of(how);
|
|
426
|
+
let (ops, security) = on_path(path.mnt(), path.dentry(), |at| {
|
|
427
|
+
do_dentry_open(cred, at, kind, mode, how, || path.dentry().open(how))
|
|
428
|
+
})?;
|
|
429
|
+
Ok(Self::new(path, ops, mode, how, cred, security))
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/// The root of `mnt` opened as a directory for `cred`. A Wasm Driver opens each
|
|
433
|
+
/// Mount's root with the Cred its Policies are added to afterwards, as a
|
|
434
|
+
/// Linux task opens what it keeps before `landlock_restrict_self`.
|
|
435
|
+
pub fn root(mnt: &Arc<VfsMount>, cred: &Arc<Cred>) -> Result<Self, FilesystemError> {
|
|
436
|
+
let read = OpenHow {
|
|
437
|
+
read: true,
|
|
438
|
+
..OpenHow::default()
|
|
439
|
+
};
|
|
440
|
+
Self::dentry_open(Path::root(mnt), &read, cred)
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/// Where it was opened; a call relative to it starts here.
|
|
444
|
+
pub fn path(&self) -> &Path {
|
|
445
|
+
&self.path
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/// Where its next read, write, or listing starts, as `f_pos`.
|
|
449
|
+
pub fn pos(&self) -> u64 {
|
|
450
|
+
self.pos
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/// Whether it is a directory, as `S_ISDIR(file_inode(file))`.
|
|
454
|
+
pub fn is_directory(&self) -> bool {
|
|
455
|
+
self.kind() == FileType::Dir
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
/// What kind of entry it is, as `file_inode(file)->i_mode` says.
|
|
459
|
+
pub fn kind(&self) -> FileType {
|
|
460
|
+
self.inode.kind()
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/// What it was opened for.
|
|
464
|
+
#[cfg(test)]
|
|
465
|
+
pub(crate) fn mode(&self) -> FMode {
|
|
466
|
+
self.mode
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/// Whether it was opened to read.
|
|
470
|
+
pub fn readable(&self) -> bool {
|
|
471
|
+
self.mode.contains(FMode::READ)
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/// Whether it was opened to write.
|
|
475
|
+
pub fn writable(&self) -> bool {
|
|
476
|
+
self.mode.contains(FMode::WRITE)
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/// Whether it stands only for where it is, so a call reaching it through
|
|
480
|
+
/// its descriptor is refused, as `fdget` refuses an `FMODE_PATH` file and
|
|
481
|
+
/// only `fdget_raw` takes one.
|
|
482
|
+
pub fn is_path(&self) -> bool {
|
|
483
|
+
self.mode.contains(FMode::PATH)
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/// Whether each write lands at the end.
|
|
487
|
+
pub fn append(&self) -> bool {
|
|
488
|
+
self.append
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/// Whether it asked not to wait.
|
|
492
|
+
pub fn nonblock(&self) -> bool {
|
|
493
|
+
self.nonblock
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/// Whether what each write needs read back is durable before it answers.
|
|
497
|
+
pub fn dsync(&self) -> bool {
|
|
498
|
+
self.dsync
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
/// Whether everything each write changed is durable before it answers.
|
|
502
|
+
pub fn sync(&self) -> bool {
|
|
503
|
+
self.sync
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/// Change the flags an open file may change, as `fcntl`'s `setfl` takes
|
|
507
|
+
/// only `SETFL_MASK`.
|
|
508
|
+
pub fn setfl(&mut self, append: bool, nonblock: bool) {
|
|
509
|
+
self.append = append;
|
|
510
|
+
self.nonblock = nonblock;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/// Move its position, answering where it now stands, as `vfs_llseek` refuses
|
|
514
|
+
/// a file with no position and asks `f_op->llseek` of the rest.
|
|
515
|
+
pub fn llseek(&mut self, offset: i64, whence: Whence) -> Result<u64, FilesystemError> {
|
|
516
|
+
if !self.mode.contains(FMode::LSEEK) {
|
|
517
|
+
return Err(FilesystemError::NotSeekable);
|
|
518
|
+
}
|
|
519
|
+
let dentry = self.path.dentry().clone();
|
|
520
|
+
self.pos = self.ops.llseek(&dentry, self.pos, offset, whence)?;
|
|
521
|
+
Ok(self.pos)
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/// Take a hint about `len` bytes from `offset`, as `generic_fadvise`
|
|
525
|
+
/// refuses a FIFO, then a negative offset or length, and ignores the rest.
|
|
526
|
+
/// Every Filesystem is still correct without one.
|
|
527
|
+
pub fn fadvise(&self, offset: u64, len: u64) -> Result<(), FilesystemError> {
|
|
528
|
+
if self.kind() == FileType::Fifo {
|
|
529
|
+
return Err(FilesystemError::NotSeekable);
|
|
530
|
+
}
|
|
531
|
+
match offset > OFFSET_MAX || len > OFFSET_MAX {
|
|
532
|
+
true => Err(FilesystemError::InvalidInput),
|
|
533
|
+
false => Ok(()),
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/// Reserve `len` bytes from `offset`, as `vfs_fallocate` refuses in this
|
|
538
|
+
/// order before it asks `f_op->fallocate`: what the call asks, the open's
|
|
539
|
+
/// mode, a FIFO, a directory, and then anything but a regular file or a
|
|
540
|
+
/// block device.
|
|
541
|
+
pub fn fallocate(&mut self, offset: u64, len: u64) -> Result<(), FilesystemError> {
|
|
542
|
+
if offset > OFFSET_MAX || len == 0 || len > OFFSET_MAX {
|
|
543
|
+
return Err(FilesystemError::InvalidInput);
|
|
544
|
+
}
|
|
545
|
+
self.may_write()?;
|
|
546
|
+
match self.kind() {
|
|
547
|
+
FileType::Fifo => return Err(FilesystemError::NotSeekable),
|
|
548
|
+
FileType::Dir => return Err(FilesystemError::IsADirectory),
|
|
549
|
+
FileType::RegFile | FileType::Blkdev => {}
|
|
550
|
+
_ => return Err(FilesystemError::NoSuchDevice),
|
|
551
|
+
}
|
|
552
|
+
if offset + len > OFFSET_MAX {
|
|
553
|
+
return Err(FilesystemError::FileTooLarge);
|
|
554
|
+
}
|
|
555
|
+
self.ops.fallocate(offset, len)
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
/// What a call would find ready, as `vfs_poll` asks `f_op->poll`.
|
|
559
|
+
pub fn poll(&self) -> PollMask {
|
|
560
|
+
self.ops.poll(self.mode)
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/// Whether it reads and writes what comes next, with no position.
|
|
564
|
+
pub fn is_stream(&self) -> bool {
|
|
565
|
+
self.mode.contains(FMode::STREAM)
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
/// A call landing at `pos`, at the end where it was opened to append, and
|
|
569
|
+
/// asking the sync it was opened for, as `init_sync_kiocb` takes
|
|
570
|
+
/// `IOCB_APPEND`, `IOCB_DSYNC`, and `IOCB_SYNC` from `f_flags`.
|
|
571
|
+
fn kiocb(&self, pos: u64) -> Kiocb {
|
|
572
|
+
let iocb = Kiocb {
|
|
573
|
+
dsync: self.dsync,
|
|
574
|
+
sync: self.sync,
|
|
575
|
+
..Kiocb::at(pos)
|
|
576
|
+
};
|
|
577
|
+
match self.append {
|
|
578
|
+
true => iocb.with_append(),
|
|
579
|
+
false => iocb,
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/// Whether a read may reach it, as `vfs_read` asks of `f_mode` before it
|
|
584
|
+
/// touches the caller's buffer.
|
|
585
|
+
pub fn may_read(&self) -> Result<(), FilesystemError> {
|
|
586
|
+
match self.readable() {
|
|
587
|
+
true => Ok(()),
|
|
588
|
+
false => Err(FilesystemError::BadDescriptor),
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/// Whether a write may reach it, as `vfs_write` asks of `f_mode` before
|
|
593
|
+
/// it touches the caller's buffer.
|
|
594
|
+
pub fn may_write(&self) -> Result<(), FilesystemError> {
|
|
595
|
+
match self.writable() {
|
|
596
|
+
true => Ok(()),
|
|
597
|
+
false => Err(FilesystemError::BadDescriptor),
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/// Whether a read from `pos` may reach it, as `pread` refuses a negative
|
|
602
|
+
/// offset, then a file without one.
|
|
603
|
+
pub fn may_pread(&self, pos: u64) -> Result<(), FilesystemError> {
|
|
604
|
+
self.may_at(pos, FMode::PREAD)?;
|
|
605
|
+
self.may_read()
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/// Whether a write at `pos` may reach it, as `pwrite` refuses a negative
|
|
609
|
+
/// offset, then a file without one.
|
|
610
|
+
pub fn may_pwrite(&self, pos: u64) -> Result<(), FilesystemError> {
|
|
611
|
+
self.may_at(pos, FMode::PWRITE)?;
|
|
612
|
+
self.may_write()
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
fn may_at(&self, pos: u64, mode: FMode) -> Result<(), FilesystemError> {
|
|
616
|
+
if pos > OFFSET_MAX {
|
|
617
|
+
return Err(FilesystemError::InvalidInput);
|
|
618
|
+
}
|
|
619
|
+
match self.mode.contains(mode) {
|
|
620
|
+
true => Ok(()),
|
|
621
|
+
false => Err(FilesystemError::NotSeekable),
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
/// Where a read or write at the file's position lands, none for a stream,
|
|
626
|
+
/// as Linux's `file_ppos` answers.
|
|
627
|
+
fn file_ppos(&self) -> Option<u64> {
|
|
628
|
+
(!self.is_stream()).then_some(self.pos)
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
/// Refuse a call of `count` bytes from `ppos` if it would end past the
|
|
632
|
+
/// largest offset, as `rw_verify_area` does; a call landing nowhere passes.
|
|
633
|
+
fn rw_verify_area(&self, ppos: Option<u64>, count: usize) -> Result<(), FilesystemError> {
|
|
634
|
+
let Some(pos) = ppos else {
|
|
635
|
+
return Ok(());
|
|
636
|
+
};
|
|
637
|
+
match pos.checked_add(count as u64) {
|
|
638
|
+
Some(end) if end <= OFFSET_MAX => Ok(()),
|
|
639
|
+
_ => Err(FilesystemError::InvalidInput),
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
/// Read from its position, moving it past what was read, as `vfs_read`
|
|
644
|
+
/// does; a stream starts from no position and keeps none, as
|
|
645
|
+
/// `new_sync_read` hands it `ki_pos` 0.
|
|
646
|
+
pub fn read(&mut self, buf: &mut [u8]) -> Result<usize, FilesystemError> {
|
|
647
|
+
self.may_read()?;
|
|
648
|
+
let ppos = self.file_ppos();
|
|
649
|
+
self.rw_verify_area(ppos, buf.len())?;
|
|
650
|
+
let mut iocb = self.kiocb(ppos.unwrap_or(0));
|
|
651
|
+
let count = buf.len().min(MAX_RW_COUNT);
|
|
652
|
+
let read = self.ops.read_iter(&mut iocb, &mut buf[..count])?;
|
|
653
|
+
if ppos.is_some() {
|
|
654
|
+
self.pos = iocb.pos;
|
|
655
|
+
}
|
|
656
|
+
Ok(read)
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
/// Read from `pos`, leaving its position, as `pread` does.
|
|
660
|
+
pub fn pread(&mut self, buf: &mut [u8], pos: u64) -> Result<usize, FilesystemError> {
|
|
661
|
+
self.may_pread(pos)?;
|
|
662
|
+
self.rw_verify_area(Some(pos), buf.len())?;
|
|
663
|
+
let count = buf.len().min(MAX_RW_COUNT);
|
|
664
|
+
self.ops.read_iter(&mut self.kiocb(pos), &mut buf[..count])
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/// Write at its position, moving it past what was written, as `vfs_write`
|
|
668
|
+
/// does; a stream starts from no position and keeps none, as
|
|
669
|
+
/// `new_sync_write` hands it `ki_pos` 0.
|
|
670
|
+
pub fn write(&mut self, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
671
|
+
self.may_write()?;
|
|
672
|
+
let ppos = self.file_ppos();
|
|
673
|
+
self.rw_verify_area(ppos, buf.len())?;
|
|
674
|
+
let mut iocb = self.kiocb(ppos.unwrap_or(0));
|
|
675
|
+
let written = self
|
|
676
|
+
.ops
|
|
677
|
+
.write_iter(&mut iocb, &buf[..buf.len().min(MAX_RW_COUNT)])?;
|
|
678
|
+
if written > 0 && ppos.is_some() {
|
|
679
|
+
self.pos = iocb.pos;
|
|
680
|
+
}
|
|
681
|
+
Ok(written)
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/// Write at `pos`, leaving its position, as `pwrite` does. Opened to
|
|
685
|
+
/// append, it lands at the end whatever `pos` says.
|
|
686
|
+
pub fn pwrite(&mut self, buf: &[u8], pos: u64) -> Result<usize, FilesystemError> {
|
|
687
|
+
self.may_pwrite(pos)?;
|
|
688
|
+
self.rw_verify_area(Some(pos), buf.len())?;
|
|
689
|
+
self.ops
|
|
690
|
+
.write_iter(&mut self.kiocb(pos), &buf[..buf.len().min(MAX_RW_COUNT)])
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
/// Make what was written durable, as `vfs_fsync` does, and `vfs_fsync_range`
|
|
694
|
+
/// passes `datasync` through.
|
|
695
|
+
pub fn fsync(&mut self, datasync: bool) -> Result<(), FilesystemError> {
|
|
696
|
+
self.ops.fsync(datasync)
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
/// What it reports of itself, as `vfs_getattr` answers.
|
|
700
|
+
pub fn getattr(&self) -> Result<Kstat, FilesystemError> {
|
|
701
|
+
self.path.dentry().getattr()
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
/// Truncate or extend it through what it was opened on, as `do_ftruncate`
|
|
705
|
+
/// refuses anything but a file open to write, and reaches `->setattr`.
|
|
706
|
+
pub fn truncate(&mut self, len: u64) -> Result<(), FilesystemError> {
|
|
707
|
+
if self.kind() != FileType::RegFile || !self.writable() || len > OFFSET_MAX {
|
|
708
|
+
return Err(FilesystemError::InvalidInput);
|
|
709
|
+
}
|
|
710
|
+
self.cred.security_file_truncate(&self.security)?;
|
|
711
|
+
let dentry = self.path.dentry();
|
|
712
|
+
let held = dentry.inode_lock()?;
|
|
713
|
+
notify_change(dentry, &held, &Iattr::size(len).through(self.ops.as_ref()))
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/// Hand `ctx` the entries of the directory from where it stands until the
|
|
717
|
+
/// actor stops, leaving it past each taken, as `iterate_dir` moves `f_pos`. It
|
|
718
|
+
/// holds the directory's lock shared, so a removal through the VFS lands
|
|
719
|
+
/// before or after, and a removed directory answers not found, as it asks
|
|
720
|
+
/// `IS_DEADDIR`.
|
|
721
|
+
pub fn iterate_dir(&mut self, ctx: &mut DirContext<'_>) -> Result<(), FilesystemError> {
|
|
722
|
+
let dentry = self.path.dentry().clone();
|
|
723
|
+
let _held = dentry.inode_lock_shared()?;
|
|
724
|
+
if dentry.is_deaddir() {
|
|
725
|
+
return Err(FilesystemError::NotFound);
|
|
726
|
+
}
|
|
727
|
+
ctx.pos = self.pos;
|
|
728
|
+
ctx.cred = self.cred.clone();
|
|
729
|
+
let listed = self.ops.iterate_shared(&dentry, ctx);
|
|
730
|
+
self.pos = ctx.pos;
|
|
731
|
+
listed
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/// What its Filesystem lists past `.` and `..`, for a test of what the
|
|
735
|
+
/// Filesystem answers beneath the VFS.
|
|
736
|
+
#[cfg(test)]
|
|
737
|
+
pub(crate) fn iterate_shared(&mut self) -> Result<Vec<DirEntry>, FilesystemError> {
|
|
738
|
+
let dentry = self.path.dentry().clone();
|
|
739
|
+
let mut entries = Vec::new();
|
|
740
|
+
let mut actor = |emit: &DirEmit<'_>| {
|
|
741
|
+
entries.push(emit.to_dir_entry());
|
|
742
|
+
true
|
|
743
|
+
};
|
|
744
|
+
self.ops
|
|
745
|
+
.iterate_shared(&dentry, &mut DirContext::new(2, &mut actor))?;
|
|
746
|
+
Ok(entries)
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/// Close it once the last descriptor goes, answering what its flush
|
|
750
|
+
/// answers, as `filp_close` does; dropping it is what `->release` does.
|
|
751
|
+
pub fn close(mut self) -> Result<(), FilesystemError> {
|
|
752
|
+
self.ops.flush()
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
#[cfg(test)]
|
|
757
|
+
mod tests {
|
|
758
|
+
use super::super::kern_path::filp_open;
|
|
759
|
+
use super::super::vfs_fstatat;
|
|
760
|
+
use super::*;
|
|
761
|
+
use crate::filesystem::{create_pipe_files, PipeInodeInfo};
|
|
762
|
+
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
763
|
+
|
|
764
|
+
use crate::filesystem::{Filesystem, InodeOperations, Lookup, Overlay, Tmpfs};
|
|
765
|
+
use crate::mount::{Access, Mount};
|
|
766
|
+
use crate::vfs::{Identity, Inode};
|
|
767
|
+
|
|
768
|
+
/// A file whose seek lands at a position of its own.
|
|
769
|
+
struct SeeksToNine;
|
|
770
|
+
|
|
771
|
+
impl FileOperations for SeeksToNine {
|
|
772
|
+
fn llseek(
|
|
773
|
+
&mut self,
|
|
774
|
+
_dentry: &Dentry,
|
|
775
|
+
_pos: u64,
|
|
776
|
+
_offset: i64,
|
|
777
|
+
_whence: Whence,
|
|
778
|
+
) -> Result<u64, FilesystemError> {
|
|
779
|
+
Ok(9)
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/// A directory holding at every name a file whose `open` answers what
|
|
784
|
+
/// its function makes.
|
|
785
|
+
struct Opens(fn() -> Box<dyn FileOperations>);
|
|
786
|
+
|
|
787
|
+
impl crate::vfs::FileSystemType for Opens {
|
|
788
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
789
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
790
|
+
Ok(())
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
impl InodeOperations for Opens {
|
|
795
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
796
|
+
Ok(Kstat::new(FileType::RegFile, 0))
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
800
|
+
let found = Kstat::new(FileType::RegFile, 0);
|
|
801
|
+
Ok(Lookup::Found(Inode::new(&found, Arc::new(Self(self.0)))))
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
fn open(
|
|
805
|
+
&self,
|
|
806
|
+
_dentry: &Dentry,
|
|
807
|
+
_options: &OpenHow,
|
|
808
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
809
|
+
Ok((self.0)())
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
/// The file at any name beneath a Mount of a Filesystem whose `open`
|
|
814
|
+
/// answers what `opens` makes, opened to read.
|
|
815
|
+
fn open_one(opens: fn() -> Box<dyn FileOperations>) -> File {
|
|
816
|
+
let mount = Mount::new(
|
|
817
|
+
"/data",
|
|
818
|
+
Filesystem::new(Arc::new(Opens(opens))).expect("the instance must stand"),
|
|
819
|
+
Access::Read,
|
|
820
|
+
)
|
|
821
|
+
.expect("/data is a guest path");
|
|
822
|
+
let read = OpenHow {
|
|
823
|
+
read: true,
|
|
824
|
+
..OpenHow::default()
|
|
825
|
+
};
|
|
826
|
+
filp_open(&Arc::default(), &Path::root(mount.mnt()), "file", &read).expect("the file opens")
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
/// A file its Filesystem opens with no position, whose reads answer the
|
|
830
|
+
/// position they were handed and move it past what they took.
|
|
831
|
+
struct Stream;
|
|
832
|
+
|
|
833
|
+
impl FileOperations for Stream {
|
|
834
|
+
fn is_stream(&self) -> bool {
|
|
835
|
+
true
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
fn read_iter(
|
|
839
|
+
&mut self,
|
|
840
|
+
iocb: &mut Kiocb,
|
|
841
|
+
buf: &mut [u8],
|
|
842
|
+
) -> Result<usize, FilesystemError> {
|
|
843
|
+
buf[0] = iocb.pos as u8;
|
|
844
|
+
iocb.pos += buf.len() as u64;
|
|
845
|
+
Ok(buf.len())
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// The stream's own read moves the position handed to it, so only handing
|
|
850
|
+
// none and keeping none leaves each read at 0, as file_ppos answers NULL
|
|
851
|
+
// for FMODE_STREAM.
|
|
852
|
+
// @behavior RT-328
|
|
853
|
+
#[test]
|
|
854
|
+
fn a_stream_read_keeps_no_position() {
|
|
855
|
+
let mut file = open_one(|| Box::new(Stream));
|
|
856
|
+
let mut read = || {
|
|
857
|
+
let mut buf = [9; 4];
|
|
858
|
+
file.read(&mut buf).map(|_| buf[0])
|
|
859
|
+
};
|
|
860
|
+
let (first, second) = (read(), read());
|
|
861
|
+
assert_eq!(
|
|
862
|
+
(first, second, file.pos()),
|
|
863
|
+
(Ok(0), Ok(0), 0),
|
|
864
|
+
"File::read on a stream must hand each read position 0 and leave the file's position \
|
|
865
|
+
where it was, as new_sync_read keeps no ki_pos for FMODE_STREAM"
|
|
866
|
+
);
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// A file a Filesystem opens as a stream would otherwise seek as any file
|
|
870
|
+
// does, so only the open's mark keeps a position from it.
|
|
871
|
+
// @behavior RT-273
|
|
872
|
+
#[test]
|
|
873
|
+
fn a_file_its_filesystem_opens_as_a_stream_takes_no_position() {
|
|
874
|
+
let mut file = open_one(|| Box::new(Stream));
|
|
875
|
+
assert_eq!(
|
|
876
|
+
(
|
|
877
|
+
file.llseek(0, Whence::Set),
|
|
878
|
+
file.pread(&mut [0], 0),
|
|
879
|
+
file.is_stream()
|
|
880
|
+
),
|
|
881
|
+
(
|
|
882
|
+
Err(FilesystemError::NotSeekable),
|
|
883
|
+
Err(FilesystemError::NotSeekable),
|
|
884
|
+
true
|
|
885
|
+
),
|
|
886
|
+
"File::llseek and File::pread on a file its Filesystem's open marks a stream must \
|
|
887
|
+
answer ESPIPE, as stream_open clears FMODE_LSEEK and FMODE_PREAD"
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
// A stream loses only its position, so an open that made the file still
|
|
892
|
+
// reports it made it.
|
|
893
|
+
#[test]
|
|
894
|
+
fn a_stream_keeps_what_its_open_made() {
|
|
895
|
+
let made = FMode::WRITE
|
|
896
|
+
.with(FMode::LSEEK)
|
|
897
|
+
.with(FMode::PWRITE)
|
|
898
|
+
.with(FMode::CREATED);
|
|
899
|
+
assert_eq!(
|
|
900
|
+
made.stream_open(),
|
|
901
|
+
FMode::WRITE.with(FMode::STREAM).with(FMode::CREATED),
|
|
902
|
+
"FMode::stream_open on a file its open made must keep FMODE_CREATED, as stream_open \
|
|
903
|
+
clears only FMODE_LSEEK, FMODE_PREAD, and FMODE_PWRITE"
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
/// A file its Filesystem gives no operation at all.
|
|
908
|
+
struct NoSeek;
|
|
909
|
+
|
|
910
|
+
impl FileOperations for NoSeek {}
|
|
911
|
+
|
|
912
|
+
// @behavior RT-280
|
|
913
|
+
#[test]
|
|
914
|
+
fn a_file_without_llseek_takes_no_seek() {
|
|
915
|
+
let mut file = open_one(|| Box::new(NoSeek));
|
|
916
|
+
assert_eq!(
|
|
917
|
+
(file.llseek(0, Whence::Set), file.pos()),
|
|
918
|
+
(Err(FilesystemError::NotSeekable), 0),
|
|
919
|
+
"File::llseek of a file whose Filesystem answers no llseek must answer ESPIPE and \
|
|
920
|
+
leave its position, as Linux clears FMODE_LSEEK for one"
|
|
921
|
+
);
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
// @behavior RT-248
|
|
925
|
+
#[test]
|
|
926
|
+
fn a_file_answers_where_its_own_seek_lands() {
|
|
927
|
+
let mut file = open_one(|| Box::new(SeeksToNine));
|
|
928
|
+
assert_eq!(
|
|
929
|
+
(file.llseek(0, Whence::Set), file.llseek(0, Whence::Cur)),
|
|
930
|
+
(Ok(9), Ok(9)),
|
|
931
|
+
"File::llseek on a file answering llseek itself must answer and keep where that \
|
|
932
|
+
seek lands, as vfs_llseek asks f_op->llseek"
|
|
933
|
+
);
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
/// A directory holding a file at every name, neither with `open`.
|
|
937
|
+
struct OpensNothing;
|
|
938
|
+
|
|
939
|
+
impl crate::vfs::FileSystemType for OpensNothing {
|
|
940
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
941
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
942
|
+
Ok(())
|
|
943
|
+
}
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
impl InodeOperations for OpensNothing {
|
|
947
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
948
|
+
Ok(match dentry.is_root() {
|
|
949
|
+
false => Kstat::new(FileType::RegFile, 0),
|
|
950
|
+
true => Kstat::new(FileType::Dir, 0),
|
|
951
|
+
})
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
955
|
+
let found = Kstat::new(FileType::RegFile, 0);
|
|
956
|
+
Ok(Lookup::Found(Inode::new(&found, Arc::new(Self))))
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
// Only `O_PATH` opens a Linux directory for neither, and a read of that
|
|
961
|
+
// answers EBADF before anything asks what it is.
|
|
962
|
+
// @behavior RT-235
|
|
963
|
+
#[test]
|
|
964
|
+
fn a_directory_opened_for_neither_reading_nor_writing_refuses_a_read() {
|
|
965
|
+
let tmpfs: Tmpfs = [("d/f", "x")].into_iter().collect();
|
|
966
|
+
let mount = Mount::new(
|
|
967
|
+
"/data",
|
|
968
|
+
Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
|
|
969
|
+
Access::Read,
|
|
970
|
+
)
|
|
971
|
+
.expect("/data is a guest path");
|
|
972
|
+
let how = OpenHow {
|
|
973
|
+
directory: true,
|
|
974
|
+
..OpenHow::default()
|
|
975
|
+
};
|
|
976
|
+
let Ok(mut dir) = filp_open(&Arc::default(), &Path::root(mount.mnt()), "d", &how) else {
|
|
977
|
+
panic!("the directory must open");
|
|
978
|
+
};
|
|
979
|
+
assert_eq!(
|
|
980
|
+
dir.read(&mut [0; 1]),
|
|
981
|
+
Err(FilesystemError::BadDescriptor),
|
|
982
|
+
"File::read of a directory opened for neither reading nor writing must answer EBADF"
|
|
983
|
+
);
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// Linux's `generic_write_checks_count` returns before it reads the end for
|
|
987
|
+
// a write of nothing, and `new_sync_write` moves `f_pos` only past a positive
|
|
988
|
+
// answer, so a Filesystem moving `Kiocb::pos` regardless cannot move a file.
|
|
989
|
+
// @behavior RT-222
|
|
990
|
+
#[test]
|
|
991
|
+
fn a_write_of_nothing_leaves_an_appending_file_where_it_stands() {
|
|
992
|
+
let mount = Mount::new(
|
|
993
|
+
"/data",
|
|
994
|
+
Filesystem::new(Arc::new(Tmpfs::new())).expect("the instance must stand"),
|
|
995
|
+
Access::ReadWrite,
|
|
996
|
+
)
|
|
997
|
+
.expect("/data is a guest path");
|
|
998
|
+
let how = OpenHow {
|
|
999
|
+
write: true,
|
|
1000
|
+
create: true,
|
|
1001
|
+
append: true,
|
|
1002
|
+
..OpenHow::default()
|
|
1003
|
+
};
|
|
1004
|
+
let Ok(mut file) = filp_open(&Arc::default(), &Path::root(mount.mnt()), "log", &how) else {
|
|
1005
|
+
panic!("the file must be made");
|
|
1006
|
+
};
|
|
1007
|
+
file.write(b"hello").expect("the write must land");
|
|
1008
|
+
file.llseek(0, Whence::Set).expect("the seek must land");
|
|
1009
|
+
file.write(b"").expect("a write of nothing must answer");
|
|
1010
|
+
assert_eq!(
|
|
1011
|
+
file.llseek(0, Whence::Cur),
|
|
1012
|
+
Ok(0),
|
|
1013
|
+
"File::write of nothing to a file opened to append, positioned at 0, must leave it at 0"
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// A built-in Filesystem always has `open`, so only one without it shows
|
|
1018
|
+
// what the runtime answers.
|
|
1019
|
+
// @behavior RT-218
|
|
1020
|
+
#[test]
|
|
1021
|
+
fn an_entry_without_open_opens_with_no_operations() {
|
|
1022
|
+
let mount = Mount::new(
|
|
1023
|
+
"/data",
|
|
1024
|
+
Filesystem::new(Arc::new(OpensNothing)).expect("the instance must stand"),
|
|
1025
|
+
Access::Read,
|
|
1026
|
+
)
|
|
1027
|
+
.expect("/data is a guest path");
|
|
1028
|
+
let how = OpenHow {
|
|
1029
|
+
read: true,
|
|
1030
|
+
..OpenHow::default()
|
|
1031
|
+
};
|
|
1032
|
+
let cred = Arc::default();
|
|
1033
|
+
let read = filp_open(&cred, &Path::root(mount.mnt()), "file", &how)
|
|
1034
|
+
.map_err(|_| "the open was refused")
|
|
1035
|
+
.map(|mut file| file.read(&mut [0; 1]));
|
|
1036
|
+
assert_eq!(
|
|
1037
|
+
read,
|
|
1038
|
+
Ok(Err(FilesystemError::InvalidInput)),
|
|
1039
|
+
"vfs::do_file_open of a file whose Filesystem has no open must succeed, and File::read \
|
|
1040
|
+
of it must answer EINVAL, as Linux's opens with no f_op->open"
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/// `file.txt`, seven bytes long, opened for `access`.
|
|
1045
|
+
fn open(read: bool, write: bool) -> File {
|
|
1046
|
+
let tmpfs = Tmpfs::try_from_iter([("file.txt", "granted")]).expect("the seed must fit");
|
|
1047
|
+
let mount = Mount::new(
|
|
1048
|
+
"/data",
|
|
1049
|
+
Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
|
|
1050
|
+
Access::ReadWrite,
|
|
1051
|
+
)
|
|
1052
|
+
.expect("/data is a guest path");
|
|
1053
|
+
let how = OpenHow {
|
|
1054
|
+
read,
|
|
1055
|
+
write,
|
|
1056
|
+
..OpenHow::default()
|
|
1057
|
+
};
|
|
1058
|
+
filp_open(&Arc::default(), &Path::root(mount.mnt()), "file.txt", &how)
|
|
1059
|
+
.unwrap_or_else(|_| panic!("file.txt must open"))
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
// Opening what a path already names walks nothing, so only
|
|
1063
|
+
// `do_dentry_open` can take write access to the mount, as in Linux.
|
|
1064
|
+
// @behavior RT-078
|
|
1065
|
+
#[test]
|
|
1066
|
+
fn a_dentry_opened_to_write_beneath_a_read_only_mount_answers_erofs() {
|
|
1067
|
+
let tmpfs = Tmpfs::try_from_iter([("file.txt", "granted")]).expect("the seed must fit");
|
|
1068
|
+
let mount = Mount::new(
|
|
1069
|
+
"/data",
|
|
1070
|
+
Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
|
|
1071
|
+
Access::Read,
|
|
1072
|
+
)
|
|
1073
|
+
.expect("/data is a guest path");
|
|
1074
|
+
let read = OpenHow {
|
|
1075
|
+
read: true,
|
|
1076
|
+
..OpenHow::default()
|
|
1077
|
+
};
|
|
1078
|
+
let write = OpenHow {
|
|
1079
|
+
write: true,
|
|
1080
|
+
..OpenHow::default()
|
|
1081
|
+
};
|
|
1082
|
+
let path = filp_open(&Arc::default(), &Path::root(mount.mnt()), "file.txt", &read)
|
|
1083
|
+
.unwrap_or_else(|_| panic!("file.txt must open to read"))
|
|
1084
|
+
.path()
|
|
1085
|
+
.clone();
|
|
1086
|
+
assert_eq!(
|
|
1087
|
+
File::dentry_open(path, &write, &Arc::default()).err(),
|
|
1088
|
+
Some(FilesystemError::ReadOnlyFilesystem),
|
|
1089
|
+
"File::dentry_open to write a file beneath a read-only Mount must answer EROFS"
|
|
1090
|
+
);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
// No Wasm Driver stands in front of these calls, so the file itself is what
|
|
1094
|
+
// refuses each one before its Filesystem is asked.
|
|
1095
|
+
// @behavior RT-180
|
|
1096
|
+
#[test]
|
|
1097
|
+
fn an_open_file_answers_for_what_it_was_opened_for() {
|
|
1098
|
+
let mut buf = [0; 2];
|
|
1099
|
+
assert_eq!(
|
|
1100
|
+
(
|
|
1101
|
+
open(true, false).write(b"xy"),
|
|
1102
|
+
open(true, false).pwrite(b"xy", 0),
|
|
1103
|
+
open(false, true).read(&mut buf),
|
|
1104
|
+
open(false, true).pread(&mut buf, 0),
|
|
1105
|
+
),
|
|
1106
|
+
(
|
|
1107
|
+
Err(FilesystemError::BadDescriptor),
|
|
1108
|
+
Err(FilesystemError::BadDescriptor),
|
|
1109
|
+
Err(FilesystemError::BadDescriptor),
|
|
1110
|
+
Err(FilesystemError::BadDescriptor),
|
|
1111
|
+
),
|
|
1112
|
+
"File::write and pwrite on a file opened to read, and File::read and pread on one \
|
|
1113
|
+
opened to write, must answer EBADF, as vfs_read and vfs_write do"
|
|
1114
|
+
);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
/// What a listing of the root from `pos` hands an actor taking `room`
|
|
1118
|
+
/// entries, as the name and the position of each, and where the directory
|
|
1119
|
+
/// stands once the actor refused the next.
|
|
1120
|
+
fn list(file: &mut File, pos: u64, room: usize) -> (Vec<(String, u64)>, u64) {
|
|
1121
|
+
let mut seen = Vec::new();
|
|
1122
|
+
let mut actor = |emit: &DirEmit<'_>| {
|
|
1123
|
+
let took = seen.len() < room;
|
|
1124
|
+
if took {
|
|
1125
|
+
seen.push((emit.name.to_string(), emit.offset));
|
|
1126
|
+
}
|
|
1127
|
+
took
|
|
1128
|
+
};
|
|
1129
|
+
file.llseek(pos as i64, Whence::Set)
|
|
1130
|
+
.unwrap_or_else(|_| panic!("the directory must seek"));
|
|
1131
|
+
file.iterate_dir(&mut DirContext::new(0, &mut actor))
|
|
1132
|
+
.unwrap_or_else(|_| panic!("the directory must list"));
|
|
1133
|
+
(seen, file.pos())
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
/// A directory reading its two entries at once, as a Bind does.
|
|
1137
|
+
struct ReadsWhole;
|
|
1138
|
+
|
|
1139
|
+
struct ReadsWholeDir(crate::filesystem::Rdir);
|
|
1140
|
+
|
|
1141
|
+
impl FileOperations for ReadsWholeDir {
|
|
1142
|
+
fn llseek(
|
|
1143
|
+
&mut self,
|
|
1144
|
+
dentry: &Dentry,
|
|
1145
|
+
pos: u64,
|
|
1146
|
+
offset: i64,
|
|
1147
|
+
whence: Whence,
|
|
1148
|
+
) -> Result<u64, FilesystemError> {
|
|
1149
|
+
generic_file_llseek(dentry, pos, offset, whence)
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
fn iterate_shared(
|
|
1153
|
+
&mut self,
|
|
1154
|
+
dentry: &Dentry,
|
|
1155
|
+
ctx: &mut DirContext<'_>,
|
|
1156
|
+
) -> Result<(), FilesystemError> {
|
|
1157
|
+
self.0.iterate(dentry, ctx, || {
|
|
1158
|
+
Ok(vec![
|
|
1159
|
+
DirEntry::new("a", FileType::RegFile),
|
|
1160
|
+
DirEntry::new("b", FileType::RegFile),
|
|
1161
|
+
])
|
|
1162
|
+
})
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1166
|
+
impl crate::vfs::FileSystemType for ReadsWhole {
|
|
1167
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1168
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
1169
|
+
Ok(())
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
impl InodeOperations for ReadsWhole {
|
|
1174
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1175
|
+
Ok(Kstat::new(FileType::Dir, 0))
|
|
1176
|
+
}
|
|
1177
|
+
|
|
1178
|
+
fn open(
|
|
1179
|
+
&self,
|
|
1180
|
+
_dentry: &Dentry,
|
|
1181
|
+
_options: &OpenHow,
|
|
1182
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1183
|
+
Ok(Box::new(ReadsWholeDir(crate::filesystem::Rdir::default())))
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
/// A Filesystem listing whole, whose directory cannot be stated.
|
|
1188
|
+
struct CannotStat;
|
|
1189
|
+
|
|
1190
|
+
impl crate::vfs::FileSystemType for CannotStat {
|
|
1191
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1192
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
1193
|
+
Ok(())
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
impl InodeOperations for CannotStat {
|
|
1198
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1199
|
+
Err(FilesystemError::Other("no stat".into()))
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
fn open(
|
|
1203
|
+
&self,
|
|
1204
|
+
_dentry: &Dentry,
|
|
1205
|
+
_options: &OpenHow,
|
|
1206
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1207
|
+
Ok(Box::new(ReadsWholeDir(crate::filesystem::Rdir::default())))
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/// A Filesystem listing whole, whose every directory stats as one inode.
|
|
1212
|
+
struct StatsAsOne;
|
|
1213
|
+
|
|
1214
|
+
impl crate::vfs::FileSystemType for StatsAsOne {
|
|
1215
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1216
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
1217
|
+
Ok(())
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
impl InodeOperations for StatsAsOne {
|
|
1222
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1223
|
+
Ok(Kstat::new(FileType::Dir, 0).with_identity(7, 9))
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
fn open(
|
|
1227
|
+
&self,
|
|
1228
|
+
_dentry: &Dentry,
|
|
1229
|
+
_options: &OpenHow,
|
|
1230
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1231
|
+
Ok(Box::new(ReadsWholeDir(crate::filesystem::Rdir::default())))
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1235
|
+
/// The identities a listing of `dir` from the start hands `.` and `..`.
|
|
1236
|
+
fn read_dots(dir: &mut File) -> Vec<Option<Identity>> {
|
|
1237
|
+
let mut dots = Vec::new();
|
|
1238
|
+
dir.iterate_dir(&mut DirContext::new(0, &mut |entry| {
|
|
1239
|
+
if entry.name.starts_with('.') {
|
|
1240
|
+
dots.push(entry.identity);
|
|
1241
|
+
}
|
|
1242
|
+
true
|
|
1243
|
+
}))
|
|
1244
|
+
.unwrap_or_else(|_| panic!("the directory must list"));
|
|
1245
|
+
dots
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
// The root was made without asking the Filesystem, so only `.` and `..`
|
|
1249
|
+
// taken from its stat name the inode it reports.
|
|
1250
|
+
// @behavior RT-305
|
|
1251
|
+
#[test]
|
|
1252
|
+
fn a_listing_read_whole_names_its_dots_as_a_stat_does() {
|
|
1253
|
+
let mount = Mount::new(
|
|
1254
|
+
"/data",
|
|
1255
|
+
Filesystem::new(Arc::new(StatsAsOne)).expect("the instance must stand"),
|
|
1256
|
+
Access::Read,
|
|
1257
|
+
)
|
|
1258
|
+
.expect("/data is a guest path");
|
|
1259
|
+
let Ok(mut dir) = File::root(mount.mnt(), &Arc::default()) else {
|
|
1260
|
+
panic!("the root must open");
|
|
1261
|
+
};
|
|
1262
|
+
let one = Identity::from_numbers(7, 9);
|
|
1263
|
+
assert_eq!(
|
|
1264
|
+
read_dots(&mut dir),
|
|
1265
|
+
[one, one],
|
|
1266
|
+
"File::iterate_dir through Rdir of a root must name . and .. as the Filesystem's \
|
|
1267
|
+
stat of it names them"
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
// @behavior RT-305
|
|
1272
|
+
#[test]
|
|
1273
|
+
fn a_tmpfs_listing_names_its_dots_as_a_stat_does() {
|
|
1274
|
+
let tmpfs: Tmpfs = [("d/f", "x")].into_iter().collect();
|
|
1275
|
+
let mount = Mount::new(
|
|
1276
|
+
"/data",
|
|
1277
|
+
Filesystem::new(Arc::new(tmpfs)).expect("the instance must stand"),
|
|
1278
|
+
Access::Read,
|
|
1279
|
+
)
|
|
1280
|
+
.expect("/data is a guest path");
|
|
1281
|
+
let (root, cred) = (Path::root(mount.mnt()), Arc::default());
|
|
1282
|
+
let stat = |path| {
|
|
1283
|
+
crate::vfs::vfs_fstatat(&cred, &root, path, false)
|
|
1284
|
+
.ok()
|
|
1285
|
+
.and_then(|stat| Identity::of(&stat))
|
|
1286
|
+
};
|
|
1287
|
+
let how = OpenHow {
|
|
1288
|
+
read: true,
|
|
1289
|
+
directory: true,
|
|
1290
|
+
..OpenHow::default()
|
|
1291
|
+
};
|
|
1292
|
+
let Ok(mut dir) = filp_open(&cred, &root, "d", &how) else {
|
|
1293
|
+
panic!("the directory must open");
|
|
1294
|
+
};
|
|
1295
|
+
assert_eq!(
|
|
1296
|
+
read_dots(&mut dir),
|
|
1297
|
+
[stat("d"), stat(".")],
|
|
1298
|
+
"File::iterate_dir of a Tmpfs directory must name . and .. as a stat of the \
|
|
1299
|
+
directory and of the one above it names them"
|
|
1300
|
+
);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// The entries read fine, so only a listing asking the stat for `.` before
|
|
1304
|
+
// it hands any entry answers the failure with nothing handed.
|
|
1305
|
+
// @behavior RT-304
|
|
1306
|
+
#[test]
|
|
1307
|
+
fn a_listing_whose_directory_cannot_be_stated_hands_nothing() {
|
|
1308
|
+
let mount = Mount::new(
|
|
1309
|
+
"/data",
|
|
1310
|
+
Filesystem::new(Arc::new(CannotStat)).expect("the instance must stand"),
|
|
1311
|
+
Access::Read,
|
|
1312
|
+
)
|
|
1313
|
+
.expect("/data is a guest path");
|
|
1314
|
+
let Ok(mut dir) = File::root(mount.mnt(), &Arc::default()) else {
|
|
1315
|
+
panic!("the root must open");
|
|
1316
|
+
};
|
|
1317
|
+
let mut seen = 0;
|
|
1318
|
+
let answer = dir.iterate_dir(&mut DirContext::new(0, &mut |_| {
|
|
1319
|
+
seen += 1;
|
|
1320
|
+
true
|
|
1321
|
+
}));
|
|
1322
|
+
assert_eq!(
|
|
1323
|
+
(answer, seen),
|
|
1324
|
+
(Err(FilesystemError::Other("no stat".into())), 0),
|
|
1325
|
+
"File::iterate_dir through Rdir of a directory its Filesystem cannot stat must answer \
|
|
1326
|
+
that failure and hand no entry, as ovl_cache_update fails the listing"
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
|
|
1330
|
+
/// A listing of `file` from the start, its actor stopping after `room`
|
|
1331
|
+
/// entries.
|
|
1332
|
+
fn begin_listing(file: &mut File, room: usize) -> Result<(), FilesystemError> {
|
|
1333
|
+
let mut seen = 0;
|
|
1334
|
+
file.iterate_dir(&mut DirContext::new(0, &mut |_| {
|
|
1335
|
+
seen += 1;
|
|
1336
|
+
seen < room
|
|
1337
|
+
}))
|
|
1338
|
+
}
|
|
1339
|
+
|
|
1340
|
+
/// A security module refusing to open any directory.
|
|
1341
|
+
struct RefusesDirectories;
|
|
1342
|
+
|
|
1343
|
+
impl crate::vfs::SecurityOperations for RefusesDirectories {
|
|
1344
|
+
fn file_open(
|
|
1345
|
+
&self,
|
|
1346
|
+
_path: crate::vfs::SecurityPath<'_>,
|
|
1347
|
+
kind: FileType,
|
|
1348
|
+
_mode: FMode,
|
|
1349
|
+
_how: &OpenHow,
|
|
1350
|
+
) -> Result<FileSecurity, FilesystemError> {
|
|
1351
|
+
match kind {
|
|
1352
|
+
FileType::Dir => Err(FilesystemError::PermissionDenied),
|
|
1353
|
+
_ => Ok(None),
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// A root opened without the modules would hand a descriptor out whatever
|
|
1359
|
+
// they say, so only the hook dentry_open asks refuses it.
|
|
1360
|
+
// @behavior RT-291
|
|
1361
|
+
#[test]
|
|
1362
|
+
fn opening_a_mounts_root_asks_the_security_modules() {
|
|
1363
|
+
let mount = Mount::new(
|
|
1364
|
+
"/data",
|
|
1365
|
+
Filesystem::new(Arc::new(Tmpfs::default())).expect("the instance must stand"),
|
|
1366
|
+
Access::Read,
|
|
1367
|
+
)
|
|
1368
|
+
.expect("/data is a guest path");
|
|
1369
|
+
let cred = Cred::new(vec![Arc::new(RefusesDirectories)]);
|
|
1370
|
+
assert_eq!(
|
|
1371
|
+
File::root(mount.mnt(), &cred).err(),
|
|
1372
|
+
Some(FilesystemError::PermissionDenied),
|
|
1373
|
+
"File::root under a security module refusing to open a directory must answer as \
|
|
1374
|
+
the module refuses, as dentry_open asks security_file_open"
|
|
1375
|
+
);
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
// Every descriptor holds a listing of its own, so only counting them together
|
|
1379
|
+
// keeps a guest from multiplying a directory by its open files.
|
|
1380
|
+
// @behavior RT-237
|
|
1381
|
+
#[test]
|
|
1382
|
+
fn listings_held_between_calls_count_against_their_limit() {
|
|
1383
|
+
let mount = Mount::new(
|
|
1384
|
+
"/data",
|
|
1385
|
+
Filesystem::new(Arc::new(ReadsWhole)).expect("the instance must stand"),
|
|
1386
|
+
Access::Read,
|
|
1387
|
+
)
|
|
1388
|
+
.expect("/data is a guest path");
|
|
1389
|
+
// `.`, `..`, and two one-letter names.
|
|
1390
|
+
let one_listing = 4 * size_of::<DirEntry>() + 1 + 2 + 2;
|
|
1391
|
+
let cred = crate::security::prepare_creds_within(&[], Some(one_listing));
|
|
1392
|
+
// A Wasm Driver opens the root before the Policies apply and lists through
|
|
1393
|
+
// the Cred they were added to, so both must count against one limit.
|
|
1394
|
+
let restricted = crate::security::restrict_self(&cred, [&mount]);
|
|
1395
|
+
let open = |cred: &Arc<Cred>| {
|
|
1396
|
+
File::root(mount.mnt(), cred).unwrap_or_else(|_| panic!("the root must open"))
|
|
1397
|
+
};
|
|
1398
|
+
let (mut first, mut second, mut whole) = (open(&cred), open(&restricted), open(&cred));
|
|
1399
|
+
let held = begin_listing(&mut first, 1);
|
|
1400
|
+
let refused = begin_listing(&mut second, 1);
|
|
1401
|
+
let read_whole = begin_listing(&mut whole, usize::MAX);
|
|
1402
|
+
drop(first);
|
|
1403
|
+
let after_close = begin_listing(&mut second, 1);
|
|
1404
|
+
assert_eq!(
|
|
1405
|
+
(held, refused, read_whole, after_close),
|
|
1406
|
+
(
|
|
1407
|
+
Ok(()),
|
|
1408
|
+
Err(FilesystemError::OutOfMemory),
|
|
1409
|
+
Err(FilesystemError::OutOfMemory),
|
|
1410
|
+
Ok(())
|
|
1411
|
+
),
|
|
1412
|
+
"File::iterate_dir of a second listing past its caller's memory limit, stopped part \
|
|
1413
|
+
way or read whole, must answer ENOMEM, as ovl_cache_get allocates before dir_emit, \
|
|
1414
|
+
and closing the first must give its bytes back"
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/// Each entry of the root of `filesystem` with what a listing names it
|
|
1419
|
+
/// and what a stat of it names it.
|
|
1420
|
+
fn identities<F: crate::vfs::FileSystemType>(
|
|
1421
|
+
filesystem: F,
|
|
1422
|
+
) -> Vec<(String, Option<Identity>, Option<Identity>)> {
|
|
1423
|
+
let mount = Mount::new(
|
|
1424
|
+
"/data",
|
|
1425
|
+
Filesystem::new(Arc::new(filesystem)).expect("the instance must stand"),
|
|
1426
|
+
Access::ReadWrite,
|
|
1427
|
+
)
|
|
1428
|
+
.expect("/data is a guest path");
|
|
1429
|
+
let start = Path::root(mount.mnt());
|
|
1430
|
+
let mut root = File::root(mount.mnt(), &Arc::default())
|
|
1431
|
+
.unwrap_or_else(|_| panic!("the root must open"));
|
|
1432
|
+
let mut listed = Vec::new();
|
|
1433
|
+
root.llseek(2, Whence::Set)
|
|
1434
|
+
.unwrap_or_else(|_| panic!("the root must seek past its dots"));
|
|
1435
|
+
root.iterate_dir(&mut DirContext::new(0, &mut |emit| {
|
|
1436
|
+
listed.push((emit.name.to_string(), emit.identity));
|
|
1437
|
+
true
|
|
1438
|
+
}))
|
|
1439
|
+
.unwrap_or_else(|_| panic!("the root must list"));
|
|
1440
|
+
listed
|
|
1441
|
+
.into_iter()
|
|
1442
|
+
.map(|(name, identity)| {
|
|
1443
|
+
let stat = vfs_fstatat(&Arc::default(), &start, &name, false)
|
|
1444
|
+
.unwrap_or_else(|_| panic!("{name} must stat"));
|
|
1445
|
+
(name, identity, Identity::of(&stat))
|
|
1446
|
+
})
|
|
1447
|
+
.collect()
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
fn assert_named_alike(label: &str, entries: Vec<(String, Option<Identity>, Option<Identity>)>) {
|
|
1451
|
+
assert!(!entries.is_empty(), "{label} must list something");
|
|
1452
|
+
for (name, listed, stat) in entries {
|
|
1453
|
+
assert!(
|
|
1454
|
+
listed.is_some() && listed == stat,
|
|
1455
|
+
"{name} of a {label} through File::iterate_dir must name what vfs_fstatat names, \
|
|
1456
|
+
not {listed:?} beside {stat:?}"
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
// @behavior RT-183
|
|
1462
|
+
#[test]
|
|
1463
|
+
fn a_tmpfs_lists_each_entry_as_a_stat_names_it() {
|
|
1464
|
+
let tmpfs: Tmpfs = [("a", "x"), ("d/b", "y")].into_iter().collect();
|
|
1465
|
+
assert_named_alike("Tmpfs", identities(tmpfs));
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1468
|
+
// @behavior RT-183
|
|
1469
|
+
#[test]
|
|
1470
|
+
fn an_overlay_lists_each_entry_as_a_stat_names_it() {
|
|
1471
|
+
let lower: Tmpfs = [("a", "x"), ("d/b", "y")].into_iter().collect();
|
|
1472
|
+
let upper = Tmpfs::new();
|
|
1473
|
+
let overlay = Overlay::new(
|
|
1474
|
+
vec![Filesystem::new(Arc::new(lower)).expect("the instance must stand")],
|
|
1475
|
+
Some(Filesystem::new(Arc::new(upper)).expect("the instance must stand")),
|
|
1476
|
+
)
|
|
1477
|
+
.unwrap_or_else(|_| panic!("the Overlay must make"));
|
|
1478
|
+
assert_named_alike("Overlay", identities(overlay));
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
// A host names no identity through a Bind beyond unix.
|
|
1482
|
+
// @behavior RT-183
|
|
1483
|
+
#[cfg(unix)]
|
|
1484
|
+
#[test]
|
|
1485
|
+
fn a_bind_lists_each_entry_as_a_stat_names_it() {
|
|
1486
|
+
let dir = std::env::temp_dir().join(format!("kobako-listed-{}", std::process::id()));
|
|
1487
|
+
std::fs::create_dir_all(dir.join("d")).expect("the host directory must make");
|
|
1488
|
+
std::fs::write(dir.join("a"), "x").expect("the host file must write");
|
|
1489
|
+
let bind =
|
|
1490
|
+
crate::filesystem::Bind::new(&dir).unwrap_or_else(|_| panic!("the Bind must make"));
|
|
1491
|
+
let entries = identities(bind);
|
|
1492
|
+
std::fs::remove_dir_all(&dir).expect("the host directory must go");
|
|
1493
|
+
assert_named_alike("Bind", entries);
|
|
1494
|
+
}
|
|
1495
|
+
|
|
1496
|
+
// @behavior RT-182
|
|
1497
|
+
#[test]
|
|
1498
|
+
fn a_listing_hands_the_dots_first_and_goes_on_where_it_stopped() {
|
|
1499
|
+
let tmpfs: Arc<Tmpfs> = Arc::new([("a", "x"), ("b", "y")].into_iter().collect());
|
|
1500
|
+
let mount = Mount::new(
|
|
1501
|
+
"/data",
|
|
1502
|
+
Filesystem::new(tmpfs).expect("the instance must stand"),
|
|
1503
|
+
Access::ReadWrite,
|
|
1504
|
+
)
|
|
1505
|
+
.expect("/data is a guest path");
|
|
1506
|
+
let mut root = File::root(mount.mnt(), &Arc::default())
|
|
1507
|
+
.unwrap_or_else(|_| panic!("the root must open"));
|
|
1508
|
+
let entry = |name: &str, at| (name.to_string(), at);
|
|
1509
|
+
assert_eq!(
|
|
1510
|
+
(list(&mut root, 0, 3), list(&mut root, 2, 9)),
|
|
1511
|
+
(
|
|
1512
|
+
(vec![entry(".", 0), entry("..", 1), entry("a", 2)], 3),
|
|
1513
|
+
(vec![entry("a", 2), entry("b", 3)], 4),
|
|
1514
|
+
),
|
|
1515
|
+
"File::iterate_dir must hand `.` and `..` ahead of the entries, each at its own \
|
|
1516
|
+
position as dir_emit hands ctx->pos, stand past the last one taken, and go on \
|
|
1517
|
+
from any of those positions"
|
|
1518
|
+
);
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
// @behavior RT-182
|
|
1522
|
+
#[test]
|
|
1523
|
+
fn a_listing_read_whole_goes_on_past_the_last_entry_taken() {
|
|
1524
|
+
let mount = Mount::new(
|
|
1525
|
+
"/data",
|
|
1526
|
+
Filesystem::new(Arc::new(ReadsWhole)).expect("the instance must stand"),
|
|
1527
|
+
Access::Read,
|
|
1528
|
+
)
|
|
1529
|
+
.expect("/data is a guest path");
|
|
1530
|
+
let mut root = File::root(mount.mnt(), &Arc::default())
|
|
1531
|
+
.unwrap_or_else(|_| panic!("the root must open"));
|
|
1532
|
+
let entry = |name: &str, at| (name.to_string(), at);
|
|
1533
|
+
assert_eq!(
|
|
1534
|
+
(list(&mut root, 0, 3), list(&mut root, 3, 9)),
|
|
1535
|
+
(
|
|
1536
|
+
(vec![entry(".", 0), entry("..", 1), entry("a", 2)], 3),
|
|
1537
|
+
(vec![entry("b", 3)], 4),
|
|
1538
|
+
),
|
|
1539
|
+
"File::iterate_dir over a Filesystem listing through Rdir must hand each entry its \
|
|
1540
|
+
own position and stand past the last one taken, so a listing going on from there \
|
|
1541
|
+
repeats nothing"
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
/// A file counting its syncs, at every name of a directory.
|
|
1546
|
+
struct CountsSyncs(Arc<AtomicUsize>);
|
|
1547
|
+
|
|
1548
|
+
impl FileOperations for CountsSyncs {
|
|
1549
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
1550
|
+
generic_write_sync(self, iocb, buf.len())
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
fn fsync(&mut self, _datasync: bool) -> Result<(), FilesystemError> {
|
|
1554
|
+
self.0.fetch_add(1, Ordering::SeqCst);
|
|
1555
|
+
Ok(())
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
|
|
1559
|
+
/// A file taking whatever it is handed, recording how much and how it syncs.
|
|
1560
|
+
#[derive(Clone, Default)]
|
|
1561
|
+
struct RecordsCalls {
|
|
1562
|
+
taken: Arc<AtomicUsize>,
|
|
1563
|
+
syncs: Arc<std::sync::Mutex<Vec<bool>>>,
|
|
1564
|
+
}
|
|
1565
|
+
|
|
1566
|
+
impl FileOperations for RecordsCalls {
|
|
1567
|
+
fn read_iter(
|
|
1568
|
+
&mut self,
|
|
1569
|
+
_iocb: &mut Kiocb,
|
|
1570
|
+
buf: &mut [u8],
|
|
1571
|
+
) -> Result<usize, FilesystemError> {
|
|
1572
|
+
self.taken.store(buf.len(), Ordering::SeqCst);
|
|
1573
|
+
Ok(0)
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
fn write_iter(&mut self, iocb: &mut Kiocb, buf: &[u8]) -> Result<usize, FilesystemError> {
|
|
1577
|
+
self.taken.store(buf.len(), Ordering::SeqCst);
|
|
1578
|
+
generic_write_sync(self, iocb, buf.len())
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
fn fsync(&mut self, datasync: bool) -> Result<(), FilesystemError> {
|
|
1582
|
+
self.syncs.lock().expect("never poisoned").push(datasync);
|
|
1583
|
+
Ok(())
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
impl crate::vfs::FileSystemType for RecordsCalls {
|
|
1588
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1589
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
1590
|
+
Ok(())
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
impl InodeOperations for RecordsCalls {
|
|
1595
|
+
fn getattr(&self, dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1596
|
+
Ok(match dentry.is_root() {
|
|
1597
|
+
false => Kstat::new(FileType::RegFile, 0),
|
|
1598
|
+
true => Kstat::new(FileType::Dir, 0),
|
|
1599
|
+
})
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
1603
|
+
let found = Kstat::new(FileType::RegFile, 0);
|
|
1604
|
+
Ok(Lookup::Found(Inode::new(&found, Arc::new(self.clone()))))
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
fn open(
|
|
1608
|
+
&self,
|
|
1609
|
+
_dentry: &Dentry,
|
|
1610
|
+
_options: &OpenHow,
|
|
1611
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1612
|
+
Ok(Box::new(self.clone()))
|
|
1613
|
+
}
|
|
1614
|
+
}
|
|
1615
|
+
|
|
1616
|
+
/// `calls` open at `file`, as `how` asks.
|
|
1617
|
+
fn open_recording(calls: &RecordsCalls, how: OpenHow) -> File {
|
|
1618
|
+
let mount = Mount::new(
|
|
1619
|
+
"/data",
|
|
1620
|
+
Filesystem::new(Arc::new(calls.clone())).expect("the instance must stand"),
|
|
1621
|
+
Access::ReadWrite,
|
|
1622
|
+
)
|
|
1623
|
+
.expect("/data is a guest path");
|
|
1624
|
+
filp_open(&Arc::default(), &Path::root(mount.mnt()), "file", &how)
|
|
1625
|
+
.unwrap_or_else(|_| panic!("the file must open"))
|
|
1626
|
+
}
|
|
1627
|
+
|
|
1628
|
+
// @behavior RT-251
|
|
1629
|
+
#[test]
|
|
1630
|
+
fn each_write_syncs_only_as_far_as_the_file_was_opened_to() {
|
|
1631
|
+
let calls = RecordsCalls::default();
|
|
1632
|
+
let open = |dsync, sync| {
|
|
1633
|
+
open_recording(
|
|
1634
|
+
&calls,
|
|
1635
|
+
OpenHow {
|
|
1636
|
+
write: true,
|
|
1637
|
+
dsync,
|
|
1638
|
+
sync,
|
|
1639
|
+
..OpenHow::default()
|
|
1640
|
+
},
|
|
1641
|
+
)
|
|
1642
|
+
};
|
|
1643
|
+
open(false, false).write(b"x").expect("the write lands");
|
|
1644
|
+
open(true, false).write(b"x").expect("the write lands");
|
|
1645
|
+
open(false, true).write(b"x").expect("the write lands");
|
|
1646
|
+
let mut file = open(false, false);
|
|
1647
|
+
file.fsync(true).expect("the sync lands");
|
|
1648
|
+
file.fsync(false).expect("the sync lands");
|
|
1649
|
+
assert_eq!(
|
|
1650
|
+
*calls.syncs.lock().expect("never poisoned"),
|
|
1651
|
+
[true, false, true, false],
|
|
1652
|
+
"File::write on a file opened O_DSYNC must sync its data alone, on one opened O_SYNC \
|
|
1653
|
+
all of it, and File::fsync must pass datasync through, as vfs_fsync_range does"
|
|
1654
|
+
);
|
|
1655
|
+
}
|
|
1656
|
+
|
|
1657
|
+
// @behavior RT-252
|
|
1658
|
+
#[test]
|
|
1659
|
+
fn a_single_call_takes_at_most_the_most_one_read_or_write_takes() {
|
|
1660
|
+
let calls = RecordsCalls::default();
|
|
1661
|
+
let mut file = open_recording(
|
|
1662
|
+
&calls,
|
|
1663
|
+
OpenHow {
|
|
1664
|
+
read: true,
|
|
1665
|
+
write: true,
|
|
1666
|
+
..OpenHow::default()
|
|
1667
|
+
},
|
|
1668
|
+
);
|
|
1669
|
+
// The allocator hands zeroed pages it has not touched, so this holds little.
|
|
1670
|
+
let mut past = vec![0u8; MAX_RW_COUNT + 1];
|
|
1671
|
+
let mut taken = |call: &mut dyn FnMut(&mut File, &mut [u8])| {
|
|
1672
|
+
calls.taken.store(0, Ordering::SeqCst);
|
|
1673
|
+
call(&mut file, &mut past);
|
|
1674
|
+
calls.taken.load(Ordering::SeqCst)
|
|
1675
|
+
};
|
|
1676
|
+
let answers = [
|
|
1677
|
+
taken(&mut |file, buf| drop(file.read(buf))),
|
|
1678
|
+
taken(&mut |file, buf| drop(file.pread(buf, 0))),
|
|
1679
|
+
taken(&mut |file, buf| drop(file.write(buf))),
|
|
1680
|
+
taken(&mut |file, buf| drop(file.pwrite(buf, 0))),
|
|
1681
|
+
];
|
|
1682
|
+
assert_eq!(
|
|
1683
|
+
answers, [0x7fff_f000; 4],
|
|
1684
|
+
"File::read, pread, write, and pwrite past MAX_RW_COUNT must hand the file only that \
|
|
1685
|
+
many bytes, as vfs_read and vfs_write cut count"
|
|
1686
|
+
);
|
|
1687
|
+
}
|
|
1688
|
+
|
|
1689
|
+
struct HoldsCountsSyncs(Arc<AtomicUsize>);
|
|
1690
|
+
|
|
1691
|
+
impl crate::vfs::FileSystemType for HoldsCountsSyncs {
|
|
1692
|
+
fn fill_super(self: Arc<Self>, sb: &mut Filesystem) -> Result<(), FilesystemError> {
|
|
1693
|
+
sb.d_make_root(Inode::new(&Kstat::new(FileType::Dir, 0), self));
|
|
1694
|
+
Ok(())
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
impl InodeOperations for HoldsCountsSyncs {
|
|
1699
|
+
fn getattr(&self, _dentry: &Dentry) -> Result<Kstat, FilesystemError> {
|
|
1700
|
+
Ok(Kstat::new(FileType::Dir, 0))
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
fn lookup(&self, _dentry: &Dentry) -> Result<Lookup, FilesystemError> {
|
|
1704
|
+
let found = Kstat::new(FileType::RegFile, 0);
|
|
1705
|
+
let ops = Arc::new(Self(self.0.clone()));
|
|
1706
|
+
Ok(Lookup::Found(Inode::new(&found, ops)))
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
fn open(
|
|
1710
|
+
&self,
|
|
1711
|
+
_dentry: &Dentry,
|
|
1712
|
+
_options: &OpenHow,
|
|
1713
|
+
) -> Result<Box<dyn FileOperations>, FilesystemError> {
|
|
1714
|
+
Ok(Box::new(CountsSyncs(self.0.clone())))
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1718
|
+
// @behavior RT-091
|
|
1719
|
+
#[test]
|
|
1720
|
+
fn a_file_opened_to_sync_makes_each_write_durable() {
|
|
1721
|
+
let syncs = Arc::new(AtomicUsize::new(0));
|
|
1722
|
+
let mount = Mount::new(
|
|
1723
|
+
"/data",
|
|
1724
|
+
Filesystem::new(Arc::new(HoldsCountsSyncs(syncs.clone())))
|
|
1725
|
+
.expect("the instance must stand"),
|
|
1726
|
+
Access::ReadWrite,
|
|
1727
|
+
)
|
|
1728
|
+
.expect("/data is a guest path");
|
|
1729
|
+
let open = |sync| {
|
|
1730
|
+
let how = OpenHow {
|
|
1731
|
+
write: true,
|
|
1732
|
+
sync,
|
|
1733
|
+
..OpenHow::default()
|
|
1734
|
+
};
|
|
1735
|
+
filp_open(&Arc::default(), &Path::root(mount.mnt()), "file", &how)
|
|
1736
|
+
.unwrap_or_else(|_| panic!("the file must open"))
|
|
1737
|
+
};
|
|
1738
|
+
let mut plain = open(false);
|
|
1739
|
+
let mut synced = open(true);
|
|
1740
|
+
plain.write(b"x").expect("the write must land");
|
|
1741
|
+
let after_plain = syncs.load(Ordering::SeqCst);
|
|
1742
|
+
synced.write(b"x").expect("the write must land");
|
|
1743
|
+
synced.pwrite(b"x", 0).expect("the write must land");
|
|
1744
|
+
synced.write(b"").expect("the empty write must land");
|
|
1745
|
+
assert_eq!(
|
|
1746
|
+
(after_plain, syncs.load(Ordering::SeqCst)),
|
|
1747
|
+
(0, 2),
|
|
1748
|
+
"File::write and pwrite must ask a file opened to sync to sync once per write landing \
|
|
1749
|
+
bytes, as generic_write_sync does, and a file opened otherwise never"
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// Linux's answer in a container: `F_GETFL` reports `O_NONBLOCK` and
|
|
1754
|
+
// `O_SYNC` on a directory opened with them.
|
|
1755
|
+
// @behavior RT-210
|
|
1756
|
+
#[test]
|
|
1757
|
+
fn a_directory_opened_with_flags_reports_them() {
|
|
1758
|
+
let tmpfs: Arc<Tmpfs> = Arc::new([("dir/a", "x")].into_iter().collect());
|
|
1759
|
+
let mount = Mount::new(
|
|
1760
|
+
"/data",
|
|
1761
|
+
Filesystem::new(tmpfs).expect("the instance must stand"),
|
|
1762
|
+
Access::ReadWrite,
|
|
1763
|
+
)
|
|
1764
|
+
.expect("/data is a guest path");
|
|
1765
|
+
let how = OpenHow {
|
|
1766
|
+
read: true,
|
|
1767
|
+
nonblock: true,
|
|
1768
|
+
sync: true,
|
|
1769
|
+
..OpenHow::default()
|
|
1770
|
+
};
|
|
1771
|
+
let dir = filp_open(&Arc::default(), &Path::root(mount.mnt()), "dir", &how)
|
|
1772
|
+
.unwrap_or_else(|_| panic!("the directory must open"));
|
|
1773
|
+
assert_eq!(
|
|
1774
|
+
(dir.nonblock(), dir.sync()),
|
|
1775
|
+
(true, true),
|
|
1776
|
+
"File::nonblock and File::sync on a directory do_file_open opened with both must \
|
|
1777
|
+
report them, as F_GETFL does"
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
// @behavior RT-184
|
|
1782
|
+
#[test]
|
|
1783
|
+
fn a_directory_answers_as_a_directory_open_to_read_does() {
|
|
1784
|
+
let tmpfs: Arc<Tmpfs> = Arc::new([("a", "x")].into_iter().collect());
|
|
1785
|
+
let mount = Mount::new(
|
|
1786
|
+
"/data",
|
|
1787
|
+
Filesystem::new(tmpfs).expect("the instance must stand"),
|
|
1788
|
+
Access::ReadWrite,
|
|
1789
|
+
)
|
|
1790
|
+
.expect("/data is a guest path");
|
|
1791
|
+
let mut dir = File::root(mount.mnt(), &Arc::default())
|
|
1792
|
+
.unwrap_or_else(|_| panic!("the root must open"));
|
|
1793
|
+
let mut buf = [0; 2];
|
|
1794
|
+
assert_eq!(
|
|
1795
|
+
(
|
|
1796
|
+
dir.read(&mut buf),
|
|
1797
|
+
dir.pread(&mut buf, 0),
|
|
1798
|
+
dir.write(b"xy"),
|
|
1799
|
+
dir.pwrite(b"xy", 0),
|
|
1800
|
+
dir.truncate(0),
|
|
1801
|
+
dir.fallocate(0, 1),
|
|
1802
|
+
dir.fsync(false),
|
|
1803
|
+
dir.fadvise(0, 0),
|
|
1804
|
+
),
|
|
1805
|
+
(
|
|
1806
|
+
Err(FilesystemError::IsADirectory),
|
|
1807
|
+
Err(FilesystemError::IsADirectory),
|
|
1808
|
+
Err(FilesystemError::BadDescriptor),
|
|
1809
|
+
Err(FilesystemError::BadDescriptor),
|
|
1810
|
+
Err(FilesystemError::InvalidInput),
|
|
1811
|
+
Err(FilesystemError::BadDescriptor),
|
|
1812
|
+
Ok(()),
|
|
1813
|
+
Ok(()),
|
|
1814
|
+
),
|
|
1815
|
+
"File calls on a directory must answer as Linux answers one open to read: EISDIR to \
|
|
1816
|
+
read, EBADF to write or allocate, EINVAL to truncate, and success to sync or advise"
|
|
1817
|
+
);
|
|
1818
|
+
}
|
|
1819
|
+
|
|
1820
|
+
// A pipe's ends are streams, which clear the position on their own, so only
|
|
1821
|
+
// a pseudo file that is none shows what `alloc_file` itself gives.
|
|
1822
|
+
#[test]
|
|
1823
|
+
fn a_pseudo_file_reads_and_writes_at_no_position() {
|
|
1824
|
+
struct NoOperations;
|
|
1825
|
+
impl FileOperations for NoOperations {}
|
|
1826
|
+
let how = OpenHow {
|
|
1827
|
+
read: true,
|
|
1828
|
+
write: true,
|
|
1829
|
+
..OpenHow::default()
|
|
1830
|
+
};
|
|
1831
|
+
let inode = Inode::new(&Kstat::new(FileType::RegFile, 0), Arc::new(OpensNothing));
|
|
1832
|
+
let mnt = open(true, false).path().mnt().clone();
|
|
1833
|
+
let mut file =
|
|
1834
|
+
alloc_file_pseudo(inode, &mnt, &how, Box::new(NoOperations), &Arc::default());
|
|
1835
|
+
assert_eq!(
|
|
1836
|
+
(file.pread(&mut [0], 0), file.pwrite(b"x", 0)),
|
|
1837
|
+
(
|
|
1838
|
+
Err(FilesystemError::NotSeekable),
|
|
1839
|
+
Err(FilesystemError::NotSeekable)
|
|
1840
|
+
),
|
|
1841
|
+
"File::pread and File::pwrite on a file alloc_file_pseudo made must answer ESPIPE, \
|
|
1842
|
+
as file_init_path gives no FMODE_PREAD or FMODE_PWRITE"
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
|
|
1846
|
+
// @behavior RT-181
|
|
1847
|
+
#[test]
|
|
1848
|
+
fn an_allocation_refuses_in_linuxs_order() {
|
|
1849
|
+
struct NoOperations;
|
|
1850
|
+
impl FileOperations for NoOperations {}
|
|
1851
|
+
let (_, mut pipe) = create_pipe_files(&PipeInodeInfo::new(1), &Arc::default());
|
|
1852
|
+
let how = OpenHow {
|
|
1853
|
+
write: true,
|
|
1854
|
+
..OpenHow::default()
|
|
1855
|
+
};
|
|
1856
|
+
let path = open(false, true).path().clone();
|
|
1857
|
+
assert_eq!(
|
|
1858
|
+
(
|
|
1859
|
+
open(true, false).fallocate(0, 0),
|
|
1860
|
+
open(true, false).fallocate(0, 1),
|
|
1861
|
+
File::new(
|
|
1862
|
+
path,
|
|
1863
|
+
Box::new(NoOperations),
|
|
1864
|
+
FMode::of(&how),
|
|
1865
|
+
&how,
|
|
1866
|
+
&Arc::default(),
|
|
1867
|
+
Vec::new()
|
|
1868
|
+
)
|
|
1869
|
+
.fallocate(0, 1),
|
|
1870
|
+
pipe.fallocate(0, 1),
|
|
1871
|
+
),
|
|
1872
|
+
(
|
|
1873
|
+
Err(FilesystemError::InvalidInput),
|
|
1874
|
+
Err(FilesystemError::BadDescriptor),
|
|
1875
|
+
Err(FilesystemError::Unsupported),
|
|
1876
|
+
Err(FilesystemError::NotSeekable),
|
|
1877
|
+
),
|
|
1878
|
+
"File::fallocate must answer an empty range EINVAL, a file not open to write EBADF, \
|
|
1879
|
+
a file without fallocate ENOTSUP, and a pipe ESPIPE, as vfs_fallocate does"
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
// @behavior RT-180
|
|
1884
|
+
#[test]
|
|
1885
|
+
fn an_open_file_refuses_a_call_ending_past_the_largest_offset() {
|
|
1886
|
+
let mut buf = [0; 2];
|
|
1887
|
+
let mut file = open(true, true);
|
|
1888
|
+
file.llseek(OFFSET_MAX as i64 - 1, Whence::Set)
|
|
1889
|
+
.unwrap_or_else(|_| panic!("the largest offset must be one a file can stand at"));
|
|
1890
|
+
assert_eq!(
|
|
1891
|
+
(
|
|
1892
|
+
file.read(&mut buf),
|
|
1893
|
+
file.write(b"xy"),
|
|
1894
|
+
file.pread(&mut buf, OFFSET_MAX - 1),
|
|
1895
|
+
file.pwrite(b"xy", OFFSET_MAX - 1),
|
|
1896
|
+
file.pread(&mut buf, OFFSET_MAX + 1),
|
|
1897
|
+
file.pwrite(b"xy", OFFSET_MAX + 1),
|
|
1898
|
+
file.truncate(OFFSET_MAX + 1),
|
|
1899
|
+
file.pwrite(b"", OFFSET_MAX),
|
|
1900
|
+
),
|
|
1901
|
+
(
|
|
1902
|
+
Err(FilesystemError::InvalidInput),
|
|
1903
|
+
Err(FilesystemError::InvalidInput),
|
|
1904
|
+
Err(FilesystemError::InvalidInput),
|
|
1905
|
+
Err(FilesystemError::InvalidInput),
|
|
1906
|
+
Err(FilesystemError::InvalidInput),
|
|
1907
|
+
Err(FilesystemError::InvalidInput),
|
|
1908
|
+
Err(FilesystemError::InvalidInput),
|
|
1909
|
+
Ok(0),
|
|
1910
|
+
),
|
|
1911
|
+
"File calls ending past the largest signed offset must answer EINVAL, as \
|
|
1912
|
+
rw_verify_area does, and an empty one ending at it must pass"
|
|
1913
|
+
);
|
|
1914
|
+
}
|
|
1915
|
+
}
|