5555import java .lang .reflect .Modifier ;
5656import java .net .URI ;
5757import java .nio .ByteBuffer ;
58+ import java .nio .channels .NonWritableChannelException ;
5859import java .nio .channels .SeekableByteChannel ;
5960import java .nio .file .AccessMode ;
6061import java .nio .file .DirectoryStream ;
6162import java .nio .file .Files ;
63+ import java .nio .file .LinkOption ;
6264import java .nio .file .NoSuchFileException ;
6365import java .nio .file .NotDirectoryException ;
66+ import java .nio .file .NotLinkException ;
6467import java .nio .file .OpenOption ;
6568import java .nio .file .Path ;
6669import java .nio .file .StandardCopyOption ;
8588import static org .graalvm .python .embedding .utils .VirtualFileSystem .HostIO .READ_WRITE ;
8689import static org .junit .Assert .assertEquals ;
8790import static org .junit .Assert .assertFalse ;
91+ import static org .junit .Assert .assertNotEquals ;
8892import static org .junit .Assert .assertTrue ;
8993import static org .junit .Assert .fail ;
9094
@@ -175,8 +179,10 @@ public void toRealPath() throws Exception {
175179 private static void toRealPathVFS (FileSystem fs , String pathPrefix ) throws IOException {
176180 assertEquals (Path .of (VFS_MOUNT_POINT , "dir1" ), fs .toRealPath (Path .of (pathPrefix , "dir1" )));
177181 assertEquals (Path .of (VFS_MOUNT_POINT , "SomeFile" ), fs .toRealPath (Path .of (pathPrefix , "SomeFile" )));
178- assertEquals (Path .of (VFS_MOUNT_POINT , "does-not-exist" , "extractme" ), fs .toRealPath (Path .of (pathPrefix , "does-not-exist" , "extractme" )));
182+ assertEquals (Path .of (VFS_MOUNT_POINT , "does-not-exist" ), fs .toRealPath (Path .of (pathPrefix , "does-not-exist" )));
183+ assertEquals (Path .of (VFS_MOUNT_POINT , "extractme" ), fs .toRealPath (Path .of (pathPrefix , "extractme" ), LinkOption .NOFOLLOW_LINKS ));
179184 checkExtractedFile (fs .toRealPath (Path .of (pathPrefix , "extractme" )), new String []{"text1" , "text2" });
185+ checkException (NoSuchFileException .class , () -> fs .toRealPath (Path .of (pathPrefix , "does-not-exist" , "extractme" )));
180186 }
181187
182188 @ Test
@@ -383,10 +389,23 @@ private static void checkAccessVFS(FileSystem fs, String pathPrefix) throws IOEx
383389 fs .checkAccess (Path .of (pathPrefix , "dir1" ), Set .of (AccessMode .READ ));
384390 // check regular resource file
385391 fs .checkAccess (Path .of (pathPrefix , "SomeFile" ), Set .of (AccessMode .READ ));
392+
386393 // check to be extracted file
394+ fs .checkAccess (Path .of (pathPrefix , "extractme" ), Set .of (AccessMode .READ ), LinkOption .NOFOLLOW_LINKS );
395+ checkException (SecurityException .class , () -> fs .checkAccess (Path .of (pathPrefix , "extractme" ), Set .of (AccessMode .WRITE ), LinkOption .NOFOLLOW_LINKS ));
387396 fs .checkAccess (Path .of (pathPrefix , "extractme" ), Set .of (AccessMode .READ ));
397+ // even though extracted -> FS is read-only and we are limiting the access to read-only also
398+ // for extracted files
399+ checkException (IOException .class , () -> fs .checkAccess (Path .of (pathPrefix , "extractme" ), Set .of (AccessMode .WRITE )));
400+
401+ checkException (NoSuchFileException .class , () -> fs .checkAccess (Path .of (pathPrefix , "does-not-exits" , "extractme" ), Set .of (AccessMode .READ ), LinkOption .NOFOLLOW_LINKS ));
402+ checkException (NoSuchFileException .class , () -> fs .checkAccess (Path .of (pathPrefix , "does-not-exits" , "extractme" ), Set .of (AccessMode .READ )));
388403
389404 checkException (SecurityException .class , () -> fs .checkAccess (Path .of (pathPrefix , "SomeFile" ), Set .of (AccessMode .WRITE )), "write access should not be possible with VFS" );
405+ checkException (SecurityException .class , () -> fs .checkAccess (Path .of (pathPrefix , "does-not-exist" ), Set .of (AccessMode .WRITE )), "execute access should not be possible with VFS" );
406+ checkException (SecurityException .class , () -> fs .checkAccess (Path .of (pathPrefix , "SomeFile" ), Set .of (AccessMode .EXECUTE )), "execute access should not be possible with VFS" );
407+ checkException (SecurityException .class , () -> fs .checkAccess (Path .of (pathPrefix , "does-not-exist" ), Set .of (AccessMode .EXECUTE )), "execute access should not be possible with VFS" );
408+
390409 checkException (NoSuchFileException .class , () -> fs .checkAccess (Path .of (pathPrefix , "does-not-exits" ), Set .of (AccessMode .READ )),
391410 "should not be able to access a file which does not exist in VFS" );
392411 checkException (NoSuchFileException .class , () -> fs .checkAccess (Path .of (pathPrefix , "does-not-exits" , "extractme" ), Set .of (AccessMode .READ )),
@@ -485,6 +504,11 @@ public void newByteChannel() throws Exception {
485504
486505 newByteChannelVFS (fs , VFS_MOUNT_POINT );
487506 withCWD (fs , VFS_ROOT_PATH , (fst ) -> newByteChannelVFS (fst , "" ));
507+
508+ checkException (NullPointerException .class , () -> fs .newByteChannel (Path .of (VFS_MOUNT_POINT , "does-not-exist" ), null ));
509+ withCWD (fs , VFS_ROOT_PATH , (fst ) -> checkException (NullPointerException .class , () -> fst .newByteChannel (Path .of ("does-not-exist" ), null )));
510+ checkException (NullPointerException .class , () -> fs .newByteChannel (Path .of (VFS_MOUNT_POINT , "does-not-exist" , "extractme" ), null ));
511+ withCWD (fs , VFS_ROOT_PATH , (fst ) -> checkException (NullPointerException .class , () -> fst .newByteChannel (Path .of ("does-not-exist" , "extractme" ), null )));
488512 }
489513
490514 // from real FS
@@ -502,25 +526,34 @@ public void newByteChannel() throws Exception {
502526 }
503527
504528 private static void newByteChannelVFS (FileSystem fs , String pathPrefix ) throws IOException {
505- Path path = Path .of (pathPrefix , "file1" );
506- for (StandardOpenOption o : StandardOpenOption .values ()) {
529+ Path file1 = Path .of (pathPrefix , "file1" );
530+ Path extractable = Path .of (pathPrefix , "extractme" );
531+ for (StandardOpenOption o : new StandardOpenOption []{StandardOpenOption .WRITE , StandardOpenOption .READ }) {
507532 if (o == StandardOpenOption .READ ) {
508- SeekableByteChannel bch = fs .newByteChannel (path , Set .of (o ));
509- ByteBuffer buffer = ByteBuffer .allocate (1024 );
510- bch .read (buffer );
511- String s = new String (buffer .array ());
512- String [] ss = s .split (System .lineSeparator ());
513- assertTrue (ss .length >= 2 );
514- assertEquals ("text1" , ss [0 ]);
515- assertEquals ("text2" , ss [1 ]);
516-
517- checkException (IOException .class , () -> bch .write (buffer ), "should not be able to write to VFS" );
518- checkException (IOException .class , () -> bch .truncate (0 ), "should not be able to write to VFS" );
533+ newByteChannelVFS (fs , file1 , Set .of (o ));
534+ newByteChannelVFS (fs , file1 , Set .of (o , LinkOption .NOFOLLOW_LINKS ));
535+ newByteChannelVFS (fs , extractable , Set .of (o ));
536+ checkException (IOException .class , () -> fs .newByteChannel (extractable , Set .of (o , LinkOption .NOFOLLOW_LINKS )));
519537 } else {
520- checkCanOnlyRead (fs , path , o );
538+ checkCanOnlyRead (fs , file1 , o );
539+ checkCanOnlyRead (fs , extractable , o );
521540 }
522541 }
523- checkCanOnlyRead (fs , path , StandardOpenOption .READ , StandardOpenOption .WRITE );
542+ checkCanOnlyRead (fs , file1 , StandardOpenOption .READ , StandardOpenOption .WRITE );
543+ checkCanOnlyRead (fs , extractable , StandardOpenOption .READ , StandardOpenOption .WRITE );
544+ }
545+
546+ private static void newByteChannelVFS (FileSystem fs , Path path , Set <OpenOption > options ) throws IOException {
547+ SeekableByteChannel bch = fs .newByteChannel (path , options );
548+ ByteBuffer buffer = ByteBuffer .allocate (1024 );
549+ bch .read (buffer );
550+ String s = new String (buffer .array ());
551+ String [] ss = s .split (System .lineSeparator ());
552+ assertTrue (ss .length >= 2 );
553+ assertEquals ("text1" , ss [0 ]);
554+ assertEquals ("text2" , ss [1 ]);
555+ checkException (NonWritableChannelException .class , () -> bch .write (buffer ), "should not be able to write to VFS" );
556+ checkException (NonWritableChannelException .class , () -> bch .truncate (0 ), "should not be able to write to VFS" );
524557 }
525558
526559 private static void newByteChannelRealFS (FileSystem fs , Path path , String expectedText ) throws IOException {
@@ -622,6 +655,19 @@ private static void readAttributesVFS(FileSystem fs, String pathPrefix) throws I
622655 Map <String , Object > attrs = fs .readAttributes (Path .of (pathPrefix , "dir1" ), "creationTime" );
623656 assertEquals (FileTime .fromMillis (0 ), attrs .get ("creationTime" ));
624657
658+ attrs = fs .readAttributes (Path .of (pathPrefix , "extractme" ), "creationTime,isSymbolicLink,isRegularFile" , LinkOption .NOFOLLOW_LINKS );
659+ assertEquals (FileTime .fromMillis (0 ), attrs .get ("creationTime" ));
660+ assertTrue ((Boolean ) attrs .get ("isSymbolicLink" ));
661+ assertFalse ((Boolean ) attrs .get ("isRegularFile" )); //
662+
663+ attrs = fs .readAttributes (Path .of (pathPrefix , "extractme" ), "creationTime,isSymbolicLink,isRegularFile" );
664+ assertNotEquals (FileTime .fromMillis (0 ), attrs .get ("creationTime" ));
665+ assertFalse ((Boolean ) attrs .get ("isSymbolicLink" ));
666+ assertTrue ((Boolean ) attrs .get ("isRegularFile" ));
667+
668+ checkException (NoSuchFileException .class , () -> fs .readAttributes (Path .of (pathPrefix , "does-not-exist" , "extractme" ), "creationTime" , LinkOption .NOFOLLOW_LINKS ));
669+ checkException (NoSuchFileException .class , () -> fs .readAttributes (Path .of (pathPrefix , "does-not-exist" , "extractme" ), "creationTime" ));
670+
625671 checkException (NoSuchFileException .class , () -> fs .readAttributes (Path .of (pathPrefix , "does-not-exist" ), "creationTime" ), "" );
626672 checkException (UnsupportedOperationException .class , () -> fs .readAttributes (Path .of (pathPrefix , "file1" ), "unix:creationTime" ), "" );
627673 }
@@ -876,7 +922,6 @@ public void createAndReadSymbolicLink() throws Exception {
876922 checkException (IOException .class , () -> fs .createSymbolicLink (VFS_ROOT_PATH .resolve ("link1" ), realFSLinkTarget ));
877923
878924 checkException (SecurityException .class , () -> fs .createSymbolicLink (VFS_ROOT_PATH , VFS_ROOT_PATH .resolve ("link" )));
879- checkException (SecurityException .class , () -> fs .readSymbolicLink (VFS_ROOT_PATH .resolve ("link1" )));
880925 }
881926 checkException (SecurityException .class , () -> rHostIOVFS .createSymbolicLink (realFSDir .resolve ("link2" ), realFSLinkTarget ));
882927 checkException (SecurityException .class , () -> noHostIOVFS .createSymbolicLink (realFSDir .resolve ("link3" ), realFSLinkTarget ));
@@ -902,6 +947,20 @@ private void checkSymlink(Path dir, Path target, Path symlink) throws Exception
902947 }
903948 }
904949
950+ @ Test
951+ public void readSymbolicLink () throws Exception {
952+ for (FileSystem fs : new FileSystem []{rwHostIOVFS , rHostIOVFS , noHostIOVFS }) {
953+ readSymbolicLink (fs , VFS_MOUNT_POINT );
954+ withCWD (fs , VFS_ROOT_PATH , (fst ) -> readSymbolicLink (fst , "" ));
955+ }
956+ }
957+
958+ private static void readSymbolicLink (FileSystem fs , String vfsPrefix ) throws IOException {
959+ checkException (NotLinkException .class , () -> fs .readSymbolicLink (Path .of (vfsPrefix , "file1" )));
960+ checkException (NoSuchFileException .class , () -> fs .readSymbolicLink (Path .of (vfsPrefix , "does-not-exist" )));
961+ checkExtractedFile (fs .readSymbolicLink (Path .of (vfsPrefix , "extractme" )), new String []{"text1" , "text2" });
962+ }
963+
905964 @ Test
906965 public void move () throws Exception {
907966 Path realFSDir = Files .createTempDirectory ("graalpy.vfs.test" );
0 commit comments