2023-05-10 20:06:59 -04:00
// Copyright 2018-2023 the Deno authors. All rights reserved. MIT license.
use std ::borrow ::Cow ;
use std ::collections ::HashMap ;
use std ::collections ::HashSet ;
use std ::fs ::File ;
use std ::io ::Read ;
use std ::io ::Seek ;
use std ::io ::SeekFrom ;
use std ::path ::Path ;
use std ::path ::PathBuf ;
use std ::rc ::Rc ;
use std ::sync ::Arc ;
use deno_core ::anyhow ::Context ;
use deno_core ::error ::AnyError ;
use deno_core ::parking_lot ::Mutex ;
use deno_core ::BufMutView ;
use deno_core ::BufView ;
2023-08-01 14:48:39 -04:00
use deno_core ::ResourceHandleFd ;
2023-05-10 20:06:59 -04:00
use deno_runtime ::deno_fs ::FsDirEntry ;
use deno_runtime ::deno_io ;
use deno_runtime ::deno_io ::fs ::FsError ;
use deno_runtime ::deno_io ::fs ::FsResult ;
use deno_runtime ::deno_io ::fs ::FsStat ;
use serde ::Deserialize ;
use serde ::Serialize ;
2023-05-27 10:33:15 -04:00
use thiserror ::Error ;
2023-05-10 20:06:59 -04:00
use crate ::util ;
2023-05-28 00:03:49 -04:00
use crate ::util ::fs ::canonicalize_path ;
2023-05-10 20:06:59 -04:00
2023-05-27 10:33:15 -04:00
#[ derive(Error, Debug) ]
#[ error(
" Failed to strip prefix '{}' from '{}' " , root_path . display ( ) , target . display ( )
) ]
pub struct StripRootError {
root_path : PathBuf ,
target : PathBuf ,
}
2023-05-10 20:06:59 -04:00
pub struct VfsBuilder {
root_path : PathBuf ,
root_dir : VirtualDirectory ,
files : Vec < Vec < u8 > > ,
current_offset : u64 ,
file_offsets : HashMap < String , u64 > ,
}
impl VfsBuilder {
2023-05-28 00:03:49 -04:00
pub fn new ( root_path : PathBuf ) -> Result < Self , AnyError > {
let root_path = canonicalize_path ( & root_path ) ? ;
2023-05-27 10:33:15 -04:00
log ::debug! ( " Building vfs with root '{}' " , root_path . display ( ) ) ;
2023-05-28 00:03:49 -04:00
Ok ( Self {
2023-05-10 20:06:59 -04:00
root_dir : VirtualDirectory {
name : root_path
. file_stem ( )
. unwrap ( )
. to_string_lossy ( )
. into_owned ( ) ,
entries : Vec ::new ( ) ,
} ,
root_path ,
files : Vec ::new ( ) ,
current_offset : 0 ,
file_offsets : Default ::default ( ) ,
2023-05-28 00:03:49 -04:00
} )
2023-05-10 20:06:59 -04:00
}
pub fn set_root_dir_name ( & mut self , name : String ) {
self . root_dir . name = name ;
}
pub fn add_dir_recursive ( & mut self , path : & Path ) -> Result < ( ) , AnyError > {
2023-05-28 00:03:49 -04:00
let path = canonicalize_path ( path ) ? ;
self . add_dir_recursive_internal ( & path )
}
fn add_dir_recursive_internal (
& mut self ,
path : & Path ,
) -> Result < ( ) , AnyError > {
2023-05-27 10:33:15 -04:00
self . add_dir ( path ) ? ;
2023-05-10 20:06:59 -04:00
let read_dir = std ::fs ::read_dir ( path )
. with_context ( | | format! ( " Reading {} " , path . display ( ) ) ) ? ;
for entry in read_dir {
let entry = entry ? ;
let file_type = entry . file_type ( ) ? ;
let path = entry . path ( ) ;
if file_type . is_dir ( ) {
2023-05-28 00:03:49 -04:00
self . add_dir_recursive_internal ( & path ) ? ;
2023-05-10 20:06:59 -04:00
} else if file_type . is_file ( ) {
2023-11-29 09:32:23 -05:00
self . add_file_at_path ( & path ) ? ;
2023-05-10 20:06:59 -04:00
} else if file_type . is_symlink ( ) {
2023-05-27 10:33:15 -04:00
let target = util ::fs ::canonicalize_path ( & path )
2023-05-10 20:06:59 -04:00
. with_context ( | | format! ( " Reading symlink {} " , path . display ( ) ) ) ? ;
2023-05-27 10:33:15 -04:00
if let Err ( StripRootError { .. } ) = self . add_symlink ( & path , & target ) {
if target . is_file ( ) {
// this may change behavior, so warn the user about it
log ::warn! (
" Symlink target is outside '{}'. Inlining symlink at '{}' to '{}' as file. " ,
self . root_path . display ( ) ,
path . display ( ) ,
target . display ( ) ,
) ;
// inline the symlink and make the target file
let file_bytes = std ::fs ::read ( & target )
. with_context ( | | format! ( " Reading {} " , path . display ( ) ) ) ? ;
self . add_file ( & path , file_bytes ) ? ;
} else {
log ::warn! (
" Symlink target is outside '{}'. Excluding symlink at '{}' with target '{}'. " ,
self . root_path . display ( ) ,
path . display ( ) ,
target . display ( ) ,
) ;
}
}
2023-05-10 20:06:59 -04:00
}
}
Ok ( ( ) )
}
2023-05-28 00:03:49 -04:00
fn add_dir (
2023-05-27 10:33:15 -04:00
& mut self ,
path : & Path ,
) -> Result < & mut VirtualDirectory , StripRootError > {
log ::debug! ( " Ensuring directory '{}' " , path . display ( ) ) ;
let path = self . path_relative_root ( path ) ? ;
2023-05-10 20:06:59 -04:00
let mut current_dir = & mut self . root_dir ;
for component in path . components ( ) {
let name = component . as_os_str ( ) . to_string_lossy ( ) ;
let index = match current_dir
. entries
. binary_search_by ( | e | e . name ( ) . cmp ( & name ) )
{
Ok ( index ) = > index ,
Err ( insert_index ) = > {
current_dir . entries . insert (
insert_index ,
VfsEntry ::Dir ( VirtualDirectory {
name : name . to_string ( ) ,
entries : Vec ::new ( ) ,
} ) ,
) ;
insert_index
}
} ;
match & mut current_dir . entries [ index ] {
VfsEntry ::Dir ( dir ) = > {
current_dir = dir ;
}
_ = > unreachable! ( ) ,
} ;
}
2023-05-27 10:33:15 -04:00
Ok ( current_dir )
2023-05-10 20:06:59 -04:00
}
2023-11-29 09:32:23 -05:00
pub fn add_file_at_path ( & mut self , path : & Path ) -> Result < ( ) , AnyError > {
let file_bytes = std ::fs ::read ( path )
. with_context ( | | format! ( " Reading {} " , path . display ( ) ) ) ? ;
self . add_file ( path , file_bytes )
}
2023-05-28 00:03:49 -04:00
fn add_file ( & mut self , path : & Path , data : Vec < u8 > ) -> Result < ( ) , AnyError > {
2023-05-27 10:33:15 -04:00
log ::debug! ( " Adding file '{}' " , path . display ( ) ) ;
2023-05-10 20:06:59 -04:00
let checksum = util ::checksum ::gen ( & [ & data ] ) ;
let offset = if let Some ( offset ) = self . file_offsets . get ( & checksum ) {
// duplicate file, reuse an old offset
* offset
} else {
self . file_offsets . insert ( checksum , self . current_offset ) ;
self . current_offset
} ;
2023-05-27 10:33:15 -04:00
let dir = self . add_dir ( path . parent ( ) . unwrap ( ) ) ? ;
2023-05-10 20:06:59 -04:00
let name = path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) ;
let data_len = data . len ( ) ;
match dir . entries . binary_search_by ( | e | e . name ( ) . cmp ( & name ) ) {
Ok ( _ ) = > unreachable! ( ) ,
Err ( insert_index ) = > {
dir . entries . insert (
insert_index ,
VfsEntry ::File ( VirtualFile {
name : name . to_string ( ) ,
offset ,
len : data . len ( ) as u64 ,
} ) ,
) ;
}
}
// new file, update the list of files
if self . current_offset = = offset {
self . files . push ( data ) ;
self . current_offset + = data_len as u64 ;
}
2023-05-27 10:33:15 -04:00
Ok ( ( ) )
2023-05-10 20:06:59 -04:00
}
2023-05-28 00:03:49 -04:00
fn add_symlink (
2023-05-27 10:33:15 -04:00
& mut self ,
path : & Path ,
target : & Path ,
) -> Result < ( ) , StripRootError > {
log ::debug! (
" Adding symlink '{}' to '{}' " ,
path . display ( ) ,
target . display ( )
) ;
let dest = self . path_relative_root ( target ) ? ;
let dir = self . add_dir ( path . parent ( ) . unwrap ( ) ) ? ;
2023-05-10 20:06:59 -04:00
let name = path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) ;
match dir . entries . binary_search_by ( | e | e . name ( ) . cmp ( & name ) ) {
Ok ( _ ) = > unreachable! ( ) ,
Err ( insert_index ) = > {
dir . entries . insert (
insert_index ,
VfsEntry ::Symlink ( VirtualSymlink {
name : name . to_string ( ) ,
dest_parts : dest
. components ( )
. map ( | c | c . as_os_str ( ) . to_string_lossy ( ) . to_string ( ) )
. collect ::< Vec < _ > > ( ) ,
} ) ,
) ;
}
}
2023-05-27 10:33:15 -04:00
Ok ( ( ) )
2023-05-10 20:06:59 -04:00
}
pub fn into_dir_and_files ( self ) -> ( VirtualDirectory , Vec < Vec < u8 > > ) {
( self . root_dir , self . files )
}
2023-05-25 15:29:58 -04:00
2023-05-27 10:33:15 -04:00
fn path_relative_root ( & self , path : & Path ) -> Result < PathBuf , StripRootError > {
2023-05-25 15:29:58 -04:00
match path . strip_prefix ( & self . root_path ) {
2023-05-27 10:33:15 -04:00
Ok ( p ) = > Ok ( p . to_path_buf ( ) ) ,
Err ( _ ) = > Err ( StripRootError {
root_path : self . root_path . clone ( ) ,
target : path . to_path_buf ( ) ,
} ) ,
2023-05-25 15:29:58 -04:00
}
}
2023-05-10 20:06:59 -04:00
}
#[ derive(Debug) ]
enum VfsEntryRef < ' a > {
Dir ( & ' a VirtualDirectory ) ,
File ( & ' a VirtualFile ) ,
Symlink ( & ' a VirtualSymlink ) ,
}
impl < ' a > VfsEntryRef < ' a > {
pub fn as_fs_stat ( & self ) -> FsStat {
match self {
VfsEntryRef ::Dir ( _ ) = > FsStat {
is_directory : true ,
is_file : false ,
is_symlink : false ,
atime : None ,
birthtime : None ,
mtime : None ,
blksize : 0 ,
size : 0 ,
dev : 0 ,
ino : 0 ,
mode : 0 ,
nlink : 0 ,
uid : 0 ,
gid : 0 ,
rdev : 0 ,
blocks : 0 ,
2023-05-24 15:18:13 -04:00
is_block_device : false ,
is_char_device : false ,
is_fifo : false ,
is_socket : false ,
2023-05-10 20:06:59 -04:00
} ,
VfsEntryRef ::File ( file ) = > FsStat {
is_directory : false ,
is_file : true ,
is_symlink : false ,
atime : None ,
birthtime : None ,
mtime : None ,
blksize : 0 ,
size : file . len ,
dev : 0 ,
ino : 0 ,
mode : 0 ,
nlink : 0 ,
uid : 0 ,
gid : 0 ,
rdev : 0 ,
blocks : 0 ,
2023-05-24 15:18:13 -04:00
is_block_device : false ,
is_char_device : false ,
is_fifo : false ,
is_socket : false ,
2023-05-10 20:06:59 -04:00
} ,
VfsEntryRef ::Symlink ( _ ) = > FsStat {
is_directory : false ,
is_file : false ,
is_symlink : true ,
atime : None ,
birthtime : None ,
mtime : None ,
blksize : 0 ,
size : 0 ,
dev : 0 ,
ino : 0 ,
mode : 0 ,
nlink : 0 ,
uid : 0 ,
gid : 0 ,
rdev : 0 ,
blocks : 0 ,
2023-05-24 15:18:13 -04:00
is_block_device : false ,
is_char_device : false ,
is_fifo : false ,
is_socket : false ,
2023-05-10 20:06:59 -04:00
} ,
}
}
}
// todo(dsherret): we should store this more efficiently in the binary
#[ derive(Debug, Serialize, Deserialize) ]
pub enum VfsEntry {
Dir ( VirtualDirectory ) ,
File ( VirtualFile ) ,
Symlink ( VirtualSymlink ) ,
}
impl VfsEntry {
pub fn name ( & self ) -> & str {
match self {
VfsEntry ::Dir ( dir ) = > & dir . name ,
VfsEntry ::File ( file ) = > & file . name ,
VfsEntry ::Symlink ( symlink ) = > & symlink . name ,
}
}
fn as_ref ( & self ) -> VfsEntryRef {
match self {
VfsEntry ::Dir ( dir ) = > VfsEntryRef ::Dir ( dir ) ,
VfsEntry ::File ( file ) = > VfsEntryRef ::File ( file ) ,
VfsEntry ::Symlink ( symlink ) = > VfsEntryRef ::Symlink ( symlink ) ,
}
}
}
#[ derive(Debug, Serialize, Deserialize) ]
pub struct VirtualDirectory {
pub name : String ,
// should be sorted by name
pub entries : Vec < VfsEntry > ,
}
#[ derive(Debug, Clone, Serialize, Deserialize) ]
pub struct VirtualFile {
pub name : String ,
pub offset : u64 ,
pub len : u64 ,
}
#[ derive(Debug, Serialize, Deserialize) ]
pub struct VirtualSymlink {
pub name : String ,
pub dest_parts : Vec < String > ,
}
impl VirtualSymlink {
pub fn resolve_dest_from_root ( & self , root : & Path ) -> PathBuf {
let mut dest = root . to_path_buf ( ) ;
for part in & self . dest_parts {
dest . push ( part ) ;
}
dest
}
}
#[ derive(Debug) ]
pub struct VfsRoot {
pub dir : VirtualDirectory ,
pub root_path : PathBuf ,
pub start_file_offset : u64 ,
}
impl VfsRoot {
fn find_entry < ' a > (
& ' a self ,
path : & Path ,
) -> std ::io ::Result < ( PathBuf , VfsEntryRef < ' a > ) > {
self . find_entry_inner ( path , & mut HashSet ::new ( ) )
}
fn find_entry_inner < ' a > (
& ' a self ,
path : & Path ,
seen : & mut HashSet < PathBuf > ,
) -> std ::io ::Result < ( PathBuf , VfsEntryRef < ' a > ) > {
let mut path = Cow ::Borrowed ( path ) ;
loop {
let ( resolved_path , entry ) =
self . find_entry_no_follow_inner ( & path , seen ) ? ;
match entry {
VfsEntryRef ::Symlink ( symlink ) = > {
if ! seen . insert ( path . to_path_buf ( ) ) {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::Other ,
" circular symlinks " ,
) ) ;
}
path = Cow ::Owned ( symlink . resolve_dest_from_root ( & self . root_path ) ) ;
}
_ = > {
return Ok ( ( resolved_path , entry ) ) ;
}
}
}
}
fn find_entry_no_follow (
& self ,
path : & Path ,
) -> std ::io ::Result < ( PathBuf , VfsEntryRef ) > {
self . find_entry_no_follow_inner ( path , & mut HashSet ::new ( ) )
}
fn find_entry_no_follow_inner < ' a > (
& ' a self ,
path : & Path ,
seen : & mut HashSet < PathBuf > ,
) -> std ::io ::Result < ( PathBuf , VfsEntryRef < ' a > ) > {
let relative_path = match path . strip_prefix ( & self . root_path ) {
Ok ( p ) = > p ,
Err ( _ ) = > {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
" path not found " ,
) ) ;
}
} ;
let mut final_path = self . root_path . clone ( ) ;
let mut current_entry = VfsEntryRef ::Dir ( & self . dir ) ;
for component in relative_path . components ( ) {
let component = component . as_os_str ( ) . to_string_lossy ( ) ;
let current_dir = match current_entry {
VfsEntryRef ::Dir ( dir ) = > {
final_path . push ( component . as_ref ( ) ) ;
dir
}
VfsEntryRef ::Symlink ( symlink ) = > {
let dest = symlink . resolve_dest_from_root ( & self . root_path ) ;
let ( resolved_path , entry ) = self . find_entry_inner ( & dest , seen ) ? ;
final_path = resolved_path ; // overwrite with the new resolved path
match entry {
VfsEntryRef ::Dir ( dir ) = > {
final_path . push ( component . as_ref ( ) ) ;
dir
}
_ = > {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
" path not found " ,
) ) ;
}
}
}
_ = > {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
" path not found " ,
) ) ;
}
} ;
match current_dir
. entries
. binary_search_by ( | e | e . name ( ) . cmp ( & component ) )
{
Ok ( index ) = > {
current_entry = current_dir . entries [ index ] . as_ref ( ) ;
}
Err ( _ ) = > {
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::NotFound ,
" path not found " ,
) ) ;
}
}
}
Ok ( ( final_path , current_entry ) )
}
}
#[ derive(Clone) ]
struct FileBackedVfsFile {
file : VirtualFile ,
pos : Arc < Mutex < u64 > > ,
vfs : Arc < FileBackedVfs > ,
}
impl FileBackedVfsFile {
fn seek ( & self , pos : SeekFrom ) -> FsResult < u64 > {
match pos {
SeekFrom ::Start ( pos ) = > {
* self . pos . lock ( ) = pos ;
Ok ( pos )
}
SeekFrom ::End ( offset ) = > {
if offset < 0 & & - offset as u64 > self . file . len {
2023-05-27 10:33:15 -04:00
let msg = " An attempt was made to move the file pointer before the beginning of the file. " ;
Err (
std ::io ::Error ::new ( std ::io ::ErrorKind ::PermissionDenied , msg )
. into ( ) ,
)
2023-05-10 20:06:59 -04:00
} else {
let mut current_pos = self . pos . lock ( ) ;
* current_pos = if offset > = 0 {
self . file . len - ( offset as u64 )
} else {
self . file . len + ( - offset as u64 )
} ;
Ok ( * current_pos )
}
}
SeekFrom ::Current ( offset ) = > {
let mut current_pos = self . pos . lock ( ) ;
if offset > = 0 {
* current_pos + = offset as u64 ;
} else if - offset as u64 > * current_pos {
return Err ( std ::io ::Error ::new ( std ::io ::ErrorKind ::PermissionDenied , " An attempt was made to move the file pointer before the beginning of the file. " ) . into ( ) ) ;
} else {
* current_pos - = - offset as u64 ;
}
Ok ( * current_pos )
}
}
}
fn read_to_buf ( & self , buf : & mut [ u8 ] ) -> FsResult < usize > {
let pos = {
let mut pos = self . pos . lock ( ) ;
let read_pos = * pos ;
// advance the position due to the read
* pos = std ::cmp ::min ( self . file . len , * pos + buf . len ( ) as u64 ) ;
read_pos
} ;
self
. vfs
. read_file ( & self . file , pos , buf )
. map_err ( | err | err . into ( ) )
}
fn read_to_end ( & self ) -> FsResult < Vec < u8 > > {
let pos = {
let mut pos = self . pos . lock ( ) ;
let read_pos = * pos ;
// todo(dsherret): should this always set it to the end of the file?
if * pos < self . file . len {
// advance the position due to the read
* pos = self . file . len ;
}
read_pos
} ;
if pos > self . file . len {
return Ok ( Vec ::new ( ) ) ;
}
let size = ( self . file . len - pos ) as usize ;
let mut buf = vec! [ 0 ; size ] ;
self . vfs . read_file ( & self . file , pos , & mut buf ) ? ;
Ok ( buf )
}
}
#[ async_trait::async_trait(?Send) ]
impl deno_io ::fs ::File for FileBackedVfsFile {
fn read_sync ( self : Rc < Self > , buf : & mut [ u8 ] ) -> FsResult < usize > {
self . read_to_buf ( buf )
}
async fn read_byob (
self : Rc < Self > ,
mut buf : BufMutView ,
) -> FsResult < ( usize , BufMutView ) > {
let inner = ( * self ) . clone ( ) ;
tokio ::task ::spawn ( async move {
let nread = inner . read_to_buf ( & mut buf ) ? ;
Ok ( ( nread , buf ) )
} )
. await ?
}
fn write_sync ( self : Rc < Self > , _buf : & [ u8 ] ) -> FsResult < usize > {
Err ( FsError ::NotSupported )
}
async fn write (
self : Rc < Self > ,
_buf : BufView ,
) -> FsResult < deno_core ::WriteOutcome > {
Err ( FsError ::NotSupported )
}
fn write_all_sync ( self : Rc < Self > , _buf : & [ u8 ] ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn write_all ( self : Rc < Self > , _buf : BufView ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn read_all_sync ( self : Rc < Self > ) -> FsResult < Vec < u8 > > {
self . read_to_end ( )
}
async fn read_all_async ( self : Rc < Self > ) -> FsResult < Vec < u8 > > {
let inner = ( * self ) . clone ( ) ;
tokio ::task ::spawn_blocking ( move | | inner . read_to_end ( ) ) . await ?
}
fn chmod_sync ( self : Rc < Self > , _pathmode : u32 ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn chmod_async ( self : Rc < Self > , _mode : u32 ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn seek_sync ( self : Rc < Self > , pos : SeekFrom ) -> FsResult < u64 > {
self . seek ( pos )
}
async fn seek_async ( self : Rc < Self > , pos : SeekFrom ) -> FsResult < u64 > {
self . seek ( pos )
}
fn datasync_sync ( self : Rc < Self > ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn datasync_async ( self : Rc < Self > ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn sync_sync ( self : Rc < Self > ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn sync_async ( self : Rc < Self > ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn stat_sync ( self : Rc < Self > ) -> FsResult < FsStat > {
Err ( FsError ::NotSupported )
}
async fn stat_async ( self : Rc < Self > ) -> FsResult < FsStat > {
Err ( FsError ::NotSupported )
}
fn lock_sync ( self : Rc < Self > , _exclusive : bool ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn lock_async ( self : Rc < Self > , _exclusive : bool ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn unlock_sync ( self : Rc < Self > ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn unlock_async ( self : Rc < Self > ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn truncate_sync ( self : Rc < Self > , _len : u64 ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn truncate_async ( self : Rc < Self > , _len : u64 ) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
fn utime_sync (
self : Rc < Self > ,
_atime_secs : i64 ,
_atime_nanos : u32 ,
_mtime_secs : i64 ,
_mtime_nanos : u32 ,
) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
async fn utime_async (
self : Rc < Self > ,
_atime_secs : i64 ,
_atime_nanos : u32 ,
_mtime_secs : i64 ,
_mtime_nanos : u32 ,
) -> FsResult < ( ) > {
Err ( FsError ::NotSupported )
}
// lower level functionality
fn as_stdio ( self : Rc < Self > ) -> FsResult < std ::process ::Stdio > {
Err ( FsError ::NotSupported )
}
2023-08-01 14:48:39 -04:00
fn backing_fd ( self : Rc < Self > ) -> Option < ResourceHandleFd > {
2023-05-10 20:06:59 -04:00
None
}
fn try_clone_inner ( self : Rc < Self > ) -> FsResult < Rc < dyn deno_io ::fs ::File > > {
Ok ( self )
}
}
#[ derive(Debug) ]
pub struct FileBackedVfs {
file : Mutex < File > ,
fs_root : VfsRoot ,
}
impl FileBackedVfs {
pub fn new ( file : File , fs_root : VfsRoot ) -> Self {
Self {
file : Mutex ::new ( file ) ,
fs_root ,
}
}
pub fn root ( & self ) -> & Path {
& self . fs_root . root_path
}
pub fn is_path_within ( & self , path : & Path ) -> bool {
path . starts_with ( & self . fs_root . root_path )
}
pub fn open_file (
self : & Arc < Self > ,
path : & Path ,
) -> std ::io ::Result < Rc < dyn deno_io ::fs ::File > > {
let file = self . file_entry ( path ) ? ;
Ok ( Rc ::new ( FileBackedVfsFile {
file : file . clone ( ) ,
vfs : self . clone ( ) ,
pos : Default ::default ( ) ,
} ) )
}
pub fn read_dir ( & self , path : & Path ) -> std ::io ::Result < Vec < FsDirEntry > > {
let dir = self . dir_entry ( path ) ? ;
Ok (
dir
. entries
. iter ( )
. map ( | entry | FsDirEntry {
name : entry . name ( ) . to_string ( ) ,
is_file : matches ! ( entry , VfsEntry ::File ( _ ) ) ,
is_directory : matches ! ( entry , VfsEntry ::Dir ( _ ) ) ,
is_symlink : matches ! ( entry , VfsEntry ::Symlink ( _ ) ) ,
} )
. collect ( ) ,
)
}
pub fn read_link ( & self , path : & Path ) -> std ::io ::Result < PathBuf > {
let ( _ , entry ) = self . fs_root . find_entry_no_follow ( path ) ? ;
match entry {
VfsEntryRef ::Symlink ( symlink ) = > {
Ok ( symlink . resolve_dest_from_root ( & self . fs_root . root_path ) )
}
VfsEntryRef ::Dir ( _ ) | VfsEntryRef ::File ( _ ) = > Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::Other ,
" not a symlink " ,
) ) ,
}
}
pub fn lstat ( & self , path : & Path ) -> std ::io ::Result < FsStat > {
let ( _ , entry ) = self . fs_root . find_entry_no_follow ( path ) ? ;
Ok ( entry . as_fs_stat ( ) )
}
pub fn stat ( & self , path : & Path ) -> std ::io ::Result < FsStat > {
let ( _ , entry ) = self . fs_root . find_entry ( path ) ? ;
Ok ( entry . as_fs_stat ( ) )
}
pub fn canonicalize ( & self , path : & Path ) -> std ::io ::Result < PathBuf > {
let ( path , _ ) = self . fs_root . find_entry ( path ) ? ;
Ok ( path )
}
pub fn read_file_all ( & self , file : & VirtualFile ) -> std ::io ::Result < Vec < u8 > > {
let mut buf = vec! [ 0 ; file . len as usize ] ;
self . read_file ( file , 0 , & mut buf ) ? ;
Ok ( buf )
}
pub fn read_file (
& self ,
file : & VirtualFile ,
pos : u64 ,
buf : & mut [ u8 ] ,
) -> std ::io ::Result < usize > {
let mut fs_file = self . file . lock ( ) ;
fs_file . seek ( SeekFrom ::Start (
self . fs_root . start_file_offset + file . offset + pos ,
) ) ? ;
fs_file . read ( buf )
}
pub fn dir_entry ( & self , path : & Path ) -> std ::io ::Result < & VirtualDirectory > {
let ( _ , entry ) = self . fs_root . find_entry ( path ) ? ;
match entry {
VfsEntryRef ::Dir ( dir ) = > Ok ( dir ) ,
VfsEntryRef ::Symlink ( _ ) = > unreachable! ( ) ,
VfsEntryRef ::File ( _ ) = > Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::Other ,
" path is a file " ,
) ) ,
}
}
pub fn file_entry ( & self , path : & Path ) -> std ::io ::Result < & VirtualFile > {
let ( _ , entry ) = self . fs_root . find_entry ( path ) ? ;
match entry {
VfsEntryRef ::Dir ( _ ) = > Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::Other ,
" path is a directory " ,
) ) ,
VfsEntryRef ::Symlink ( _ ) = > unreachable! ( ) ,
VfsEntryRef ::File ( file ) = > Ok ( file ) ,
}
}
}
#[ cfg(test) ]
mod test {
use std ::io ::Write ;
use test_util ::TempDir ;
use super ::* ;
2023-05-28 00:03:49 -04:00
#[ track_caller ]
2023-05-10 20:06:59 -04:00
fn read_file ( vfs : & FileBackedVfs , path : & Path ) -> String {
let file = vfs . file_entry ( path ) . unwrap ( ) ;
String ::from_utf8 ( vfs . read_file_all ( file ) . unwrap ( ) ) . unwrap ( )
}
#[ test ]
fn builds_and_uses_virtual_fs ( ) {
let temp_dir = TempDir ::new ( ) ;
2023-05-28 09:55:30 -04:00
// we canonicalize the temp directory because the vfs builder
// will canonicalize the root path
2023-06-10 11:09:45 -04:00
let src_path = temp_dir . path ( ) . canonicalize ( ) . join ( " src " ) ;
src_path . create_dir_all ( ) ;
let src_path = src_path . to_path_buf ( ) ;
2023-05-28 00:03:49 -04:00
let mut builder = VfsBuilder ::new ( src_path . clone ( ) ) . unwrap ( ) ;
2023-05-27 10:33:15 -04:00
builder
. add_file ( & src_path . join ( " a.txt " ) , " data " . into ( ) )
. unwrap ( ) ;
builder
. add_file ( & src_path . join ( " b.txt " ) , " data " . into ( ) )
. unwrap ( ) ;
2023-05-10 20:06:59 -04:00
assert_eq! ( builder . files . len ( ) , 1 ) ; // because duplicate data
2023-05-27 10:33:15 -04:00
builder
. add_file ( & src_path . join ( " c.txt " ) , " c " . into ( ) )
. unwrap ( ) ;
builder
. add_file ( & src_path . join ( " sub_dir " ) . join ( " d.txt " ) , " d " . into ( ) )
. unwrap ( ) ;
builder
. add_file ( & src_path . join ( " e.txt " ) , " e " . into ( ) )
. unwrap ( ) ;
builder
. add_symlink (
& src_path . join ( " sub_dir " ) . join ( " e.txt " ) ,
& src_path . join ( " e.txt " ) ,
)
. unwrap ( ) ;
2023-05-10 20:06:59 -04:00
// get the virtual fs
let ( dest_path , virtual_fs ) = into_virtual_fs ( builder , & temp_dir ) ;
assert_eq! ( read_file ( & virtual_fs , & dest_path . join ( " a.txt " ) ) , " data " ) ;
assert_eq! ( read_file ( & virtual_fs , & dest_path . join ( " b.txt " ) ) , " data " ) ;
// attempt reading a symlink
assert_eq! (
read_file ( & virtual_fs , & dest_path . join ( " sub_dir " ) . join ( " e.txt " ) ) ,
" e " ,
) ;
// canonicalize symlink
assert_eq! (
virtual_fs
. canonicalize ( & dest_path . join ( " sub_dir " ) . join ( " e.txt " ) )
. unwrap ( ) ,
dest_path . join ( " e.txt " ) ,
) ;
// metadata
assert! (
virtual_fs
. lstat ( & dest_path . join ( " sub_dir " ) . join ( " e.txt " ) )
. unwrap ( )
. is_symlink
) ;
assert! (
virtual_fs
. stat ( & dest_path . join ( " sub_dir " ) . join ( " e.txt " ) )
. unwrap ( )
. is_file
) ;
assert! (
virtual_fs
. stat ( & dest_path . join ( " sub_dir " ) )
. unwrap ( )
. is_directory ,
) ;
assert! ( virtual_fs . stat ( & dest_path . join ( " e.txt " ) ) . unwrap ( ) . is_file , ) ;
}
#[ test ]
fn test_include_dir_recursive ( ) {
let temp_dir = TempDir ::new ( ) ;
2023-06-10 11:09:45 -04:00
let temp_dir_path = temp_dir . path ( ) . canonicalize ( ) ;
2023-05-10 20:06:59 -04:00
temp_dir . create_dir_all ( " src/nested/sub_dir " ) ;
temp_dir . write ( " src/a.txt " , " data " ) ;
temp_dir . write ( " src/b.txt " , " data " ) ;
util ::fs ::symlink_dir (
2023-06-10 11:09:45 -04:00
temp_dir_path . join ( " src/nested/sub_dir " ) . as_path ( ) ,
temp_dir_path . join ( " src/sub_dir_link " ) . as_path ( ) ,
2023-05-10 20:06:59 -04:00
)
. unwrap ( ) ;
temp_dir . write ( " src/nested/sub_dir/c.txt " , " c " ) ;
// build and create the virtual fs
2023-06-10 11:09:45 -04:00
let src_path = temp_dir_path . join ( " src " ) . to_path_buf ( ) ;
2023-05-28 00:03:49 -04:00
let mut builder = VfsBuilder ::new ( src_path . clone ( ) ) . unwrap ( ) ;
2023-05-10 20:06:59 -04:00
builder . add_dir_recursive ( & src_path ) . unwrap ( ) ;
let ( dest_path , virtual_fs ) = into_virtual_fs ( builder , & temp_dir ) ;
assert_eq! ( read_file ( & virtual_fs , & dest_path . join ( " a.txt " ) ) , " data " , ) ;
assert_eq! ( read_file ( & virtual_fs , & dest_path . join ( " b.txt " ) ) , " data " , ) ;
assert_eq! (
read_file (
& virtual_fs ,
& dest_path . join ( " nested " ) . join ( " sub_dir " ) . join ( " c.txt " )
) ,
" c " ,
) ;
assert_eq! (
read_file ( & virtual_fs , & dest_path . join ( " sub_dir_link " ) . join ( " c.txt " ) ) ,
" c " ,
) ;
assert! (
virtual_fs
. lstat ( & dest_path . join ( " sub_dir_link " ) )
. unwrap ( )
. is_symlink
) ;
assert_eq! (
virtual_fs
. canonicalize ( & dest_path . join ( " sub_dir_link " ) . join ( " c.txt " ) )
. unwrap ( ) ,
dest_path . join ( " nested " ) . join ( " sub_dir " ) . join ( " c.txt " ) ,
) ;
}
fn into_virtual_fs (
builder : VfsBuilder ,
temp_dir : & TempDir ,
) -> ( PathBuf , FileBackedVfs ) {
let virtual_fs_file = temp_dir . path ( ) . join ( " virtual_fs " ) ;
let ( root_dir , files ) = builder . into_dir_and_files ( ) ;
{
let mut file = std ::fs ::File ::create ( & virtual_fs_file ) . unwrap ( ) ;
for file_data in & files {
file . write_all ( file_data ) . unwrap ( ) ;
}
}
let file = std ::fs ::File ::open ( & virtual_fs_file ) . unwrap ( ) ;
let dest_path = temp_dir . path ( ) . join ( " dest " ) ;
(
2023-06-10 11:09:45 -04:00
dest_path . to_path_buf ( ) ,
2023-05-10 20:06:59 -04:00
FileBackedVfs ::new (
file ,
VfsRoot {
dir : root_dir ,
2023-06-10 11:09:45 -04:00
root_path : dest_path . to_path_buf ( ) ,
2023-05-10 20:06:59 -04:00
start_file_offset : 0 ,
} ,
) ,
)
}
#[ test ]
fn circular_symlink ( ) {
let temp_dir = TempDir ::new ( ) ;
2023-06-10 11:09:45 -04:00
let src_path = temp_dir . path ( ) . canonicalize ( ) . join ( " src " ) ;
src_path . create_dir_all ( ) ;
let src_path = src_path . to_path_buf ( ) ;
2023-05-28 00:03:49 -04:00
let mut builder = VfsBuilder ::new ( src_path . clone ( ) ) . unwrap ( ) ;
2023-05-27 10:33:15 -04:00
builder
. add_symlink ( & src_path . join ( " a.txt " ) , & src_path . join ( " b.txt " ) )
. unwrap ( ) ;
builder
. add_symlink ( & src_path . join ( " b.txt " ) , & src_path . join ( " c.txt " ) )
. unwrap ( ) ;
builder
. add_symlink ( & src_path . join ( " c.txt " ) , & src_path . join ( " a.txt " ) )
. unwrap ( ) ;
2023-05-10 20:06:59 -04:00
let ( dest_path , virtual_fs ) = into_virtual_fs ( builder , & temp_dir ) ;
assert_eq! (
virtual_fs
. file_entry ( & dest_path . join ( " a.txt " ) )
. err ( )
. unwrap ( )
. to_string ( ) ,
" circular symlinks " ,
) ;
assert_eq! (
virtual_fs . read_link ( & dest_path . join ( " a.txt " ) ) . unwrap ( ) ,
dest_path . join ( " b.txt " )
) ;
assert_eq! (
virtual_fs . read_link ( & dest_path . join ( " b.txt " ) ) . unwrap ( ) ,
dest_path . join ( " c.txt " )
) ;
}
#[ tokio::test ]
async fn test_open_file ( ) {
let temp_dir = TempDir ::new ( ) ;
2023-06-10 11:09:45 -04:00
let temp_path = temp_dir . path ( ) . canonicalize ( ) ;
2023-05-28 00:03:49 -04:00
let mut builder = VfsBuilder ::new ( temp_path . to_path_buf ( ) ) . unwrap ( ) ;
2023-05-27 10:33:15 -04:00
builder
. add_file (
2023-06-10 11:09:45 -04:00
temp_path . join ( " a.txt " ) . as_path ( ) ,
2023-05-27 10:33:15 -04:00
" 0123456789 " . to_string ( ) . into_bytes ( ) ,
)
. unwrap ( ) ;
2023-05-10 20:06:59 -04:00
let ( dest_path , virtual_fs ) = into_virtual_fs ( builder , & temp_dir ) ;
let virtual_fs = Arc ::new ( virtual_fs ) ;
let file = virtual_fs . open_file ( & dest_path . join ( " a.txt " ) ) . unwrap ( ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Current ( 2 ) ) . unwrap ( ) ;
let mut buf = vec! [ 0 ; 2 ] ;
file . clone ( ) . read_sync ( & mut buf ) . unwrap ( ) ;
assert_eq! ( buf , b " 23 " ) ;
file . clone ( ) . read_sync ( & mut buf ) . unwrap ( ) ;
assert_eq! ( buf , b " 45 " ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Current ( - 4 ) ) . unwrap ( ) ;
file . clone ( ) . read_sync ( & mut buf ) . unwrap ( ) ;
assert_eq! ( buf , b " 23 " ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Start ( 2 ) ) . unwrap ( ) ;
file . clone ( ) . read_sync ( & mut buf ) . unwrap ( ) ;
assert_eq! ( buf , b " 23 " ) ;
file . clone ( ) . seek_sync ( SeekFrom ::End ( 2 ) ) . unwrap ( ) ;
file . clone ( ) . read_sync ( & mut buf ) . unwrap ( ) ;
assert_eq! ( buf , b " 89 " ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Current ( - 8 ) ) . unwrap ( ) ;
file . clone ( ) . read_sync ( & mut buf ) . unwrap ( ) ;
assert_eq! ( buf , b " 23 " ) ;
assert_eq! (
file
. clone ( )
. seek_sync ( SeekFrom ::Current ( - 5 ) )
. err ( )
. unwrap ( )
. into_io_error ( )
. to_string ( ) ,
" An attempt was made to move the file pointer before the beginning of the file. "
) ;
// go beyond the file length, then back
file . clone ( ) . seek_sync ( SeekFrom ::Current ( 40 ) ) . unwrap ( ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Current ( - 38 ) ) . unwrap ( ) ;
let read_buf = file . clone ( ) . read ( 2 ) . await . unwrap ( ) ;
assert_eq! ( read_buf . to_vec ( ) , b " 67 " ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Current ( - 2 ) ) . unwrap ( ) ;
// read to the end of the file
let all_buf = file . clone ( ) . read_all_sync ( ) . unwrap ( ) ;
assert_eq! ( all_buf . to_vec ( ) , b " 6789 " ) ;
file . clone ( ) . seek_sync ( SeekFrom ::Current ( - 9 ) ) . unwrap ( ) ;
// try try_clone_inner and read_all_async
let all_buf = file
. try_clone_inner ( )
. unwrap ( )
. read_all_async ( )
. await
. unwrap ( ) ;
assert_eq! ( all_buf . to_vec ( ) , b " 123456789 " ) ;
}
}