1use std::{
2 cell::{Cell, RefCell},
3 convert::TryFrom as _,
4 io::{Cursor, IoSlice, IoSliceMut, Read as _, Seek as _, SeekFrom, Write as _},
5 path::{Path, PathBuf},
6 str::FromStr as _,
7};
8
9use blockchain_traits::PendingTransaction;
10use oasis_types::Address;
11use wasi_types::{
12 ErrNo, Fd, FdFlags, FdStat, FileDelta, FileSize, FileStat, FileType, OpenFlags, Rights, Whence,
13};
14
15use crate::{
16 file::{File, FileCache, FileKind, CHAIN_DIR_FILENO, HOME_DIR_FILENO},
17 Result,
18};
19
20pub struct BCFS {
21 files: Vec<Option<File>>,
22 home_addr: Address,
23}
24
25impl BCFS {
26 pub fn new<S: AsRef<str>>(home_addr: Address, blockchain_name: S) -> Self {
28 Self {
29 files: File::defaults(blockchain_name.as_ref()),
30 home_addr,
31 }
32 }
33
34 pub fn prestat(&mut self, _ptx: &mut dyn PendingTransaction, fd: Fd) -> Result<&Path> {
36 match &self.file(fd)?.kind {
37 FileKind::Directory { path } => Ok(path),
38 _ => Err(ErrNo::BadF),
39 }
40 }
41
42 pub fn open(
53 &mut self,
54 ptx: &mut dyn PendingTransaction,
55 curdir: Fd,
56 path: &Path,
57 open_flags: OpenFlags,
58 fd_flags: FdFlags,
59 ) -> Result<Fd> {
60 if open_flags.contains(OpenFlags::DIRECTORY) {
61 return Err(ErrNo::NotSup);
63 }
64
65 match &self.file(curdir)?.kind {
66 FileKind::Directory { .. } => (),
67 _ => return Err(ErrNo::BadF),
68 };
69
70 let mut file_exists = true;
71 let file_kind = match self.canonicalize_path(curdir, path)? {
72 (None, path) if path == Path::new("log") => FileKind::Log,
73 (Some(addr), path) if path == Path::new("balance") => FileKind::Balance { addr },
74 (Some(addr), path) if path == Path::new("bytecode") => FileKind::Bytecode { addr },
75 (Some(addr), path) if addr == self.home_addr => {
76 let key = Self::key_for_path(&path)?;
77 file_exists = ptx.state().contains(&key);
78 if file_exists && open_flags.contains(OpenFlags::EXCL) {
79 return Err(ErrNo::Exist);
80 } else if !file_exists && !open_flags.contains(OpenFlags::CREATE) {
81 return Err(ErrNo::NoEnt);
82 } else if !file_exists {
83 ptx.state_mut().set(&key, &[]);
84 }
86 FileKind::Regular { key }
87 }
88 _ => return Err(ErrNo::NoEnt),
89 };
90
91 if file_kind.is_blockchain_intrinsic() {
92 if open_flags.intersects(OpenFlags::CREATE | OpenFlags::EXCL) {
93 return Err(ErrNo::Exist);
94 }
95 if open_flags.intersects(OpenFlags::TRUNC | OpenFlags::DIRECTORY)
96 || (file_kind.is_log() && !fd_flags.contains(FdFlags::APPEND))
97 {
98 return Err(ErrNo::Inval);
99 }
100 }
101
102 let fd = self.alloc_fd()?;
103 let (buf, dirty) = if !file_exists || open_flags.contains(OpenFlags::TRUNC) {
104 (FileCache::Present(Cursor::new(Vec::new())), true)
105 } else {
106 (
107 FileCache::Absent(if fd_flags.contains(FdFlags::APPEND) {
108 SeekFrom::End(0)
109 } else {
110 SeekFrom::Start(0)
111 }),
112 false,
113 )
114 };
115 self.files.push(Some(File {
116 kind: file_kind,
117 flags: fd_flags,
118 metadata: Cell::new(if file_exists {
119 None
120 } else {
121 Some(Self::default_filestat())
122 }),
123 buf: RefCell::new(buf),
124 dirty: Cell::new(dirty),
125 }));
126 Ok(fd)
127 }
128
129 pub fn tempfile(&mut self, _ptx: &mut dyn PendingTransaction) -> Result<Fd> {
130 let fd = self.alloc_fd()?;
131 self.files.push(Some(File {
132 kind: FileKind::Temporary,
133 flags: FdFlags::empty(),
134 metadata: Cell::new(Some(Self::default_filestat())),
135 buf: RefCell::new(FileCache::Present(Cursor::new(Vec::new()))),
136 dirty: Cell::new(false),
137 }));
138 Ok(fd)
139 }
140
141 pub fn flush(&mut self, ptx: &mut dyn PendingTransaction, fd: Fd) -> Result<()> {
142 let file = self.file(fd)?;
143 if !file.dirty.get() {
144 return Ok(());
145 }
146 let maybe_cursor = file.buf.borrow();
147 let buf = match &*maybe_cursor {
148 FileCache::Present(cursor) => cursor.get_ref(),
149 FileCache::Absent(_) => return Ok(()),
150 };
151 match &file.kind {
152 FileKind::Stdin
153 | FileKind::Bytecode { .. }
154 | FileKind::Balance { .. }
155 | FileKind::Directory { .. }
156 | FileKind::Temporary => (),
157 FileKind::Stdout => ptx.ret(buf),
158 FileKind::Stderr => ptx.err(buf),
159 FileKind::Log => {
160 if let Some((topics, data)) = Self::parse_log(buf) {
161 ptx.emit(&topics, data);
162 }
163 }
164 FileKind::Regular { key } => {
165 ptx.state_mut().set(&key, &buf);
166 for f in self.files[(HOME_DIR_FILENO as usize + 1)..].iter() {
167 if let Some(File {
169 kind: FileKind::Regular { key: f_key },
170 ..
171 }) = f
172 {
173 let f = f.as_ref().unwrap();
174 if key != f_key || f as *const File == file as *const File {
175 continue;
176 }
177 let mut f_buf = f.buf.borrow_mut();
178 let mut cursor = Cursor::new(buf.clone());
179 cursor
180 .seek(match &*f_buf {
181 FileCache::Absent(seek_from) => *seek_from,
182 FileCache::Present(cursor) => SeekFrom::Start(cursor.position()),
183 })
184 .ok(); *f_buf = FileCache::Present(cursor);
186 f.metadata.replace(None);
187 }
188 }
189 file.dirty.set(false);
190 }
191 }
192 Ok(())
193 }
194
195 pub fn close(&mut self, ptx: &mut dyn PendingTransaction, fd: Fd) -> Result<()> {
196 self.flush(ptx, fd)?;
197 match self.files.get_mut(fd_usize(fd)) {
198 Some(f) if f.is_some() => {
199 *f = None;
200 Ok(())
201 }
202 _ => Err(ErrNo::BadF),
203 }
204 }
205
206 pub fn unlink(
208 &mut self,
209 ptx: &mut dyn PendingTransaction,
210 curdir: Fd,
211 path: &Path,
212 ) -> Result<u64> {
213 let curdir_fileno = u32::from(curdir);
214 if curdir_fileno != HOME_DIR_FILENO {
215 return Err(ErrNo::Access);
216 }
217
218 let (addr, path) = self.canonicalize_path(curdir, path)?;
219 match addr {
220 Some(addr) if addr == self.home_addr => (),
221 _ => return Err(ErrNo::Access),
222 }
223
224 if path == Path::new("balance") || path == Path::new("bytecode") {
225 return Err(ErrNo::Access);
226 }
227
228 let key = Self::key_for_path(&path)?;
229 let state = ptx.state_mut();
230 let prev_len = state.get(&key).unwrap_or_default().len() as u64;
231 state.remove(&key);
232 Ok(prev_len)
233 }
234
235 pub fn seek(
236 &mut self,
237 ptx: &mut dyn PendingTransaction,
238 fd: Fd,
239 offset: FileDelta,
240 whence: Whence,
241 ) -> Result<FileSize> {
242 let file = self.file_mut(fd)?;
243
244 let mut buf = file.buf.borrow_mut();
245
246 if Whence::End == whence
247 || match &*buf {
248 FileCache::Absent(SeekFrom::End(_)) => true,
249 _ => false,
250 }
251 {
252 Self::populate_file(ptx, &file, &mut *buf)?;
253 }
254
255 match &mut *buf {
256 FileCache::Present(ref mut cursor) => {
257 Ok(cursor.seek(seekfrom_from_offset_whence(offset, whence)?)?)
258 }
259 FileCache::Absent(ref mut seek) => match whence {
260 Whence::End => unreachable!("file was just populated"),
261 Whence::Start => {
262 *seek = seekfrom_from_offset_whence(offset, whence)?;
263 Ok(offset as u64)
264 }
265 Whence::Current => match seek {
266 SeekFrom::Start(cur_offset) => {
267 let new_offset = Self::checked_offset(*cur_offset, offset)?;
268 *seek = SeekFrom::Start(new_offset);
269 Ok(new_offset as u64)
270 }
271 _ => unreachable!("handled above"),
272 },
273 },
274 }
275 }
276
277 pub fn fdstat(&self, _ptx: &mut dyn PendingTransaction, fd: Fd) -> Result<FdStat> {
278 let file = self.file(fd)?;
279 Ok(FdStat {
280 file_type: match file.kind {
281 FileKind::Directory { .. } => FileType::Directory,
282 _ => FileType::RegularFile,
283 },
284 flags: file.flags,
285 rights_base: Rights::all(),
286 rights_inheriting: Rights::all(),
287 })
288 }
289
290 pub fn filestat(&self, ptx: &dyn PendingTransaction, fd: Fd) -> Result<FileStat> {
291 let file = self.file(fd)?;
292 Self::populate_file(ptx, file, &mut *file.buf.borrow_mut())
293 }
294
295 pub fn tell(&self, ptx: &mut dyn PendingTransaction, fd: Fd) -> Result<FileSize> {
296 let file = self.file(fd)?;
297 let mut buf = file.buf.borrow_mut();
298 if let FileCache::Absent(SeekFrom::End(_)) = &*buf {
299 Self::populate_file(ptx, &file, &mut *buf)?;
300 }
301 Ok(match &mut *buf {
302 FileCache::Present(cursor) => cursor.position(),
303 FileCache::Absent(ref mut seekfrom) => match seekfrom {
304 SeekFrom::Start(offset) => *offset,
305 SeekFrom::End(_) => unreachable!("checked above"),
306 SeekFrom::Current(_) => unreachable!(),
307 },
308 })
309 }
310
311 pub fn read_vectored(
312 &mut self,
313 ptx: &mut dyn PendingTransaction,
314 fd: Fd,
315 bufs: &mut [IoSliceMut],
316 ) -> Result<usize> {
317 self.do_pread_vectored(ptx, fd, bufs, None)
318 }
319
320 pub fn pread_vectored(
321 &self,
322 ptx: &mut dyn PendingTransaction,
323 fd: Fd,
324 bufs: &mut [IoSliceMut],
325 offset: FileSize,
326 ) -> Result<usize> {
327 self.do_pread_vectored(ptx, fd, bufs, Some(SeekFrom::Start(offset)))
328 }
329
330 pub fn write_vectored(
331 &mut self,
332 ptx: &mut dyn PendingTransaction,
333 fd: Fd,
334 bufs: &[IoSlice],
335 ) -> Result<usize> {
336 self.do_pwrite_vectored(ptx, fd, bufs, None)
337 }
338
339 pub fn pwrite_vectored(
340 &mut self,
341 ptx: &mut dyn PendingTransaction,
342 fd: Fd,
343 bufs: &[IoSlice],
344 offset: FileSize,
345 ) -> Result<usize> {
346 self.do_pwrite_vectored(ptx, fd, bufs, Some(SeekFrom::Start(offset)))
347 }
348
349 pub fn renumber(
350 &mut self,
351 _ptx: &mut dyn PendingTransaction,
352 fd: Fd,
353 new_fd: Fd,
354 ) -> Result<()> {
355 if self.has_fd(fd) && self.has_fd(new_fd) {
356 self.files.swap(fd_usize(fd), fd_usize(new_fd));
357 self.files[fd_usize(fd)] = None;
358 Ok(())
359 } else {
360 Err(ErrNo::BadF)
361 }
362 }
363}
364
365fn fd_usize(fd: Fd) -> usize {
366 usize::try_from(u32::from(fd)).unwrap() }
368
369fn seekfrom_from_offset_whence(offset: FileDelta, whence: Whence) -> Result<SeekFrom> {
370 Ok(match whence {
371 Whence::Current => SeekFrom::Current(offset),
372 Whence::Start => SeekFrom::Start(if offset < 0 {
373 return Err(ErrNo::Inval);
374 } else {
375 offset as u64
376 }),
377 Whence::End => SeekFrom::End(offset),
378 })
379}
380
381impl BCFS {
382 fn canonicalize_path(&self, curdir: Fd, path: &Path) -> Result<(Option<Address>, PathBuf)> {
383 use std::path::Component;
384
385 if path.has_root() {
386 return Err(ErrNo::NoEnt); }
388
389 let curdir_fileno = u32::from(curdir);
390
391 let mut canon_path = PathBuf::new();
392
393 let mut comps = path
394 .components()
395 .skip_while(|comp| *comp == Component::CurDir)
396 .peekable();
397
398 let addr = if curdir_fileno == CHAIN_DIR_FILENO {
399 match comps.peek() {
400 Some(Component::Normal(maybe_addr)) => {
401 match maybe_addr.to_str().map(Address::from_str) {
402 Some(Ok(addr)) => {
403 comps.next();
404 Some(addr)
405 }
406 _ => None,
407 }
408 }
409 Some(Component::Prefix(_)) | Some(Component::RootDir) => return Err(ErrNo::NoEnt),
410 _ => None,
411 }
412 } else {
413 Some(self.home_addr)
414 };
415
416 let mut has_path = false;
417 for comp in comps {
418 match comp {
419 Component::Prefix(_) | Component::RootDir => return Err(ErrNo::NoEnt),
420 Component::CurDir => (),
421 Component::ParentDir => {
422 if !canon_path.pop() {
423 return Err(ErrNo::NoEnt);
424 }
425 }
426 Component::Normal(c) => {
427 has_path |= !c.is_empty();
428 canon_path.push(c);
429 }
430 }
431 }
432
433 if has_path {
434 Ok((addr, canon_path))
435 } else {
436 Err(ErrNo::Inval)
437 }
438 }
439
440 fn has_fd(&self, fd: Fd) -> bool {
441 match self.files.get(fd_usize(fd)) {
442 Some(Some(_)) => true,
443 _ => false,
444 }
445 }
446
447 fn file(&self, fd: Fd) -> Result<&File> {
448 match self.files.get(fd_usize(fd)) {
449 Some(Some(file)) => Ok(file),
450 _ => Err(ErrNo::BadF),
451 }
452 }
453
454 fn file_mut(&mut self, fd: Fd) -> Result<&mut File> {
455 match self
456 .files
457 .get_mut(usize::try_from(u64::from(fd)).map_err(|_| ErrNo::BadF)?)
458 {
459 Some(Some(file)) => Ok(file),
460 _ => Err(ErrNo::BadF),
461 }
462 }
463
464 fn alloc_fd(&self) -> Result<Fd> {
465 if self.files.len() >= u32::max_value() as usize {
466 return Err(ErrNo::NFile); }
468 Ok((self.files.len() as u32).into())
469 }
470
471 fn checked_offset(base: u64, offset: i64) -> Result<u64> {
472 if offset >= 0 {
473 base.checked_add(offset as u64)
474 } else {
475 base.checked_sub(-offset as u64)
476 }
477 .ok_or(ErrNo::Inval)
478 }
479
480 fn key_for_path(path: &Path) -> Result<Vec<u8>> {
481 path.to_str()
482 .ok_or(ErrNo::Inval)
483 .map(|s| s.as_bytes().to_vec())
484 }
485
486 fn populate_file(
487 ptx: &dyn PendingTransaction,
488 file: &File,
489 cache: &mut FileCache,
490 ) -> Result<FileStat> {
491 let file_size = match cache {
492 FileCache::Present(cursor) => cursor.get_ref().len(),
493 FileCache::Absent(offset) => {
494 let bytes = match &file.kind {
495 FileKind::Stdin => ptx.input().to_vec(),
496 FileKind::Bytecode { addr } => match ptx.code_at(addr) {
497 Some(code) => code.to_vec(),
498 None => return Err(ErrNo::NoEnt),
499 },
500 FileKind::Balance { addr } => match ptx.account_meta_at(addr) {
501 Some(meta) => meta.balance.to_le_bytes().to_vec(),
502 None => return Err(ErrNo::NoEnt),
503 },
504 FileKind::Regular { key } => match ptx.state().get(&key) {
505 Some(val) => val.to_vec(),
506 None => return Err(ErrNo::NoEnt),
507 },
508 FileKind::Stdout | FileKind::Stderr | FileKind::Log => Vec::new(),
509 FileKind::Directory { .. } | FileKind::Temporary => return Err(ErrNo::Fault),
510 };
511 let file_size = bytes.len();
512 let mut cursor = Cursor::new(bytes);
513 cursor.seek(*offset)?;
514 *cache = FileCache::Present(cursor);
515 file_size
516 }
517 } as u64;
518 match file.metadata.get() {
519 Some(meta) => Ok(meta),
520 None => {
521 let meta = FileStat {
522 device: 0u64.into(),
523 inode: 0u64.into(), file_type: FileType::RegularFile,
525 num_links: 0,
526 file_size,
527 atime: 0u64.into(), mtime: 0u64.into(),
529 ctime: 0u64.into(),
530 };
531 file.metadata.set(Some(meta));
532 Ok(meta)
533 }
534 }
535 }
536
537 fn do_pread_vectored(
538 &self,
539 ptx: &mut dyn PendingTransaction,
540 fd: Fd,
541 bufs: &mut [IoSliceMut],
542 offset: Option<SeekFrom>,
543 ) -> Result<usize> {
544 let file = self.file(fd)?;
545 match file.kind {
546 FileKind::Stdout | FileKind::Stderr { .. } | FileKind::Log { .. } => {
547 return Err(ErrNo::Inval)
548 }
549 _ => (),
550 };
551
552 let mut buf = file.buf.borrow_mut();
553 Self::populate_file(ptx, &file, &mut *buf)?;
554
555 let cursor = match &mut *buf {
556 FileCache::Present(ref mut cursor) => cursor,
557 FileCache::Absent(_) => unreachable!("file was just populated"),
558 };
559
560 match offset {
561 Some(offset) => {
562 let orig_pos = cursor.position();
563 cursor.seek(offset)?;
564 let nbytes = cursor.read_vectored(bufs)?;
565 cursor.set_position(orig_pos);
566 Ok(nbytes)
567 }
568 None => Ok(cursor.read_vectored(bufs)?),
569 }
570 }
571
572 fn do_pwrite_vectored(
573 &mut self,
574 ptx: &mut dyn PendingTransaction,
575 fd: Fd,
576 bufs: &[IoSlice],
577 offset: Option<SeekFrom>,
578 ) -> Result<usize> {
579 let file = self.file(fd)?;
580 match file.kind {
581 FileKind::Stdin | FileKind::Bytecode { .. } | FileKind::Balance { .. } => {
582 return Err(ErrNo::Inval)
583 }
584 _ => (),
585 };
586
587 let mut buf = file.buf.borrow_mut();
588 Self::populate_file(ptx, &file, &mut *buf)?;
589
590 let cursor = match &mut *buf {
591 FileCache::Present(ref mut cursor) => cursor,
592 FileCache::Absent(_) => unreachable!("file was just populated"),
593 };
594
595 let nbytes = match offset {
596 Some(offset) => {
597 let orig_pos = cursor.position();
598 cursor.seek(offset)?;
599 let nbytes = cursor.write_vectored(bufs)?;
600 cursor.set_position(orig_pos);
601 nbytes
602 }
603 None => cursor.write_vectored(bufs)?,
604 };
605 if nbytes > 0 {
606 file.dirty.replace(true);
607 }
608 Ok(nbytes)
609 }
610
611 fn parse_log(buf: &[u8]) -> Option<(Vec<&[u8]>, &[u8])> {
616 use nom::{complete, do_parse, length_count, length_data, named, number::complete::le_u32};
617 named! {
618 parser<(Vec<&[u8]>, &[u8])>,
619 complete!(do_parse!(
620 topics: length_count!(le_u32, length_data!(le_u32)) >>
621 data: length_data!(le_u32) >>
622 (topics, data)
623 ))
624 };
625 parser(buf).map(|result| result.1).ok()
626 }
627
628 fn default_filestat() -> FileStat {
629 FileStat {
630 device: 0u64.into(),
631 inode: 0u32.into(),
632 file_type: FileType::RegularFile,
633 num_links: 0,
634 file_size: 0,
635 atime: 0u64.into(),
636 mtime: 0u64.into(),
637 ctime: 0u64.into(),
638 }
639 }
640}
641
642#[cfg(test)]
643mod tests {
644 use super::*;
645
646 #[test]
647 fn test_parse_log() {
648 let (topics, data, log) = crate::tests::create_log();
649
650 let (parsed_topics, parsed_data) = BCFS::parse_log(&log).unwrap();
651 assert_eq!(
652 parsed_topics,
653 topics.iter().map(|t| t.as_slice()).collect::<Vec<_>>()
654 );
655 assert_eq!(parsed_data, data.as_slice());
656 }
657
658 quickcheck::quickcheck! {
659 fn parse_log_nopanic(inp: Vec<u8>) -> () {
660 BCFS::parse_log(&inp);
661 }
662 }
663}