bcfs/
bcfs.rs

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    /// Creates a new fs for the account at `home_addr`.
27    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    /// Returns pre-opened dir fds. @see `crate::file::special_file_ctor`
35    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    /// Returns a capability to the resource at the given (relative!) `path`.
43    ///
44    /// `curdir` is the root capability to which the `path` is relative.
45    ///
46    /// The blockchain environment is exposed through files in `/opt/<chain_name>/`:
47    /// * `<address>/balance` - contains the read-only public balance of the account at `<address>`
48    /// * `<address>/bytecode` - contains the read-only Wasm bytecode of the account at `<address>`
49    /// * `log` - an append-only file to which events can be written. @see `BCFS::parse_log`.
50    ///
51    /// The user's home directory is `/opt/<chain_name>/<address>`.
52    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            // The virtual filesystem does not yet allow opening directories.
62            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                    // ^ This must be done eagerly to match POSIX which immediately creates the file.
85                }
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                    // find any aliased files and populate them with the flushed buffer
168                    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(); // deal with the error when the file is actually read
185                        *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    /// Removes the file at `path` and returns the number of bytes previously in the file.
207    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() // can't fail because usize is at least 32 bits
367}
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); // WASI paths must be relative to a pre-opened dir.
387        }
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); // TODO(#82)
467        }
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(), // TODO(#80)
524                    file_type: FileType::RegularFile,
525                    num_links: 0,
526                    file_size,
527                    atime: 0u64.into(), // TODO(#81)
528                    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    /// Parses a log buffer into (topics, data)
612    /// Format:
613    /// num_topics [topic_len [topic_data; topic_len]; num_topics] data_len [data; data_len]
614    /// num_* are little-endian 32-bit integers.
615    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}