2024-01-01 14:58:21 -05:00
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
2023-05-10 20:06:59 -04:00
use std ::borrow ::Cow ;
2024-12-10 11:13:14 -05:00
use std ::cell ::RefCell ;
2023-05-10 20:06:59 -04:00
use std ::collections ::HashMap ;
use std ::collections ::HashSet ;
use std ::fs ::File ;
use std ::io ::Read ;
use std ::io ::Seek ;
use std ::io ::SeekFrom ;
2024-10-24 15:48:48 -04:00
use std ::ops ::Range ;
2023-05-10 20:06:59 -04:00
use std ::path ::Path ;
use std ::path ::PathBuf ;
use std ::rc ::Rc ;
use std ::sync ::Arc ;
2024-07-03 20:54:33 -04:00
use deno_core ::anyhow ::anyhow ;
2024-12-12 13:07:35 -05:00
use deno_core ::anyhow ::bail ;
2023-05-10 20:06:59 -04:00
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 ;
2024-12-12 13:07:35 -05:00
use deno_path_util ::normalize_path ;
use deno_path_util ::strip_unc_prefix ;
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 ;
2024-12-12 13:07:35 -05:00
use indexmap ::IndexSet ;
2023-05-10 20:06:59 -04:00
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 ;
2024-12-16 09:37:39 -05:00
use crate ::util ::display ::human_size ;
2024-12-11 09:40:50 -05:00
use crate ::util ::display ::DisplayTreeNode ;
2023-05-28 00:03:49 -04:00
use crate ::util ::fs ::canonicalize_path ;
2023-05-10 20:06:59 -04:00
2024-12-12 13:07:35 -05:00
use super ::binary ::DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME ;
#[ derive(Debug, PartialEq, Eq) ]
pub enum WindowsSystemRootablePath {
/// The root of the system above any drive letters.
WindowSystemRoot ,
Path ( PathBuf ) ,
}
impl WindowsSystemRootablePath {
pub fn join ( & self , name_component : & str ) -> PathBuf {
// this method doesn't handle multiple components
debug_assert! ( ! name_component . contains ( '\\' ) ) ;
debug_assert! ( ! name_component . contains ( '/' ) ) ;
match self {
WindowsSystemRootablePath ::WindowSystemRoot = > {
// windows drive letter
PathBuf ::from ( & format! ( " {} \\ " , name_component ) )
}
WindowsSystemRootablePath ::Path ( path ) = > path . join ( name_component ) ,
}
}
}
#[ derive(Debug) ]
pub struct BuiltVfs {
pub root_path : WindowsSystemRootablePath ,
pub root : VirtualDirectory ,
pub files : Vec < Vec < u8 > > ,
}
2024-11-25 09:37:48 -05:00
#[ derive(Debug, Copy, Clone) ]
pub enum VfsFileSubDataKind {
/// Raw bytes of the file.
Raw ,
/// Bytes to use for module loading. For example, for TypeScript
/// files this will be the transpiled JavaScript source.
ModuleGraph ,
}
2024-12-11 09:40:50 -05:00
#[ derive(Debug) ]
2023-05-10 20:06:59 -04:00
pub struct VfsBuilder {
2024-12-12 13:07:35 -05:00
executable_root : VirtualDirectory ,
2023-05-10 20:06:59 -04:00
files : Vec < Vec < u8 > > ,
current_offset : u64 ,
file_offsets : HashMap < String , u64 > ,
2024-12-12 13:07:35 -05:00
/// The minimum root directory that should be included in the VFS.
min_root_dir : Option < WindowsSystemRootablePath > ,
2023-05-10 20:06:59 -04:00
}
impl VfsBuilder {
2024-12-12 13:07:35 -05:00
pub fn new ( ) -> Self {
Self {
executable_root : VirtualDirectory {
name : " / " . to_string ( ) ,
2023-05-10 20:06:59 -04:00
entries : Vec ::new ( ) ,
} ,
files : Vec ::new ( ) ,
current_offset : 0 ,
file_offsets : Default ::default ( ) ,
2024-12-12 13:07:35 -05:00
min_root_dir : Default ::default ( ) ,
}
2024-10-24 15:48:48 -04:00
}
2024-12-12 13:07:35 -05:00
/// Add a directory that might be the minimum root directory
/// of the VFS.
///
/// For example, say the user has a deno.json and specifies an
/// import map in a parent directory. The import map won't be
/// included in the VFS, but its base will meaning we need to
/// tell the VFS builder to include the base of the import map
/// by calling this method.
pub fn add_possible_min_root_dir ( & mut self , path : & Path ) {
self . add_dir_raw ( path ) ;
match & self . min_root_dir {
Some ( WindowsSystemRootablePath ::WindowSystemRoot ) = > {
// already the root dir
}
Some ( WindowsSystemRootablePath ::Path ( current_path ) ) = > {
let mut common_components = Vec ::new ( ) ;
for ( a , b ) in current_path . components ( ) . zip ( path . components ( ) ) {
if a ! = b {
break ;
}
common_components . push ( a ) ;
}
if common_components . is_empty ( ) {
if cfg! ( windows ) {
self . min_root_dir =
Some ( WindowsSystemRootablePath ::WindowSystemRoot ) ;
} else {
self . min_root_dir =
Some ( WindowsSystemRootablePath ::Path ( PathBuf ::from ( " / " ) ) ) ;
}
} else {
self . min_root_dir = Some ( WindowsSystemRootablePath ::Path (
common_components . iter ( ) . collect ( ) ,
) ) ;
}
}
None = > {
self . min_root_dir =
Some ( WindowsSystemRootablePath ::Path ( path . to_path_buf ( ) ) ) ;
}
}
2023-05-10 20:06:59 -04:00
}
pub fn add_dir_recursive ( & mut self , path : & Path ) -> Result < ( ) , AnyError > {
2024-12-12 13:07:35 -05:00
let target_path = self . resolve_target_path ( path ) ? ;
self . add_dir_recursive_not_symlink ( & target_path )
2023-05-28 00:03:49 -04:00
}
2024-12-12 13:07:35 -05:00
fn add_dir_recursive_not_symlink (
2023-05-28 00:03:49 -04:00
& mut self ,
path : & Path ,
) -> Result < ( ) , AnyError > {
2024-12-12 13:07:35 -05:00
self . add_dir_raw ( path ) ;
2023-05-10 20:06:59 -04:00
let read_dir = std ::fs ::read_dir ( path )
. with_context ( | | format! ( " Reading {} " , path . display ( ) ) ) ? ;
2024-08-19 12:41:11 -04:00
let mut dir_entries =
read_dir . into_iter ( ) . collect ::< Result < Vec < _ > , _ > > ( ) ? ;
dir_entries . sort_by_cached_key ( | entry | entry . file_name ( ) ) ; // determinism
for entry in dir_entries {
2023-05-10 20:06:59 -04:00
let file_type = entry . file_type ( ) ? ;
let path = entry . path ( ) ;
if file_type . is_dir ( ) {
2024-12-12 13:07:35 -05:00
self . add_dir_recursive_not_symlink ( & path ) ? ;
2023-05-10 20:06:59 -04:00
} else if file_type . is_file ( ) {
2024-07-03 20:54:33 -04:00
self . add_file_at_path_not_symlink ( & path ) ? ;
2023-05-10 20:06:59 -04:00
} else if file_type . is_symlink ( ) {
2024-12-12 13:07:35 -05:00
match self . add_symlink ( & path ) {
Ok ( target ) = > match target {
SymlinkTarget ::File ( target ) = > {
self . add_file_at_path_not_symlink ( & target ) ?
2023-12-06 16:25:24 -05:00
}
2024-12-12 13:07:35 -05:00
SymlinkTarget ::Dir ( target ) = > {
self . add_dir_recursive_not_symlink ( & target ) ? ;
}
} ,
2023-12-06 16:25:24 -05:00
Err ( err ) = > {
2023-05-27 10:33:15 -04:00
log ::warn! (
2024-12-12 13:07:35 -05:00
" {} Failed resolving symlink. Ignoring. \n Path: {} \n Message: {:#} " ,
crate ::colors ::yellow ( " Warning " ) ,
path . display ( ) ,
err
) ;
2023-05-27 10:33:15 -04:00
}
}
2023-05-10 20:06:59 -04:00
}
}
Ok ( ( ) )
}
2024-12-12 13:07:35 -05:00
fn add_dir_raw ( & mut self , path : & Path ) -> & mut VirtualDirectory {
2023-05-27 10:33:15 -04:00
log ::debug! ( " Ensuring directory '{}' " , path . display ( ) ) ;
2024-12-12 13:07:35 -05:00
debug_assert! ( path . is_absolute ( ) ) ;
let mut current_dir = & mut self . executable_root ;
2023-05-10 20:06:59 -04:00
for component in path . components ( ) {
2024-12-12 13:07:35 -05:00
if matches! ( component , std ::path ::Component ::RootDir ) {
continue ;
}
2023-05-10 20:06:59 -04:00
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! ( ) ,
} ;
}
2024-12-12 13:07:35 -05:00
current_dir
2023-05-10 20:06:59 -04:00
}
2024-12-12 13:07:35 -05:00
pub fn get_system_root_dir_mut ( & mut self ) -> & mut VirtualDirectory {
& mut self . executable_root
}
pub fn get_dir_mut ( & mut self , path : & Path ) -> Option < & mut VirtualDirectory > {
debug_assert! ( path . is_absolute ( ) ) ;
let mut current_dir = & mut self . executable_root ;
for component in path . components ( ) {
if matches! ( component , std ::path ::Component ::RootDir ) {
continue ;
}
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 ( _ ) = > return None ,
} ;
match & mut current_dir . entries [ index ] {
VfsEntry ::Dir ( dir ) = > {
current_dir = dir ;
}
_ = > unreachable! ( ) ,
} ;
2024-07-03 20:54:33 -04:00
}
2024-12-12 13:07:35 -05:00
Some ( current_dir )
}
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_with_data ( path , file_bytes , VfsFileSubDataKind ::Raw )
2024-07-03 20:54:33 -04:00
}
2024-10-24 15:48:48 -04:00
fn add_file_at_path_not_symlink (
2024-07-03 20:54:33 -04:00
& mut self ,
path : & Path ,
) -> Result < ( ) , AnyError > {
2023-11-29 09:32:23 -05:00
let file_bytes = std ::fs ::read ( path )
. with_context ( | | format! ( " Reading {} " , path . display ( ) ) ) ? ;
2024-11-25 09:37:48 -05:00
self . add_file_with_data_inner ( path , file_bytes , VfsFileSubDataKind ::Raw )
2023-11-29 09:32:23 -05:00
}
2024-10-24 15:48:48 -04:00
pub fn add_file_with_data (
& mut self ,
path : & Path ,
data : Vec < u8 > ,
2024-11-25 09:37:48 -05:00
sub_data_kind : VfsFileSubDataKind ,
2024-10-24 15:48:48 -04:00
) -> Result < ( ) , AnyError > {
2024-12-12 13:07:35 -05:00
let metadata = std ::fs ::symlink_metadata ( path ) . with_context ( | | {
format! ( " Resolving target path for ' {} ' " , path . display ( ) )
} ) ? ;
if metadata . is_symlink ( ) {
let target = self . add_symlink ( path ) ? . into_path_buf ( ) ;
self . add_file_with_data_inner ( & target , data , sub_data_kind )
} else {
self . add_file_with_data_inner ( path , data , sub_data_kind )
2024-10-24 15:48:48 -04:00
}
}
fn add_file_with_data_inner (
& mut self ,
path : & Path ,
data : Vec < u8 > ,
2024-11-25 09:37:48 -05:00
sub_data_kind : VfsFileSubDataKind ,
2024-10-24 15:48:48 -04:00
) -> 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
} ;
2024-12-12 13:07:35 -05:00
let dir = self . add_dir_raw ( path . parent ( ) . unwrap ( ) ) ;
2023-05-10 20:06:59 -04:00
let name = path . file_name ( ) . unwrap ( ) . to_string_lossy ( ) ;
2024-12-09 20:11:52 -05:00
let offset_and_len = OffsetWithLength {
offset ,
len : data . len ( ) as u64 ,
} ;
2023-05-10 20:06:59 -04:00
match dir . entries . binary_search_by ( | e | e . name ( ) . cmp ( & name ) ) {
2024-11-25 09:37:48 -05:00
Ok ( index ) = > {
let entry = & mut dir . entries [ index ] ;
match entry {
VfsEntry ::File ( virtual_file ) = > match sub_data_kind {
VfsFileSubDataKind ::Raw = > {
2024-12-09 20:11:52 -05:00
virtual_file . offset = offset_and_len ;
2024-11-25 09:37:48 -05:00
}
VfsFileSubDataKind ::ModuleGraph = > {
2024-12-09 20:11:52 -05:00
virtual_file . module_graph_offset = offset_and_len ;
2024-11-25 09:37:48 -05:00
}
} ,
VfsEntry ::Dir ( _ ) | VfsEntry ::Symlink ( _ ) = > unreachable! ( ) ,
}
2024-07-03 20:54:33 -04:00
}
2023-05-10 20:06:59 -04:00
Err ( insert_index ) = > {
dir . entries . insert (
insert_index ,
VfsEntry ::File ( VirtualFile {
name : name . to_string ( ) ,
2024-12-09 20:11:52 -05:00
offset : offset_and_len ,
module_graph_offset : offset_and_len ,
2023-05-10 20:06:59 -04:00
} ) ,
) ;
}
}
// new file, update the list of files
if self . current_offset = = offset {
self . files . push ( data ) ;
2024-12-09 20:11:52 -05:00
self . current_offset + = offset_and_len . len ;
2023-05-10 20:06:59 -04:00
}
2023-05-27 10:33:15 -04:00
Ok ( ( ) )
2023-05-10 20:06:59 -04:00
}
2024-12-12 13:07:35 -05:00
fn resolve_target_path ( & mut self , path : & Path ) -> Result < PathBuf , AnyError > {
let metadata = std ::fs ::symlink_metadata ( path ) . with_context ( | | {
format! ( " Resolving target path for ' {} ' " , path . display ( ) )
} ) ? ;
if metadata . is_symlink ( ) {
Ok ( self . add_symlink ( path ) ? . into_path_buf ( ) )
} else {
Ok ( path . to_path_buf ( ) )
}
}
fn add_symlink ( & mut self , path : & Path ) -> Result < SymlinkTarget , AnyError > {
self . add_symlink_inner ( path , & mut IndexSet ::new ( ) )
}
fn add_symlink_inner (
2023-05-27 10:33:15 -04:00
& mut self ,
path : & Path ,
2024-12-12 13:07:35 -05:00
visited : & mut IndexSet < PathBuf > ,
) -> Result < SymlinkTarget , AnyError > {
log ::debug! ( " Adding symlink '{}' " , path . display ( ) ) ;
let target = strip_unc_prefix (
std ::fs ::read_link ( path )
. with_context ( | | format! ( " Reading symlink ' {} ' " , path . display ( ) ) ) ? ,
2023-05-27 10:33:15 -04:00
) ;
2024-12-12 13:07:35 -05:00
let target = normalize_path ( path . parent ( ) . unwrap ( ) . join ( & target ) ) ;
let dir = self . add_dir_raw ( 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 ) ) {
2024-12-12 13:07:35 -05:00
Ok ( _ ) = > { } // previously inserted
2023-05-10 20:06:59 -04:00
Err ( insert_index ) = > {
dir . entries . insert (
insert_index ,
VfsEntry ::Symlink ( VirtualSymlink {
name : name . to_string ( ) ,
2024-12-12 13:07:35 -05:00
dest_parts : VirtualSymlinkParts ::from_path ( & target ) ,
2023-05-10 20:06:59 -04:00
} ) ,
) ;
}
}
2024-12-12 13:07:35 -05:00
let target_metadata =
std ::fs ::symlink_metadata ( & target ) . with_context ( | | {
format! ( " Reading symlink target ' {} ' " , target . display ( ) )
} ) ? ;
if target_metadata . is_symlink ( ) {
if ! visited . insert ( target . clone ( ) ) {
// todo: probably don't error in this scenario
bail! (
" Circular symlink detected: {} -> {} " ,
visited
. iter ( )
. map ( | p | p . display ( ) . to_string ( ) )
. collect ::< Vec < _ > > ( )
. join ( " -> " ) ,
target . display ( )
) ;
}
self . add_symlink_inner ( & target , visited )
} else if target_metadata . is_dir ( ) {
Ok ( SymlinkTarget ::Dir ( target ) )
} else {
Ok ( SymlinkTarget ::File ( target ) )
}
2023-05-10 20:06:59 -04:00
}
2024-12-12 13:07:35 -05:00
pub fn build ( self ) -> BuiltVfs {
fn strip_prefix_from_symlinks (
dir : & mut VirtualDirectory ,
parts : & [ String ] ,
) {
for entry in & mut dir . entries {
match entry {
VfsEntry ::Dir ( dir ) = > {
strip_prefix_from_symlinks ( dir , parts ) ;
}
VfsEntry ::File ( _ ) = > { }
VfsEntry ::Symlink ( symlink ) = > {
let old_parts = std ::mem ::take ( & mut symlink . dest_parts . 0 ) ;
symlink . dest_parts . 0 =
old_parts . into_iter ( ) . skip ( parts . len ( ) ) . collect ( ) ;
}
}
}
}
let mut current_dir = self . executable_root ;
let mut current_path = if cfg! ( windows ) {
WindowsSystemRootablePath ::WindowSystemRoot
} else {
WindowsSystemRootablePath ::Path ( PathBuf ::from ( " / " ) )
} ;
loop {
if current_dir . entries . len ( ) ! = 1 {
break ;
}
if self . min_root_dir . as_ref ( ) = = Some ( & current_path ) {
break ;
}
match & current_dir . entries [ 0 ] {
VfsEntry ::Dir ( dir ) = > {
if dir . name = = DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
// special directory we want to maintain
break ;
}
match current_dir . entries . remove ( 0 ) {
VfsEntry ::Dir ( dir ) = > {
current_path =
WindowsSystemRootablePath ::Path ( current_path . join ( & dir . name ) ) ;
current_dir = dir ;
}
_ = > unreachable! ( ) ,
} ;
}
VfsEntry ::File ( _ ) | VfsEntry ::Symlink ( _ ) = > break ,
}
}
if let WindowsSystemRootablePath ::Path ( path ) = & current_path {
strip_prefix_from_symlinks (
& mut current_dir ,
& VirtualSymlinkParts ::from_path ( path ) . 0 ,
) ;
}
BuiltVfs {
root_path : current_path ,
root : current_dir ,
files : self . files ,
}
2023-05-10 20:06:59 -04:00
}
2024-12-12 13:07:35 -05:00
}
2023-05-25 15:29:58 -04:00
2024-12-12 13:07:35 -05:00
#[ derive(Debug) ]
enum SymlinkTarget {
File ( PathBuf ) ,
Dir ( PathBuf ) ,
}
impl SymlinkTarget {
pub fn into_path_buf ( self ) -> PathBuf {
match self {
Self ::File ( path ) = > path ,
Self ::Dir ( path ) = > path ,
2023-05-25 15:29:58 -04:00
}
}
2023-05-10 20:06:59 -04:00
}
2024-12-12 13:07:35 -05:00
pub fn output_vfs ( vfs : & BuiltVfs , executable_name : & str ) {
2024-12-11 09:40:50 -05:00
if ! log ::log_enabled! ( log ::Level ::Info ) {
return ; // no need to compute if won't output
}
2024-12-12 13:07:35 -05:00
if vfs . root . entries . is_empty ( ) {
2024-12-11 09:40:50 -05:00
return ; // nothing to output
}
let mut text = String ::new ( ) ;
2024-12-12 13:07:35 -05:00
let display_tree = vfs_as_display_tree ( vfs , executable_name ) ;
2024-12-11 09:40:50 -05:00
display_tree . print ( & mut text ) . unwrap ( ) ; // unwrap ok because it's writing to a string
2024-12-16 09:37:39 -05:00
log ::info! ( " \n {} \n " , deno_terminal ::colors ::bold ( " Embedded Files " ) ) ;
log ::info! ( " {} \n " , text . trim ( ) ) ;
2024-12-11 09:40:50 -05:00
log ::info! (
2024-12-16 09:37:39 -05:00
" Size: {} \n " ,
human_size ( vfs . files . iter ( ) . map ( | f | f . len ( ) as f64 ) . sum ( ) )
2024-12-11 09:40:50 -05:00
) ;
}
fn vfs_as_display_tree (
2024-12-12 13:07:35 -05:00
vfs : & BuiltVfs ,
2024-12-11 09:40:50 -05:00
executable_name : & str ,
) -> DisplayTreeNode {
2024-12-16 09:37:39 -05:00
/// The VFS only stores duplicate files once, so track that and display
/// it to the user so that it's not confusing.
#[ derive(Debug, Default, Copy, Clone) ]
struct Size {
unique : u64 ,
total : u64 ,
}
impl std ::ops ::Add for Size {
type Output = Self ;
fn add ( self , other : Self ) -> Self {
Self {
unique : self . unique + other . unique ,
total : self . total + other . total ,
}
}
}
impl std ::iter ::Sum for Size {
fn sum < I : Iterator < Item = Self > > ( iter : I ) -> Self {
iter . fold ( Self ::default ( ) , std ::ops ::Add ::add )
}
}
2024-12-11 09:40:50 -05:00
enum EntryOutput < ' a > {
2024-12-16 09:37:39 -05:00
All ( Size ) ,
2024-12-11 09:40:50 -05:00
Subset ( Vec < DirEntryOutput < ' a > > ) ,
2024-12-16 09:37:39 -05:00
File ( Size ) ,
2024-12-11 09:40:50 -05:00
Symlink ( & ' a [ String ] ) ,
}
2024-12-16 09:37:39 -05:00
impl < ' a > EntryOutput < ' a > {
pub fn size ( & self ) -> Size {
match self {
EntryOutput ::All ( size ) = > * size ,
EntryOutput ::Subset ( children ) = > {
children . iter ( ) . map ( | c | c . output . size ( ) ) . sum ( )
}
EntryOutput ::File ( size ) = > * size ,
EntryOutput ::Symlink ( _ ) = > Size {
unique : 0 ,
total : 0 ,
} ,
}
}
}
2024-12-11 09:40:50 -05:00
impl < ' a > EntryOutput < ' a > {
pub fn as_display_tree ( & self , name : String ) -> DisplayTreeNode {
2024-12-16 09:37:39 -05:00
fn format_size ( size : Size ) -> String {
if size . unique = = size . total {
human_size ( size . unique as f64 )
} else {
format! (
" {}{} " ,
human_size ( size . total as f64 ) ,
deno_terminal ::colors ::gray ( format! (
" - {} unique " ,
human_size ( size . unique as f64 )
) )
)
2024-12-12 13:07:35 -05:00
}
2024-12-16 09:37:39 -05:00
}
2024-12-11 09:40:50 -05:00
DisplayTreeNode {
text : match self {
2024-12-16 09:37:39 -05:00
EntryOutput ::All ( size ) = > {
format! ( " {} /* ( {} ) " , name , format_size ( * size ) )
}
EntryOutput ::Subset ( children ) = > {
let size = children . iter ( ) . map ( | c | c . output . size ( ) ) . sum ::< Size > ( ) ;
format! ( " {} ( {} ) " , name , format_size ( size ) )
}
EntryOutput ::File ( size ) = > {
format! ( " {} ( {} ) " , name , format_size ( * size ) )
2024-12-12 13:07:35 -05:00
}
2024-12-11 09:40:50 -05:00
EntryOutput ::Symlink ( parts ) = > {
format! ( " {} --> {} " , name , parts . join ( " / " ) )
}
} ,
2024-12-16 09:37:39 -05:00
children : match self {
EntryOutput ::All ( _ ) = > Vec ::new ( ) ,
EntryOutput ::Subset ( children ) = > children
. iter ( )
. map ( | entry | entry . output . as_display_tree ( entry . name . to_string ( ) ) )
. collect ( ) ,
EntryOutput ::File ( _ ) = > Vec ::new ( ) ,
EntryOutput ::Symlink ( _ ) = > Vec ::new ( ) ,
2024-12-11 09:40:50 -05:00
} ,
}
}
}
pub struct DirEntryOutput < ' a > {
2024-12-16 09:37:39 -05:00
name : Cow < ' a , str > ,
2024-12-11 09:40:50 -05:00
output : EntryOutput < ' a > ,
}
2024-12-16 09:37:39 -05:00
impl < ' a > DirEntryOutput < ' a > {
/// Collapses leaf nodes so they don't take up so much space when being
/// displayed.
///
/// We only want to collapse leafs so that nodes of the same depth have
/// the same indentation.
pub fn collapse_leaf_nodes ( & mut self ) {
let EntryOutput ::Subset ( vec ) = & mut self . output else {
return ;
} ;
for dir_entry in vec . iter_mut ( ) {
dir_entry . collapse_leaf_nodes ( ) ;
}
if vec . len ( ) ! = 1 {
return ;
}
let child = & mut vec [ 0 ] ;
let child_name = & child . name ;
match & mut child . output {
EntryOutput ::All ( size ) = > {
self . name = Cow ::Owned ( format! ( " {} / {} " , self . name , child_name ) ) ;
self . output = EntryOutput ::All ( * size ) ;
}
EntryOutput ::Subset ( children ) = > {
if children . is_empty ( ) {
self . name = Cow ::Owned ( format! ( " {} / {} " , self . name , child_name ) ) ;
self . output = EntryOutput ::Subset ( vec! [ ] ) ;
}
}
EntryOutput ::File ( size ) = > {
self . name = Cow ::Owned ( format! ( " {} / {} " , self . name , child_name ) ) ;
self . output = EntryOutput ::File ( * size ) ;
}
EntryOutput ::Symlink ( parts ) = > {
let new_name = format! ( " {} / {} " , self . name , child_name ) ;
self . output = EntryOutput ::Symlink ( parts ) ;
self . name = Cow ::Owned ( new_name ) ;
}
}
}
}
fn file_size ( file : & VirtualFile , seen_offsets : & mut HashSet < u64 > ) -> Size {
fn add_offset_to_size (
offset : OffsetWithLength ,
size : & mut Size ,
seen_offsets : & mut HashSet < u64 > ,
) {
if offset . len = = 0 {
// some empty files have a dummy offset, so don't
// insert them into the seen offsets
return ;
}
if seen_offsets . insert ( offset . offset ) {
size . total + = offset . len ;
size . unique + = offset . len ;
} else {
size . total + = offset . len ;
}
}
let mut size = Size ::default ( ) ;
add_offset_to_size ( file . offset , & mut size , seen_offsets ) ;
if file . module_graph_offset . offset ! = file . offset . offset {
add_offset_to_size ( file . module_graph_offset , & mut size , seen_offsets ) ;
}
size
}
fn dir_size ( dir : & VirtualDirectory , seen_offsets : & mut HashSet < u64 > ) -> Size {
let mut size = Size ::default ( ) ;
for entry in & dir . entries {
match entry {
VfsEntry ::Dir ( virtual_directory ) = > {
size = size + dir_size ( virtual_directory , seen_offsets ) ;
}
VfsEntry ::File ( file ) = > {
size = size + file_size ( file , seen_offsets ) ;
}
VfsEntry ::Symlink ( _ ) = > {
// ignore
}
}
}
size
}
fn show_global_node_modules_dir < ' a > (
vfs_dir : & ' a VirtualDirectory ,
seen_offsets : & mut HashSet < u64 > ,
) -> Vec < DirEntryOutput < ' a > > {
fn show_subset_deep < ' a > (
vfs_dir : & ' a VirtualDirectory ,
2024-12-12 13:07:35 -05:00
depth : usize ,
2024-12-16 09:37:39 -05:00
seen_offsets : & mut HashSet < u64 > ,
) -> EntryOutput < ' a > {
2024-12-12 13:07:35 -05:00
if depth = = 0 {
2024-12-16 09:37:39 -05:00
EntryOutput ::All ( dir_size ( vfs_dir , seen_offsets ) )
2024-12-12 13:07:35 -05:00
} else {
2024-12-16 09:37:39 -05:00
EntryOutput ::Subset ( show_subset ( vfs_dir , depth , seen_offsets ) )
2024-12-12 13:07:35 -05:00
}
}
2024-12-16 09:37:39 -05:00
fn show_subset < ' a > (
vfs_dir : & ' a VirtualDirectory ,
2024-12-12 13:07:35 -05:00
depth : usize ,
2024-12-16 09:37:39 -05:00
seen_offsets : & mut HashSet < u64 > ,
) -> Vec < DirEntryOutput < ' a > > {
2024-12-11 09:40:50 -05:00
vfs_dir
. entries
. iter ( )
. map ( | entry | DirEntryOutput {
2024-12-16 09:37:39 -05:00
name : Cow ::Borrowed ( entry . name ( ) ) ,
2024-12-12 13:07:35 -05:00
output : match entry {
VfsEntry ::Dir ( virtual_directory ) = > {
2024-12-16 09:37:39 -05:00
show_subset_deep ( virtual_directory , depth - 1 , seen_offsets )
}
VfsEntry ::File ( file ) = > {
EntryOutput ::File ( file_size ( file , seen_offsets ) )
2024-12-12 13:07:35 -05:00
}
VfsEntry ::Symlink ( virtual_symlink ) = > {
EntryOutput ::Symlink ( & virtual_symlink . dest_parts . 0 )
}
} ,
2024-12-11 09:40:50 -05:00
} )
2024-12-12 13:07:35 -05:00
. collect ( )
}
// in this scenario, we want to show
// .deno_compile_node_modules/localhost/<package_name>/<version>/*
2024-12-16 09:37:39 -05:00
show_subset ( vfs_dir , 3 , seen_offsets )
2024-12-12 13:07:35 -05:00
}
fn include_all_entries < ' a > (
dir_path : & WindowsSystemRootablePath ,
vfs_dir : & ' a VirtualDirectory ,
2024-12-16 09:37:39 -05:00
seen_offsets : & mut HashSet < u64 > ,
2024-12-12 13:07:35 -05:00
) -> Vec < DirEntryOutput < ' a > > {
if vfs_dir . name = = DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
2024-12-16 09:37:39 -05:00
return show_global_node_modules_dir ( vfs_dir , seen_offsets ) ;
2024-12-12 13:07:35 -05:00
}
vfs_dir
. entries
. iter ( )
. map ( | entry | DirEntryOutput {
2024-12-16 09:37:39 -05:00
name : Cow ::Borrowed ( entry . name ( ) ) ,
output : analyze_entry ( dir_path . join ( entry . name ( ) ) , entry , seen_offsets ) ,
2024-12-12 13:07:35 -05:00
} )
. collect ( )
2024-12-11 09:40:50 -05:00
}
2024-12-16 09:37:39 -05:00
fn analyze_entry < ' a > (
path : PathBuf ,
entry : & ' a VfsEntry ,
seen_offsets : & mut HashSet < u64 > ,
) -> EntryOutput < ' a > {
2024-12-11 09:40:50 -05:00
match entry {
2024-12-16 09:37:39 -05:00
VfsEntry ::Dir ( virtual_directory ) = > {
analyze_dir ( path , virtual_directory , seen_offsets )
}
VfsEntry ::File ( file ) = > EntryOutput ::File ( file_size ( file , seen_offsets ) ) ,
2024-12-11 09:40:50 -05:00
VfsEntry ::Symlink ( virtual_symlink ) = > {
2024-12-12 13:07:35 -05:00
EntryOutput ::Symlink ( & virtual_symlink . dest_parts . 0 )
2024-12-11 09:40:50 -05:00
}
}
}
2024-12-16 09:37:39 -05:00
fn analyze_dir < ' a > (
dir : PathBuf ,
vfs_dir : & ' a VirtualDirectory ,
seen_offsets : & mut HashSet < u64 > ,
) -> EntryOutput < ' a > {
2024-12-12 13:07:35 -05:00
if vfs_dir . name = = DENO_COMPILE_GLOBAL_NODE_MODULES_DIR_NAME {
2024-12-16 09:37:39 -05:00
return EntryOutput ::Subset ( show_global_node_modules_dir (
vfs_dir ,
seen_offsets ,
) ) ;
2024-12-12 13:07:35 -05:00
}
let real_entry_count = std ::fs ::read_dir ( & dir )
2024-12-11 09:40:50 -05:00
. ok ( )
. map ( | entries | entries . flat_map ( | e | e . ok ( ) ) . count ( ) )
. unwrap_or ( 0 ) ;
if real_entry_count = = vfs_dir . entries . len ( ) {
let children = vfs_dir
. entries
. iter ( )
. map ( | entry | DirEntryOutput {
2024-12-16 09:37:39 -05:00
name : Cow ::Borrowed ( entry . name ( ) ) ,
output : analyze_entry ( dir . join ( entry . name ( ) ) , entry , seen_offsets ) ,
2024-12-11 09:40:50 -05:00
} )
. collect ::< Vec < _ > > ( ) ;
if children
. iter ( )
2024-12-16 09:37:39 -05:00
. all ( | c | ! matches! ( c . output , EntryOutput ::Subset { .. } ) )
2024-12-11 09:40:50 -05:00
{
2024-12-16 09:37:39 -05:00
EntryOutput ::All ( children . iter ( ) . map ( | c | c . output . size ( ) ) . sum ( ) )
2024-12-11 09:40:50 -05:00
} else {
EntryOutput ::Subset ( children )
}
} else {
2024-12-12 13:07:35 -05:00
EntryOutput ::Subset ( include_all_entries (
& WindowsSystemRootablePath ::Path ( dir ) ,
vfs_dir ,
2024-12-16 09:37:39 -05:00
seen_offsets ,
2024-12-12 13:07:35 -05:00
) )
2024-12-11 09:40:50 -05:00
}
}
// always include all the entries for the root directory, otherwise the
// user might not have context about what's being shown
2024-12-16 09:37:39 -05:00
let mut seen_offsets = HashSet ::with_capacity ( vfs . files . len ( ) ) ;
let mut child_entries =
include_all_entries ( & vfs . root_path , & vfs . root , & mut seen_offsets ) ;
for child_entry in & mut child_entries {
child_entry . collapse_leaf_nodes ( ) ;
}
2024-12-12 13:07:35 -05:00
DisplayTreeNode {
text : deno_terminal ::colors ::italic ( executable_name ) . to_string ( ) ,
children : child_entries
. iter ( )
. map ( | entry | entry . output . as_display_tree ( entry . name . to_string ( ) ) )
. collect ( ) ,
}
2024-12-11 09:40:50 -05: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 ,
2024-11-12 23:35:04 -05:00
ctime : None ,
2023-05-10 20:06:59 -04:00
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 ,
2024-11-12 23:35:04 -05:00
ctime : None ,
2023-05-10 20:06:59 -04:00
blksize : 0 ,
2024-12-09 20:11:52 -05:00
size : file . offset . len ,
2023-05-10 20:06:59 -04:00
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 ,
2024-11-12 23:35:04 -05:00
ctime : None ,
2023-05-10 20:06:59 -04:00
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 {
2024-12-09 20:11:52 -05:00
#[ serde(rename = " n " ) ]
2023-05-10 20:06:59 -04:00
pub name : String ,
// should be sorted by name
2024-12-09 20:11:52 -05:00
#[ serde(rename = " e " ) ]
2023-05-10 20:06:59 -04:00
pub entries : Vec < VfsEntry > ,
}
2024-12-12 13:07:35 -05:00
impl VirtualDirectory {
pub fn insert_entry ( & mut self , entry : VfsEntry ) {
let name = entry . name ( ) ;
match self . entries . binary_search_by ( | e | e . name ( ) . cmp ( name ) ) {
Ok ( index ) = > {
self . entries [ index ] = entry ;
}
Err ( insert_index ) = > {
self . entries . insert ( insert_index , entry ) ;
}
}
}
}
2024-12-09 20:11:52 -05:00
#[ derive(Debug, Clone, Copy, Serialize, Deserialize) ]
pub struct OffsetWithLength {
#[ serde(rename = " o " ) ]
pub offset : u64 ,
#[ serde(rename = " l " ) ]
pub len : u64 ,
}
2023-05-10 20:06:59 -04:00
#[ derive(Debug, Clone, Serialize, Deserialize) ]
pub struct VirtualFile {
2024-12-09 20:11:52 -05:00
#[ serde(rename = " n " ) ]
2023-05-10 20:06:59 -04:00
pub name : String ,
2024-12-09 20:11:52 -05:00
#[ serde(rename = " o " ) ]
pub offset : OffsetWithLength ,
2024-11-25 09:37:48 -05:00
/// Offset file to use for module loading when it differs from the
/// raw file. Often this will be the same offset as above for data
/// such as JavaScript files, but for TypeScript files the `offset`
/// will be the original raw bytes when included as an asset and this
/// offset will be to the transpiled JavaScript source.
2024-12-09 20:11:52 -05:00
#[ serde(rename = " m " ) ]
pub module_graph_offset : OffsetWithLength ,
2023-05-10 20:06:59 -04:00
}
2024-12-12 13:07:35 -05:00
#[ derive(Debug, Serialize, Deserialize) ]
pub struct VirtualSymlinkParts ( Vec < String > ) ;
impl VirtualSymlinkParts {
pub fn from_path ( path : & Path ) -> Self {
Self (
path
. components ( )
. filter ( | c | ! matches! ( c , std ::path ::Component ::RootDir ) )
. map ( | c | c . as_os_str ( ) . to_string_lossy ( ) . to_string ( ) )
. collect ( ) ,
)
}
}
2023-05-10 20:06:59 -04:00
#[ derive(Debug, Serialize, Deserialize) ]
pub struct VirtualSymlink {
2024-12-09 20:11:52 -05:00
#[ serde(rename = " n " ) ]
2023-05-10 20:06:59 -04:00
pub name : String ,
2024-12-09 20:11:52 -05:00
#[ serde(rename = " p " ) ]
2024-12-12 13:07:35 -05:00
pub dest_parts : VirtualSymlinkParts ,
2023-05-10 20:06:59 -04:00
}
impl VirtualSymlink {
pub fn resolve_dest_from_root ( & self , root : & Path ) -> PathBuf {
let mut dest = root . to_path_buf ( ) ;
2024-12-12 13:07:35 -05:00
for part in & self . dest_parts . 0 {
2023-05-10 20:06:59 -04:00
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 ( ) {
2024-12-12 13:07:35 -05:00
let component = component . as_os_str ( ) ;
2023-05-10 20:06:59 -04:00
let current_dir = match current_entry {
VfsEntryRef ::Dir ( dir ) = > {
2024-12-12 13:07:35 -05:00
final_path . push ( component ) ;
2023-05-10 20:06:59 -04:00
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 ) = > {
2024-12-12 13:07:35 -05:00
final_path . push ( component ) ;
2023-05-10 20:06:59 -04:00
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 " ,
) ) ;
}
} ;
2024-12-12 13:07:35 -05:00
let component = component . to_string_lossy ( ) ;
2023-05-10 20:06:59 -04:00
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 ) )
}
}
struct FileBackedVfsFile {
file : VirtualFile ,
2024-12-10 11:13:14 -05:00
pos : RefCell < u64 > ,
2023-05-10 20:06:59 -04:00
vfs : Arc < FileBackedVfs > ,
}
impl FileBackedVfsFile {
fn seek ( & self , pos : SeekFrom ) -> FsResult < u64 > {
match pos {
SeekFrom ::Start ( pos ) = > {
2024-12-10 11:13:14 -05:00
* self . pos . borrow_mut ( ) = pos ;
2023-05-10 20:06:59 -04:00
Ok ( pos )
}
SeekFrom ::End ( offset ) = > {
2024-12-09 20:11:52 -05:00
if offset < 0 & & - offset as u64 > self . file . offset . 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 {
2024-12-10 11:13:14 -05:00
let mut current_pos = self . pos . borrow_mut ( ) ;
2023-05-10 20:06:59 -04:00
* current_pos = if offset > = 0 {
2024-12-09 20:11:52 -05:00
self . file . offset . len - ( offset as u64 )
2023-05-10 20:06:59 -04:00
} else {
2024-12-09 20:11:52 -05:00
self . file . offset . len + ( - offset as u64 )
2023-05-10 20:06:59 -04:00
} ;
Ok ( * current_pos )
}
}
SeekFrom ::Current ( offset ) = > {
2024-12-10 11:13:14 -05:00
let mut current_pos = self . pos . borrow_mut ( ) ;
2023-05-10 20:06:59 -04:00
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 > {
2024-11-22 14:26:38 -05:00
let read_pos = {
2024-12-10 11:13:14 -05:00
let mut pos = self . pos . borrow_mut ( ) ;
2023-05-10 20:06:59 -04:00
let read_pos = * pos ;
// advance the position due to the read
2024-12-09 20:11:52 -05:00
* pos = std ::cmp ::min ( self . file . offset . len , * pos + buf . len ( ) as u64 ) ;
2023-05-10 20:06:59 -04:00
read_pos
} ;
self
. vfs
2024-11-22 14:26:38 -05:00
. read_file ( & self . file , read_pos , buf )
2023-05-10 20:06:59 -04:00
. map_err ( | err | err . into ( ) )
}
2024-11-25 09:37:48 -05:00
fn read_to_end ( & self ) -> FsResult < Cow < 'static , [ u8 ] > > {
2024-11-22 14:26:38 -05:00
let read_pos = {
2024-12-10 11:13:14 -05:00
let mut pos = self . pos . borrow_mut ( ) ;
2023-05-10 20:06:59 -04:00
let read_pos = * pos ;
// todo(dsherret): should this always set it to the end of the file?
2024-12-09 20:11:52 -05:00
if * pos < self . file . offset . len {
2023-05-10 20:06:59 -04:00
// advance the position due to the read
2024-12-09 20:11:52 -05:00
* pos = self . file . offset . len ;
2023-05-10 20:06:59 -04:00
}
read_pos
} ;
2024-12-09 20:11:52 -05:00
if read_pos > self . file . offset . len {
2024-11-25 09:37:48 -05:00
return Ok ( Cow ::Borrowed ( & [ ] ) ) ;
}
if read_pos = = 0 {
Ok (
self
. vfs
. read_file_all ( & self . file , VfsFileSubDataKind ::Raw ) ? ,
)
} else {
2024-12-09 20:11:52 -05:00
let size = ( self . file . offset . len - read_pos ) as usize ;
2024-11-25 09:37:48 -05:00
let mut buf = vec! [ 0 ; size ] ;
self . vfs . read_file ( & self . file , read_pos , & mut buf ) ? ;
Ok ( Cow ::Owned ( buf ) )
2023-05-10 20:06:59 -04:00
}
}
}
#[ 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 ) > {
2024-12-10 11:13:14 -05:00
// this is fast, no need to spawn a task
let nread = self . read_to_buf ( & mut buf ) ? ;
Ok ( ( nread , buf ) )
2023-05-10 20:06:59 -04:00
}
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 )
}
2024-11-27 21:28:41 -05:00
fn read_all_sync ( self : Rc < Self > ) -> FsResult < Cow < 'static , [ u8 ] > > {
self . read_to_end ( )
2023-05-10 20:06:59 -04:00
}
2024-11-27 21:28:41 -05:00
async fn read_all_async ( self : Rc < Self > ) -> FsResult < Cow < 'static , [ u8 ] > > {
2024-12-10 11:13:14 -05:00
// this is fast, no need to spawn a task
self . read_to_end ( )
2023-05-10 20:06:59 -04:00
}
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 {
2024-10-24 15:48:48 -04:00
vfs_data : Cow < 'static , [ u8 ] > ,
2023-05-10 20:06:59 -04:00
fs_root : VfsRoot ,
}
impl FileBackedVfs {
2024-10-24 15:48:48 -04:00
pub fn new ( data : Cow < 'static , [ u8 ] > , fs_root : VfsRoot ) -> Self {
2023-05-10 20:06:59 -04:00
Self {
2024-10-24 15:48:48 -04:00
vfs_data : data ,
2023-05-10 20:06:59 -04:00
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 )
}
2024-10-24 15:48:48 -04:00
pub fn read_file_all (
& self ,
file : & VirtualFile ,
2024-11-25 09:37:48 -05:00
sub_data_kind : VfsFileSubDataKind ,
2024-10-24 15:48:48 -04:00
) -> std ::io ::Result < Cow < 'static , [ u8 ] > > {
2024-12-09 20:11:52 -05:00
let read_len = match sub_data_kind {
VfsFileSubDataKind ::Raw = > file . offset . len ,
VfsFileSubDataKind ::ModuleGraph = > file . module_graph_offset . len ,
} ;
let read_range = self . get_read_range ( file , sub_data_kind , 0 , read_len ) ? ;
2024-10-24 15:48:48 -04:00
match & self . vfs_data {
Cow ::Borrowed ( data ) = > Ok ( Cow ::Borrowed ( & data [ read_range ] ) ) ,
Cow ::Owned ( data ) = > Ok ( Cow ::Owned ( data [ read_range ] . to_vec ( ) ) ) ,
}
2023-05-10 20:06:59 -04:00
}
pub fn read_file (
& self ,
file : & VirtualFile ,
pos : u64 ,
buf : & mut [ u8 ] ,
) -> std ::io ::Result < usize > {
2024-11-25 09:37:48 -05:00
let read_range = self . get_read_range (
file ,
VfsFileSubDataKind ::Raw ,
pos ,
buf . len ( ) as u64 ,
) ? ;
2024-11-22 14:26:38 -05:00
let read_len = read_range . len ( ) ;
buf [ .. read_len ] . copy_from_slice ( & self . vfs_data [ read_range ] ) ;
Ok ( read_len )
2024-10-24 15:48:48 -04:00
}
fn get_read_range (
& self ,
file : & VirtualFile ,
2024-11-25 09:37:48 -05:00
sub_data_kind : VfsFileSubDataKind ,
2024-10-24 15:48:48 -04:00
pos : u64 ,
len : u64 ,
) -> std ::io ::Result < Range < usize > > {
2024-12-09 20:11:52 -05:00
let file_offset_and_len = match sub_data_kind {
VfsFileSubDataKind ::Raw = > file . offset ,
VfsFileSubDataKind ::ModuleGraph = > file . module_graph_offset ,
} ;
if pos > file_offset_and_len . len {
2024-08-01 00:15:13 -04:00
return Err ( std ::io ::Error ::new (
std ::io ::ErrorKind ::UnexpectedEof ,
" unexpected EOF " ,
) ) ;
}
2024-12-09 20:11:52 -05:00
let file_offset =
self . fs_root . start_file_offset + file_offset_and_len . offset ;
2024-11-22 14:26:38 -05:00
let start = file_offset + pos ;
2024-12-09 20:11:52 -05:00
let end = file_offset + std ::cmp ::min ( pos + len , file_offset_and_len . len ) ;
2024-10-24 15:48:48 -04:00
Ok ( start as usize .. end as usize )
2023-05-10 20:06:59 -04:00
}
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 {
2024-12-11 09:40:50 -05:00
use console_static_text ::ansi ::strip_ansi_codes ;
2023-05-10 20:06:59 -04:00
use std ::io ::Write ;
2024-12-12 13:07:35 -05:00
use test_util ::assert_contains ;
2023-05-10 20:06:59 -04:00
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 ( ) ;
2024-11-25 09:37:48 -05:00
String ::from_utf8 (
vfs
. read_file_all ( file , VfsFileSubDataKind ::Raw )
. unwrap ( )
. into_owned ( ) ,
)
. unwrap ( )
2023-05-10 20:06:59 -04:00
}
#[ 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 ( ) ;
2024-12-12 13:07:35 -05:00
src_path . join ( " sub_dir " ) . create_dir_all ( ) ;
src_path . join ( " e.txt " ) . write ( " e " ) ;
src_path . symlink_file ( " e.txt " , " sub_dir/e.txt " ) ;
2023-06-10 11:09:45 -04:00
let src_path = src_path . to_path_buf ( ) ;
2024-12-12 13:07:35 -05:00
let mut builder = VfsBuilder ::new ( ) ;
2023-05-27 10:33:15 -04:00
builder
2024-11-25 09:37:48 -05:00
. add_file_with_data_inner (
& src_path . join ( " a.txt " ) ,
" data " . into ( ) ,
VfsFileSubDataKind ::Raw ,
)
2023-05-27 10:33:15 -04:00
. unwrap ( ) ;
builder
2024-11-25 09:37:48 -05:00
. add_file_with_data_inner (
& src_path . join ( " b.txt " ) ,
" data " . into ( ) ,
VfsFileSubDataKind ::Raw ,
)
2023-05-27 10:33:15 -04:00
. 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
2024-11-25 09:37:48 -05:00
. add_file_with_data_inner (
& src_path . join ( " c.txt " ) ,
" c " . into ( ) ,
VfsFileSubDataKind ::Raw ,
)
2023-05-27 10:33:15 -04:00
. unwrap ( ) ;
builder
2024-10-24 15:48:48 -04:00
. add_file_with_data_inner (
& src_path . join ( " sub_dir " ) . join ( " d.txt " ) ,
" d " . into ( ) ,
2024-11-25 09:37:48 -05:00
VfsFileSubDataKind ::Raw ,
2024-10-24 15:48:48 -04:00
)
2023-05-27 10:33:15 -04:00
. unwrap ( ) ;
2024-12-12 13:07:35 -05:00
builder . add_file_at_path ( & src_path . join ( " e.txt " ) ) . unwrap ( ) ;
2023-05-27 10:33:15 -04:00
builder
2024-12-12 13:07:35 -05:00
. add_symlink ( & src_path . join ( " sub_dir " ) . join ( " e.txt " ) )
2023-05-27 10:33:15 -04:00
. 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 ( ) ;
2024-12-12 13:07:35 -05:00
let mut builder = VfsBuilder ::new ( ) ;
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 " ) ;
2024-12-12 13:07:35 -05:00
let vfs = builder . build ( ) ;
2023-05-10 20:06:59 -04:00
{
let mut file = std ::fs ::File ::create ( & virtual_fs_file ) . unwrap ( ) ;
2024-12-12 13:07:35 -05:00
for file_data in & vfs . files {
2023-05-10 20:06:59 -04:00
file . write_all ( file_data ) . unwrap ( ) ;
}
}
let dest_path = temp_dir . path ( ) . join ( " dest " ) ;
2024-08-01 00:15:13 -04:00
let data = std ::fs ::read ( & virtual_fs_file ) . unwrap ( ) ;
2023-05-10 20:06:59 -04:00
(
2023-06-10 11:09:45 -04:00
dest_path . to_path_buf ( ) ,
2023-05-10 20:06:59 -04:00
FileBackedVfs ::new (
2024-10-24 15:48:48 -04:00
Cow ::Owned ( data ) ,
2023-05-10 20:06:59 -04:00
VfsRoot {
2024-12-12 13:07:35 -05:00
dir : vfs . root ,
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 ( ) ;
2024-12-12 13:07:35 -05:00
src_path . symlink_file ( " a.txt " , " b.txt " ) ;
src_path . symlink_file ( " b.txt " , " c.txt " ) ;
src_path . symlink_file ( " c.txt " , " a.txt " ) ;
2023-06-10 11:09:45 -04:00
let src_path = src_path . to_path_buf ( ) ;
2024-12-12 13:07:35 -05:00
let mut builder = VfsBuilder ::new ( ) ;
let err = builder
. add_symlink ( src_path . join ( " a.txt " ) . as_path ( ) )
. unwrap_err ( ) ;
assert_contains! ( err . to_string ( ) , " Circular symlink detected " , ) ;
2023-05-10 20:06:59 -04:00
}
#[ 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 ( ) ;
2024-12-12 13:07:35 -05:00
let mut builder = VfsBuilder ::new ( ) ;
2023-05-27 10:33:15 -04:00
builder
2024-10-24 15:48:48 -04:00
. add_file_with_data_inner (
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 ( ) ,
2024-11-25 09:37:48 -05:00
VfsFileSubDataKind ::Raw ,
2023-05-27 10:33:15 -04:00
)
. 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 " ) ;
}
2024-12-11 09:40:50 -05:00
#[ test ]
fn test_vfs_as_display_tree ( ) {
let temp_dir = TempDir ::new ( ) ;
temp_dir . write ( " root.txt " , " " ) ;
temp_dir . create_dir_all ( " a " ) ;
2024-12-16 09:37:39 -05:00
temp_dir . write ( " a/a.txt " , " data " ) ;
temp_dir . write ( " a/b.txt " , " other data " ) ;
2024-12-11 09:40:50 -05:00
temp_dir . create_dir_all ( " b " ) ;
temp_dir . write ( " b/a.txt " , " " ) ;
temp_dir . write ( " b/b.txt " , " " ) ;
temp_dir . create_dir_all ( " c " ) ;
temp_dir . write ( " c/a.txt " , " contents " ) ;
temp_dir . symlink_file ( " c/a.txt " , " c/b.txt " ) ;
assert_eq! ( temp_dir . read_to_string ( " c/b.txt " ) , " contents " ) ; // ensure the symlink works
2024-12-12 13:07:35 -05:00
let mut vfs_builder = VfsBuilder ::new ( ) ;
2024-12-11 09:40:50 -05:00
// full dir
vfs_builder
. add_dir_recursive ( temp_dir . path ( ) . join ( " a " ) . as_path ( ) )
. unwrap ( ) ;
// part of the dir
vfs_builder
. add_file_at_path ( temp_dir . path ( ) . join ( " b/a.txt " ) . as_path ( ) )
. unwrap ( ) ;
// symlink
vfs_builder
. add_dir_recursive ( temp_dir . path ( ) . join ( " c " ) . as_path ( ) )
. unwrap ( ) ;
temp_dir . write ( " c/c.txt " , " " ) ; // write an extra file so it shows the whole directory
2024-12-12 13:07:35 -05:00
let node = vfs_as_display_tree ( & vfs_builder . build ( ) , " executable " ) ;
2024-12-11 09:40:50 -05:00
let mut text = String ::new ( ) ;
node . print ( & mut text ) . unwrap ( ) ;
assert_eq! (
strip_ansi_codes ( & text ) ,
r #" executable
2024-12-16 09:37:39 -05:00
├ ─ ─ a /* (14B)
├ ─ ─ b / a . txt ( 0 B )
└ ─ ┬ c ( 8 B )
├ ─ ─ a . txt ( 8 B )
2024-12-11 09:40:50 -05:00
└ ─ ─ b . txt - -> c / a . txt
" #
) ;
}
2023-05-10 20:06:59 -04:00
}