@wasm-oj/server 0.2.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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +6 -0
  3. package/THIRD_PARTY_NOTICES.md +302 -0
  4. package/crates/runtime-core/Cargo.lock +5099 -0
  5. package/crates/runtime-core/Cargo.toml +66 -0
  6. package/crates/runtime-core/README.md +47 -0
  7. package/crates/runtime-core/src/bin/wasm-oj-compiler.rs +418 -0
  8. package/crates/runtime-core/src/bin/wasm-oj-runner.rs +294 -0
  9. package/crates/runtime-core/src/capabilities.rs +118 -0
  10. package/crates/runtime-core/src/compiler.rs +658 -0
  11. package/crates/runtime-core/src/contract.rs +5 -0
  12. package/crates/runtime-core/src/deterministic.rs +1051 -0
  13. package/crates/runtime-core/src/error.rs +58 -0
  14. package/crates/runtime-core/src/filesystem.rs +547 -0
  15. package/crates/runtime-core/src/filesystem_quota.rs +167 -0
  16. package/crates/runtime-core/src/go_compiler_session.rs +297 -0
  17. package/crates/runtime-core/src/interactive.rs +1019 -0
  18. package/crates/runtime-core/src/judge_package.rs +1539 -0
  19. package/crates/runtime-core/src/lib.rs +98 -0
  20. package/crates/runtime-core/src/memory.rs +84 -0
  21. package/crates/runtime-core/src/meter.rs +549 -0
  22. package/crates/runtime-core/src/module_imports.rs +149 -0
  23. package/crates/runtime-core/src/module_policy.rs +714 -0
  24. package/crates/runtime-core/src/output.rs +204 -0
  25. package/crates/runtime-core/src/run/mod.rs +208 -0
  26. package/crates/runtime-core/src/run/native.rs +260 -0
  27. package/crates/runtime-core/src/run/web.rs +229 -0
  28. package/crates/runtime-core/src/run/web_runtime.rs +109 -0
  29. package/crates/runtime-core/src/types.rs +268 -0
  30. package/crates/runtime-core/src/web.rs +83 -0
  31. package/dist/chunks/go-toolchain-Dbt-lp2L.js +426 -0
  32. package/dist/chunks/java-toolchain-DajoRCHu.js +44 -0
  33. package/dist/chunks/python-toolchain-Dx834o2A.js +4 -0
  34. package/dist/chunks/rust-toolchain-CJ3sMxPE.js +252 -0
  35. package/dist/chunks/toolchains-C6KuA1yM.js +224 -0
  36. package/dist/go-stage.mjs +193 -0
  37. package/dist/index.d.ts +221 -0
  38. package/dist/index.js +4393 -0
  39. package/dist/java-stage.mjs +111 -0
  40. package/dist/python-stage.mjs +90 -0
  41. package/dist/rustc-stage.mjs +301 -0
  42. package/dist/server-build-stage.mjs +2564 -0
  43. package/dist/server-runner-stage.mjs +155 -0
  44. package/licenses/fflate-MIT.txt +21 -0
  45. package/licenses/runtime-core-dependencies.html +6253 -0
  46. package/licenses/runtime-core-dependencies.json +3041 -0
  47. package/licenses/wasmer-sdk-MIT.txt +21 -0
  48. package/licenses/wasmer-sdk-dependencies.html +6901 -0
  49. package/licenses/wasmer-sdk-dependencies.json +3013 -0
  50. package/package.json +70 -0
  51. package/rust-toolchain.toml +5 -0
  52. package/testdata/wojjdg02-v2-text.hex +1 -0
  53. package/vendor/shared-buffer/Cargo.toml +22 -0
  54. package/vendor/shared-buffer/LICENSE_APACHE.md +176 -0
  55. package/vendor/shared-buffer/LICENSE_MIT.md +25 -0
  56. package/vendor/shared-buffer/README.md +34 -0
  57. package/vendor/shared-buffer/src/lib.rs +58 -0
  58. package/vendor/shared-buffer/src/mmap.rs +250 -0
  59. package/vendor/shared-buffer/src/owned.rs +389 -0
  60. package/vendor/virtual-fs/Cargo.toml +181 -0
  61. package/vendor/virtual-fs/LICENSE +25 -0
  62. package/vendor/virtual-fs/src/arc_box_file.rs +142 -0
  63. package/vendor/virtual-fs/src/arc_file.rs +182 -0
  64. package/vendor/virtual-fs/src/arc_fs.rs +68 -0
  65. package/vendor/virtual-fs/src/buffer_file.rs +103 -0
  66. package/vendor/virtual-fs/src/builder.rs +232 -0
  67. package/vendor/virtual-fs/src/combine_file.rs +101 -0
  68. package/vendor/virtual-fs/src/cow_file.rs +345 -0
  69. package/vendor/virtual-fs/src/dual_write_file.rs +113 -0
  70. package/vendor/virtual-fs/src/empty_fs.rs +81 -0
  71. package/vendor/virtual-fs/src/filesystems.rs +108 -0
  72. package/vendor/virtual-fs/src/host_fs.rs +1390 -0
  73. package/vendor/virtual-fs/src/lib.rs +782 -0
  74. package/vendor/virtual-fs/src/limiter.rs +252 -0
  75. package/vendor/virtual-fs/src/mem_fs/file.rs +1799 -0
  76. package/vendor/virtual-fs/src/mem_fs/file_opener.rs +941 -0
  77. package/vendor/virtual-fs/src/mem_fs/filesystem.rs +2134 -0
  78. package/vendor/virtual-fs/src/mem_fs/mod.rs +245 -0
  79. package/vendor/virtual-fs/src/mem_fs/offloaded_file.rs +474 -0
  80. package/vendor/virtual-fs/src/mem_fs/stdio.rs +318 -0
  81. package/vendor/virtual-fs/src/mount_fs.rs +2225 -0
  82. package/vendor/virtual-fs/src/null_file.rs +87 -0
  83. package/vendor/virtual-fs/src/ops.rs +364 -0
  84. package/vendor/virtual-fs/src/overlay_fs.rs +2216 -0
  85. package/vendor/virtual-fs/src/passthru_fs.rs +119 -0
  86. package/vendor/virtual-fs/src/pipe.rs +603 -0
  87. package/vendor/virtual-fs/src/random_file.rs +88 -0
  88. package/vendor/virtual-fs/src/special_file.rs +108 -0
  89. package/vendor/virtual-fs/src/static_file.rs +133 -0
  90. package/vendor/virtual-fs/src/static_fs.rs +460 -0
  91. package/vendor/virtual-fs/src/tmp_fs.rs +95 -0
  92. package/vendor/virtual-fs/src/trace_fs.rs +258 -0
  93. package/vendor/virtual-fs/src/webc_volume_fs.rs +829 -0
  94. package/vendor/virtual-fs/src/zero_file.rs +90 -0
@@ -0,0 +1,829 @@
1
+ use std::{
2
+ convert::{TryFrom, TryInto},
3
+ io::Cursor,
4
+ path::{Path, PathBuf},
5
+ pin::Pin,
6
+ result::Result,
7
+ task::Poll,
8
+ };
9
+
10
+ use futures::future::BoxFuture;
11
+ use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
12
+ use webc::{
13
+ Container, Metadata as WebcMetadata, PathSegmentError, PathSegments, ToPathSegments, Volume,
14
+ compat::SharedBytes,
15
+ };
16
+
17
+ use crate::{
18
+ DirEntry, EmptyFileSystem, FileOpener, FileSystem, FileType, FsError, Metadata,
19
+ OpenOptionsConfig, OverlayFileSystem, ReadDir, VirtualFile,
20
+ };
21
+
22
+ #[derive(Debug, Clone)]
23
+ pub struct WebcVolumeFileSystem {
24
+ volume: Volume,
25
+ }
26
+
27
+ impl WebcVolumeFileSystem {
28
+ pub fn new(volume: Volume) -> Self {
29
+ WebcVolumeFileSystem { volume }
30
+ }
31
+
32
+ pub fn volume(&self) -> &Volume {
33
+ &self.volume
34
+ }
35
+
36
+ /// Get a filesystem where all [`Volume`]s in a [`Container`] are mounted to
37
+ /// the root directory.
38
+ pub fn mount_all(
39
+ container: &Container,
40
+ ) -> OverlayFileSystem<EmptyFileSystem, Vec<WebcVolumeFileSystem>> {
41
+ let mut filesystems = Vec::new();
42
+
43
+ for volume in container.volumes().into_values() {
44
+ filesystems.push(WebcVolumeFileSystem::new(volume));
45
+ }
46
+
47
+ OverlayFileSystem::new(EmptyFileSystem::default(), filesystems)
48
+ }
49
+ }
50
+
51
+ impl FileSystem for WebcVolumeFileSystem {
52
+ fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
53
+ let path = normalize(path).map_err(|_| FsError::InvalidInput)?;
54
+
55
+ match self.volume().metadata(&path) {
56
+ Some(meta) if !meta.is_symlink() => Err(FsError::InvalidInput),
57
+ Some(_) => self
58
+ .volume()
59
+ .read_link(&path)
60
+ .map(|(target, _)| PathBuf::from(target))
61
+ .ok_or(FsError::EntryNotFound),
62
+ None => Err(FsError::EntryNotFound),
63
+ }
64
+ }
65
+
66
+ fn read_dir(&self, path: &Path) -> Result<crate::ReadDir, FsError> {
67
+ let meta = self.metadata(path)?;
68
+
69
+ if !meta.is_dir() {
70
+ return Err(FsError::BaseNotDirectory);
71
+ }
72
+
73
+ let path = normalize(path).map_err(|_| FsError::InvalidInput)?;
74
+
75
+ let mut entries = Vec::new();
76
+
77
+ for (name, _, meta) in self
78
+ .volume()
79
+ .read_dir(&path)
80
+ .ok_or(FsError::EntryNotFound)?
81
+ {
82
+ let path = PathBuf::from(path.join(name).to_string());
83
+ entries.push(DirEntry {
84
+ path,
85
+ metadata: Ok(compat_meta(meta)),
86
+ });
87
+ }
88
+
89
+ Ok(ReadDir::new(entries))
90
+ }
91
+
92
+ fn create_dir(&self, path: &Path) -> Result<(), FsError> {
93
+ // the directory shouldn't exist yet
94
+ if self.metadata(path).is_ok() {
95
+ return Err(FsError::AlreadyExists);
96
+ }
97
+
98
+ // it's parent should exist
99
+ let parent = path.parent().unwrap_or_else(|| Path::new("/"));
100
+
101
+ match self.metadata(parent) {
102
+ Ok(parent_meta) if parent_meta.is_dir() => {
103
+ // The operation would normally be doable... but we're a readonly
104
+ // filesystem
105
+ Err(FsError::PermissionDenied)
106
+ }
107
+ Ok(_) | Err(FsError::EntryNotFound) => Err(FsError::BaseNotDirectory),
108
+ Err(other) => Err(other),
109
+ }
110
+ }
111
+
112
+ fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
113
+ // The original directory should exist
114
+ let meta = self.metadata(path)?;
115
+
116
+ // and it should be a directory
117
+ if !meta.is_dir() {
118
+ return Err(FsError::BaseNotDirectory);
119
+ }
120
+
121
+ // but we are a readonly filesystem, so you can't modify anything
122
+ Err(FsError::PermissionDenied)
123
+ }
124
+
125
+ fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
126
+ Box::pin(async {
127
+ // The original file should exist
128
+ let _ = self.metadata(from)?;
129
+
130
+ // we also want to make sure the destination's folder exists, too
131
+ let dest_parent = to.parent().unwrap_or_else(|| Path::new("/"));
132
+ let parent_meta = self.metadata(dest_parent)?;
133
+ if !parent_meta.is_dir() {
134
+ return Err(FsError::BaseNotDirectory);
135
+ }
136
+
137
+ // but we are a readonly filesystem, so you can't modify anything
138
+ Err(FsError::PermissionDenied)
139
+ })
140
+ }
141
+
142
+ fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
143
+ let path = normalize(path).map_err(|_| FsError::InvalidInput)?;
144
+
145
+ self.volume()
146
+ .metadata(path)
147
+ .map(compat_meta)
148
+ .ok_or(FsError::EntryNotFound)
149
+ }
150
+
151
+ fn symlink_metadata(&self, path: &Path) -> crate::Result<Metadata> {
152
+ self.metadata(path)
153
+ }
154
+
155
+ fn remove_file(&self, path: &Path) -> Result<(), FsError> {
156
+ let meta = self.metadata(path)?;
157
+
158
+ if !meta.is_file() {
159
+ return Err(FsError::NotAFile);
160
+ }
161
+
162
+ Err(FsError::PermissionDenied)
163
+ }
164
+
165
+ fn new_open_options(&self) -> crate::OpenOptions<'_> {
166
+ crate::OpenOptions::new(self)
167
+ }
168
+ }
169
+
170
+ impl FileOpener for WebcVolumeFileSystem {
171
+ fn open(
172
+ &self,
173
+ path: &Path,
174
+ conf: &OpenOptionsConfig,
175
+ ) -> crate::Result<Box<dyn crate::VirtualFile + Send + Sync + 'static>> {
176
+ if let Some(parent) = path.parent() {
177
+ let parent_meta = self.metadata(parent)?;
178
+ if !parent_meta.is_dir() {
179
+ return Err(FsError::BaseNotDirectory);
180
+ }
181
+ }
182
+
183
+ let timestamps = match self.volume().metadata(path) {
184
+ Some(m) if m.is_file() => m.timestamps(),
185
+ Some(_) => return Err(FsError::NotAFile),
186
+ None if conf.create() || conf.create_new() => {
187
+ // The file would normally be created, but we are a readonly fs.
188
+ return Err(FsError::PermissionDenied);
189
+ }
190
+ None => return Err(FsError::EntryNotFound),
191
+ };
192
+
193
+ match self.volume().read_file(path) {
194
+ Some((bytes, _)) => Ok(Box::new(File {
195
+ timestamps,
196
+ content: Cursor::new(bytes),
197
+ })),
198
+ None => {
199
+ // The metadata() call should guarantee this, so something
200
+ // probably went wrong internally
201
+ Err(FsError::UnknownError)
202
+ }
203
+ }
204
+ }
205
+ }
206
+
207
+ #[derive(Debug, Clone, PartialEq)]
208
+ struct File {
209
+ timestamps: Option<webc::Timestamps>,
210
+ content: Cursor<SharedBytes>,
211
+ }
212
+
213
+ impl VirtualFile for File {
214
+ fn last_accessed(&self) -> u64 {
215
+ 0
216
+ }
217
+
218
+ fn last_modified(&self) -> u64 {
219
+ self.timestamps
220
+ .map(|t| t.modified())
221
+ .unwrap_or_else(|| get_modified(None))
222
+ }
223
+
224
+ fn created_time(&self) -> u64 {
225
+ 0
226
+ }
227
+
228
+ fn size(&self) -> u64 {
229
+ self.content.get_ref().len().try_into().unwrap()
230
+ }
231
+
232
+ fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
233
+ Err(FsError::PermissionDenied)
234
+ }
235
+
236
+ fn unlink(&mut self) -> crate::Result<()> {
237
+ Err(FsError::PermissionDenied)
238
+ }
239
+
240
+ fn poll_read_ready(
241
+ self: Pin<&mut Self>,
242
+ _cx: &mut std::task::Context<'_>,
243
+ ) -> Poll<std::io::Result<usize>> {
244
+ let bytes_remaining =
245
+ self.content.get_ref().len() - usize::try_from(self.content.position()).unwrap();
246
+ Poll::Ready(Ok(bytes_remaining))
247
+ }
248
+
249
+ fn poll_write_ready(
250
+ self: Pin<&mut Self>,
251
+ _cx: &mut std::task::Context<'_>,
252
+ ) -> Poll<std::io::Result<usize>> {
253
+ Poll::Ready(Err(std::io::ErrorKind::PermissionDenied.into()))
254
+ }
255
+
256
+ fn as_owned_buffer(&self) -> Option<SharedBytes> {
257
+ Some(self.content.get_ref().clone())
258
+ }
259
+ }
260
+
261
+ impl AsyncRead for File {
262
+ fn poll_read(
263
+ mut self: Pin<&mut Self>,
264
+ cx: &mut std::task::Context<'_>,
265
+ buf: &mut tokio::io::ReadBuf<'_>,
266
+ ) -> Poll<std::io::Result<()>> {
267
+ AsyncRead::poll_read(Pin::new(&mut self.content), cx, buf)
268
+ }
269
+ }
270
+
271
+ impl AsyncSeek for File {
272
+ fn start_seek(mut self: Pin<&mut Self>, position: std::io::SeekFrom) -> std::io::Result<()> {
273
+ AsyncSeek::start_seek(Pin::new(&mut self.content), position)
274
+ }
275
+
276
+ fn poll_complete(
277
+ mut self: Pin<&mut Self>,
278
+ cx: &mut std::task::Context<'_>,
279
+ ) -> Poll<std::io::Result<u64>> {
280
+ AsyncSeek::poll_complete(Pin::new(&mut self.content), cx)
281
+ }
282
+ }
283
+
284
+ impl AsyncWrite for File {
285
+ fn poll_write(
286
+ self: Pin<&mut Self>,
287
+ _cx: &mut std::task::Context<'_>,
288
+ _buf: &[u8],
289
+ ) -> Poll<Result<usize, std::io::Error>> {
290
+ Poll::Ready(Err(std::io::ErrorKind::PermissionDenied.into()))
291
+ }
292
+
293
+ fn poll_flush(
294
+ self: Pin<&mut Self>,
295
+ _cx: &mut std::task::Context<'_>,
296
+ ) -> Poll<Result<(), std::io::Error>> {
297
+ Poll::Ready(Err(std::io::ErrorKind::PermissionDenied.into()))
298
+ }
299
+
300
+ fn poll_shutdown(
301
+ self: Pin<&mut Self>,
302
+ _cx: &mut std::task::Context<'_>,
303
+ ) -> Poll<Result<(), std::io::Error>> {
304
+ Poll::Ready(Err(std::io::ErrorKind::PermissionDenied.into()))
305
+ }
306
+ }
307
+
308
+ // HACK: WebC v2 doesn't have timestamps, and WebC v3 files sometimes
309
+ // have directories with a zero timestamp as well. Since some programs
310
+ // interpret a zero timestamp as the absence of a value, we return
311
+ // 1 second past epoch instead.
312
+ fn get_modified(timestamps: Option<webc::Timestamps>) -> u64 {
313
+ let modified = timestamps.map(|t| t.modified()).unwrap_or_default();
314
+ // 1 billion nanoseconds = 1 second
315
+ modified.max(1_000_000_000)
316
+ }
317
+
318
+ fn compat_meta(meta: WebcMetadata) -> Metadata {
319
+ match meta {
320
+ WebcMetadata::Dir { timestamps } => Metadata {
321
+ ft: FileType {
322
+ dir: true,
323
+ ..Default::default()
324
+ },
325
+ modified: get_modified(timestamps),
326
+ ..Default::default()
327
+ },
328
+ WebcMetadata::File {
329
+ length, timestamps, ..
330
+ } => Metadata {
331
+ ft: FileType {
332
+ file: true,
333
+ ..Default::default()
334
+ },
335
+ len: length.try_into().unwrap(),
336
+ modified: get_modified(timestamps),
337
+ ..Default::default()
338
+ },
339
+ WebcMetadata::Symlink {
340
+ target_length,
341
+ timestamps,
342
+ } => Metadata {
343
+ ft: FileType {
344
+ symlink: true,
345
+ ..Default::default()
346
+ },
347
+ len: target_length.try_into().unwrap(),
348
+ modified: get_modified(timestamps),
349
+ ..Default::default()
350
+ },
351
+ }
352
+ }
353
+
354
+ /// Normalize a [`Path`] into a [`PathSegments`], dealing with things like `..`
355
+ /// and skipping `.`'s.
356
+ fn normalize(path: &Path) -> Result<PathSegments, PathSegmentError> {
357
+ // normalization is handled by the ToPathSegments impl for Path
358
+ let result = path.to_path_segments();
359
+
360
+ if let Err(e) = &result {
361
+ tracing::debug!(
362
+ error = e as &dyn std::error::Error,
363
+ path=%path.display(),
364
+ "Unable to normalize a path",
365
+ );
366
+ }
367
+
368
+ result
369
+ }
370
+
371
+ #[cfg(test)]
372
+ mod tests {
373
+ use super::*;
374
+ use crate::DirEntry;
375
+ use std::collections::BTreeMap;
376
+ use std::convert::TryFrom;
377
+ use tokio::io::AsyncReadExt;
378
+ use wasmer_package::utils::from_bytes;
379
+ use webc::PathSegment;
380
+
381
+ const PYTHON_WASM: &[u8] = b"\0asm";
382
+ const DS_STORE: &[u8] = &[0; 6148];
383
+
384
+ fn python_webc() -> Container {
385
+ let timestamps = webc::v3::Timestamps::default();
386
+ let empty_directory = || webc::v3::write::Directory::new(BTreeMap::new(), timestamps);
387
+ let lib = webc::v3::write::Directory::new(
388
+ BTreeMap::from_iter([
389
+ (
390
+ PathSegment::parse(".DS_Store").unwrap(),
391
+ webc::v3::write::DirEntry::File(webc::v3::write::FileEntry::borrowed(
392
+ DS_STORE, timestamps,
393
+ )),
394
+ ),
395
+ (
396
+ PathSegment::parse("Parser").unwrap(),
397
+ webc::v3::write::DirEntry::Dir(empty_directory()),
398
+ ),
399
+ (
400
+ PathSegment::parse("python.wasm").unwrap(),
401
+ webc::v3::write::DirEntry::File(webc::v3::write::FileEntry::borrowed(
402
+ PYTHON_WASM,
403
+ timestamps,
404
+ )),
405
+ ),
406
+ (
407
+ PathSegment::parse("python3.6").unwrap(),
408
+ webc::v3::write::DirEntry::Dir(empty_directory()),
409
+ ),
410
+ ]),
411
+ timestamps,
412
+ );
413
+ let root = webc::v3::write::Directory::new(
414
+ BTreeMap::from_iter([(
415
+ PathSegment::parse("lib").unwrap(),
416
+ webc::v3::write::DirEntry::Dir(lib),
417
+ )]),
418
+ timestamps,
419
+ );
420
+ let mut writer = webc::v3::write::Writer::new(webc::v3::ChecksumAlgorithm::Sha256)
421
+ .write_manifest(&webc::metadata::Manifest::default())
422
+ .unwrap()
423
+ .write_atoms(BTreeMap::new())
424
+ .unwrap();
425
+ writer.write_volume("atom", root).unwrap();
426
+ from_bytes(writer.finish(webc::v3::SignatureAlgorithm::None).unwrap()).unwrap()
427
+ }
428
+
429
+ fn symlink_fs() -> WebcVolumeFileSystem {
430
+ let timestamps = webc::v3::Timestamps::default();
431
+ let dir = webc::v3::write::Directory::new(
432
+ BTreeMap::from_iter([
433
+ (
434
+ PathSegment::parse("target.txt").unwrap(),
435
+ webc::v3::write::DirEntry::File(webc::v3::write::FileEntry::borrowed(
436
+ b"target", timestamps,
437
+ )),
438
+ ),
439
+ (
440
+ PathSegment::parse("link").unwrap(),
441
+ webc::v3::write::DirEntry::Symlink(webc::v3::write::SymlinkEntry::borrowed(
442
+ "target.txt",
443
+ timestamps,
444
+ )),
445
+ ),
446
+ ]),
447
+ timestamps,
448
+ );
449
+ let manifest = webc::metadata::Manifest::default();
450
+ let mut writer = webc::v3::write::Writer::new(webc::v3::ChecksumAlgorithm::Sha256)
451
+ .write_manifest(&manifest)
452
+ .unwrap()
453
+ .write_atoms(BTreeMap::new())
454
+ .unwrap();
455
+ writer.write_volume("atom", dir).unwrap();
456
+ let webc = writer.finish(webc::v3::SignatureAlgorithm::None).unwrap();
457
+ let container = from_bytes(webc).unwrap();
458
+ let volume = container.volumes()["atom"].clone();
459
+
460
+ WebcVolumeFileSystem::new(volume)
461
+ }
462
+
463
+ #[test]
464
+ fn normalize_paths() {
465
+ let inputs: Vec<(&str, &[&str])> = vec![
466
+ ("/", &[]),
467
+ ("/path/to/", &["path", "to"]),
468
+ ("/path/to/file.txt", &["path", "to", "file.txt"]),
469
+ ("/folder/..", &[]),
470
+ ("/.hidden", &[".hidden"]),
471
+ ("/folder/../../../../../../../file.txt", &["file.txt"]),
472
+ #[cfg(windows)]
473
+ (r"C:\path\to\file.txt", &["path", "to", "file.txt"]),
474
+ ];
475
+
476
+ for (path, expected) in inputs {
477
+ let normalized = normalize(path.as_ref()).unwrap();
478
+ assert_eq!(normalized, expected.to_path_segments().unwrap());
479
+ }
480
+ }
481
+
482
+ #[test]
483
+ #[cfg_attr(not(windows), ignore = "Only works with PathBuf's Windows logic")]
484
+ fn normalize_windows_paths() {
485
+ let inputs: Vec<(&str, &[&str])> = vec![
486
+ (r"C:\path\to\file.txt", &["path", "to", "file.txt"]),
487
+ (r"C:/path/to/file.txt", &["path", "to", "file.txt"]),
488
+ (r"\\system07\C$\", &[]),
489
+ (r"c:\temp\test-file.txt", &["temp", "test-file.txt"]),
490
+ (
491
+ r"\\127.0.0.1\c$\temp\test-file.txt",
492
+ &["temp", "test-file.txt"],
493
+ ),
494
+ (r"\\.\c:\temp\test-file.txt", &["temp", "test-file.txt"]),
495
+ (r"\\?\c:\temp\test-file.txt", &["temp", "test-file.txt"]),
496
+ (
497
+ r"\\127.0.0.1\c$\temp\test-file.txt",
498
+ &["temp", "test-file.txt"],
499
+ ),
500
+ (
501
+ r"\\.\Volume{b75e2c83-0000-0000-0000-602f00000000}\temp\test-file.txt",
502
+ &["temp", "test-file.txt"],
503
+ ),
504
+ ];
505
+
506
+ for (path, expected) in inputs {
507
+ let normalized = normalize(path.as_ref()).unwrap();
508
+ assert_eq!(normalized, expected.to_path_segments().unwrap(), "{}", path);
509
+ }
510
+ }
511
+
512
+ #[test]
513
+ fn invalid_paths() {
514
+ let paths = [".", "..", "./file.txt", ""];
515
+
516
+ for path in paths {
517
+ assert!(normalize(path.as_ref()).is_err(), "{}", path);
518
+ }
519
+ }
520
+
521
+ #[test]
522
+ fn symlink_metadata_and_readlink() {
523
+ let fs = symlink_fs();
524
+
525
+ let link = fs.symlink_metadata("/link".as_ref()).unwrap();
526
+ assert!(link.ft.is_symlink());
527
+ assert_eq!(link.len(), "target.txt".len() as u64);
528
+ assert_eq!(
529
+ fs.readlink("/link".as_ref()).unwrap(),
530
+ Path::new("target.txt")
531
+ );
532
+ assert_eq!(
533
+ fs.new_open_options().read(true).open("/link").unwrap_err(),
534
+ FsError::NotAFile,
535
+ );
536
+
537
+ let entries: Vec<_> = fs
538
+ .read_dir("/".as_ref())
539
+ .unwrap()
540
+ .map(|entry| entry.unwrap())
541
+ .collect();
542
+ let link_entry = entries
543
+ .iter()
544
+ .find(|entry| entry.path == Path::new("/link"))
545
+ .unwrap();
546
+ assert!(link_entry.metadata().unwrap().ft.is_symlink());
547
+
548
+ assert_eq!(
549
+ fs.readlink("/target.txt".as_ref()).unwrap_err(),
550
+ FsError::InvalidInput
551
+ );
552
+ assert_eq!(
553
+ fs.readlink("/missing".as_ref()).unwrap_err(),
554
+ FsError::EntryNotFound
555
+ );
556
+ }
557
+
558
+ #[test]
559
+ fn mount_all_volumes_in_python() {
560
+ let container = python_webc();
561
+
562
+ let fs = WebcVolumeFileSystem::mount_all(&container);
563
+
564
+ // We should now have access to the python directory
565
+ let lib_meta = fs.metadata("/lib/python3.6/".as_ref()).unwrap();
566
+ assert!(lib_meta.is_dir());
567
+ }
568
+
569
+ #[test]
570
+ fn read_dir() {
571
+ let container = python_webc();
572
+ let volumes = container.volumes();
573
+ let volume = volumes["atom"].clone();
574
+
575
+ let fs = WebcVolumeFileSystem::new(volume);
576
+
577
+ let entries: Vec<_> = fs
578
+ .read_dir("/lib".as_ref())
579
+ .unwrap()
580
+ .map(|r| r.unwrap())
581
+ .collect();
582
+
583
+ let modified = get_modified(None);
584
+ let expected = vec![
585
+ DirEntry {
586
+ path: "/lib/.DS_Store".into(),
587
+ metadata: Ok(Metadata {
588
+ ft: FileType {
589
+ file: true,
590
+ ..Default::default()
591
+ },
592
+ accessed: 0,
593
+ created: 0,
594
+ modified,
595
+ len: 6148,
596
+ }),
597
+ },
598
+ DirEntry {
599
+ path: "/lib/Parser".into(),
600
+ metadata: Ok(Metadata {
601
+ ft: FileType {
602
+ dir: true,
603
+ ..Default::default()
604
+ },
605
+ accessed: 0,
606
+ created: 0,
607
+ modified,
608
+ len: 0,
609
+ }),
610
+ },
611
+ DirEntry {
612
+ path: "/lib/python.wasm".into(),
613
+ metadata: Ok(crate::Metadata {
614
+ ft: crate::FileType {
615
+ file: true,
616
+ ..Default::default()
617
+ },
618
+ accessed: 0,
619
+ created: 0,
620
+ modified,
621
+ len: PYTHON_WASM.len() as u64,
622
+ }),
623
+ },
624
+ DirEntry {
625
+ path: "/lib/python3.6".into(),
626
+ metadata: Ok(crate::Metadata {
627
+ ft: crate::FileType {
628
+ dir: true,
629
+ ..Default::default()
630
+ },
631
+ accessed: 0,
632
+ created: 0,
633
+ modified,
634
+ len: 0,
635
+ }),
636
+ },
637
+ ];
638
+ assert_eq!(entries, expected);
639
+ }
640
+
641
+ #[test]
642
+ fn metadata() {
643
+ let container = python_webc();
644
+ let volumes = container.volumes();
645
+ let volume = volumes["atom"].clone();
646
+
647
+ let fs = WebcVolumeFileSystem::new(volume);
648
+
649
+ let modified = get_modified(None);
650
+ let python_wasm = crate::Metadata {
651
+ ft: crate::FileType {
652
+ file: true,
653
+ ..Default::default()
654
+ },
655
+ accessed: 0,
656
+ created: 0,
657
+ modified,
658
+ len: PYTHON_WASM.len() as u64,
659
+ };
660
+ assert_eq!(
661
+ fs.metadata("/lib/python.wasm".as_ref()).unwrap(),
662
+ python_wasm,
663
+ );
664
+ assert_eq!(
665
+ fs.metadata("/../../../../lib/python.wasm".as_ref())
666
+ .unwrap(),
667
+ python_wasm,
668
+ );
669
+ assert_eq!(
670
+ fs.metadata("/lib/python3.6/../python3.6/../python.wasm".as_ref())
671
+ .unwrap(),
672
+ python_wasm,
673
+ );
674
+ assert_eq!(
675
+ fs.metadata("/lib/python3.6".as_ref()).unwrap(),
676
+ crate::Metadata {
677
+ ft: crate::FileType {
678
+ dir: true,
679
+ ..Default::default()
680
+ },
681
+ accessed: 0,
682
+ created: 0,
683
+ modified,
684
+ len: 0,
685
+ },
686
+ );
687
+ assert_eq!(
688
+ fs.metadata("/this/does/not/exist".as_ref()).unwrap_err(),
689
+ FsError::EntryNotFound
690
+ );
691
+ }
692
+
693
+ #[tokio::test]
694
+ async fn file_opener() {
695
+ let container = python_webc();
696
+ let volumes = container.volumes();
697
+ let volume = volumes["atom"].clone();
698
+
699
+ let fs = WebcVolumeFileSystem::new(volume);
700
+
701
+ assert_eq!(
702
+ fs.new_open_options()
703
+ .create(true)
704
+ .write(true)
705
+ .open("/file.txt")
706
+ .unwrap_err(),
707
+ FsError::PermissionDenied,
708
+ );
709
+ assert_eq!(
710
+ fs.new_open_options().read(true).open("/lib").unwrap_err(),
711
+ FsError::NotAFile,
712
+ );
713
+ assert_eq!(
714
+ fs.new_open_options()
715
+ .read(true)
716
+ .open("/this/does/not/exist.txt")
717
+ .unwrap_err(),
718
+ FsError::EntryNotFound,
719
+ );
720
+
721
+ // We should be able to actually read the file
722
+ let mut f = fs
723
+ .new_open_options()
724
+ .read(true)
725
+ .open("/lib/python.wasm")
726
+ .unwrap();
727
+ let mut buffer = Vec::new();
728
+ f.read_to_end(&mut buffer).await.unwrap();
729
+ assert!(buffer.starts_with(b"\0asm"));
730
+ assert_eq!(
731
+ fs.metadata("/lib/python.wasm".as_ref()).unwrap().len(),
732
+ u64::try_from(buffer.len()).unwrap(),
733
+ );
734
+ }
735
+
736
+ #[test]
737
+ fn remove_dir_is_not_allowed() {
738
+ let container = python_webc();
739
+ let volumes = container.volumes();
740
+ let volume = volumes["atom"].clone();
741
+
742
+ let fs = WebcVolumeFileSystem::new(volume);
743
+
744
+ assert_eq!(
745
+ fs.remove_dir("/lib".as_ref()).unwrap_err(),
746
+ FsError::PermissionDenied,
747
+ );
748
+ assert_eq!(
749
+ fs.remove_dir("/this/does/not/exist".as_ref()).unwrap_err(),
750
+ FsError::EntryNotFound,
751
+ );
752
+ assert_eq!(
753
+ fs.remove_dir("/lib/python.wasm".as_ref()).unwrap_err(),
754
+ FsError::BaseNotDirectory,
755
+ );
756
+ }
757
+
758
+ #[test]
759
+ fn remove_file_is_not_allowed() {
760
+ let container = python_webc();
761
+ let volumes = container.volumes();
762
+ let volume = volumes["atom"].clone();
763
+
764
+ let fs = WebcVolumeFileSystem::new(volume);
765
+
766
+ assert_eq!(
767
+ fs.remove_file("/lib".as_ref()).unwrap_err(),
768
+ FsError::NotAFile,
769
+ );
770
+ assert_eq!(
771
+ fs.remove_file("/this/does/not/exist".as_ref()).unwrap_err(),
772
+ FsError::EntryNotFound,
773
+ );
774
+ assert_eq!(
775
+ fs.remove_file("/lib/python.wasm".as_ref()).unwrap_err(),
776
+ FsError::PermissionDenied,
777
+ );
778
+ }
779
+
780
+ #[test]
781
+ fn create_dir_is_not_allowed() {
782
+ let container = python_webc();
783
+ let volumes = container.volumes();
784
+ let volume = volumes["atom"].clone();
785
+
786
+ let fs = WebcVolumeFileSystem::new(volume);
787
+
788
+ assert_eq!(
789
+ fs.create_dir("/lib".as_ref()).unwrap_err(),
790
+ FsError::AlreadyExists,
791
+ );
792
+ assert_eq!(
793
+ fs.create_dir("/this/does/not/exist".as_ref()).unwrap_err(),
794
+ FsError::BaseNotDirectory,
795
+ );
796
+ assert_eq!(
797
+ fs.create_dir("/lib/nested/".as_ref()).unwrap_err(),
798
+ FsError::PermissionDenied,
799
+ );
800
+ }
801
+
802
+ #[tokio::test]
803
+ async fn rename_is_not_allowed() {
804
+ let container = python_webc();
805
+ let volumes = container.volumes();
806
+ let volume = volumes["atom"].clone();
807
+
808
+ let fs = WebcVolumeFileSystem::new(volume);
809
+
810
+ assert_eq!(
811
+ fs.rename("/lib".as_ref(), "/other".as_ref())
812
+ .await
813
+ .unwrap_err(),
814
+ FsError::PermissionDenied,
815
+ );
816
+ assert_eq!(
817
+ fs.rename("/this/does/not/exist".as_ref(), "/another".as_ref())
818
+ .await
819
+ .unwrap_err(),
820
+ FsError::EntryNotFound,
821
+ );
822
+ assert_eq!(
823
+ fs.rename("/lib/python.wasm".as_ref(), "/lib/another.wasm".as_ref())
824
+ .await
825
+ .unwrap_err(),
826
+ FsError::PermissionDenied,
827
+ );
828
+ }
829
+ }