From 8667bca08f22a169b2f2e8bef1f151fee582ac55 Mon Sep 17 00:00:00 2001 From: Brian Burkhalter Date: Tue, 26 Aug 2025 13:15:52 -0700 Subject: [PATCH] 8356493: (fs) SecureDirectoryStream missing some capabilities --- .../java/nio/file/SecureDirectoryStream.java | 182 ++++++++++++++- .../sun/nio/fs/UnixFileSystemProvider.java | 2 +- .../sun/nio/fs/UnixNativeDispatcher.java | 54 +++++ .../sun/nio/fs/UnixSecureDirectoryStream.java | 153 ++++++++++++- .../native/libnio/fs/UnixNativeDispatcher.c | 69 ++++++ .../nio/file/DirectoryStream/SecureDS.java | 214 +++++++++++++++++- 6 files changed, 662 insertions(+), 12 deletions(-) diff --git a/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java b/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java index 4348c60f5e2..22fa7cd203e 100644 --- a/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java +++ b/src/java.base/share/classes/java/nio/file/SecureDirectoryStream.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2007, 2024, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2007, 2025, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -135,6 +135,182 @@ public interface SecureDirectoryStream FileAttribute... attrs) throws IOException; + /** + * Creates a new and empty file, failing if the file already exists. + * + *

This method works in a similar manner to {@linkplain Files#createFile + * Files.createFile}. If the {@code path} parameter is an {@linkplain + * Path#isAbsolute absolute} path then it locates the file to create. If + * the parameter is a relative path then it is located relative to this + * open directory. + * + *

The {@code attrs} parameter is optional with effects as specified + * for {@linkplain Files#createFile Files.createFile}. + * + * @param path + * the path of the file to create + * @param attrs + * an optional list of file attributes to set atomically when + * creating the file + * + * @return the file + * + * @throws ClosedDirectoryStreamException + * if the directory stream is closed + * @throws UnsupportedOperationException + * if the array contains an attribute that cannot be set atomically + * when creating the directory + * @throws FileAlreadyExistsException + * if a file could not otherwise be created because a file of + * that name already exists (optional specific exception) + * @throws IOException + * if an I/O error occurs or the parent directory does not exist + * + * @since 26 + */ + Path createFile(Path path, FileAttribute... attrs) + throws IOException; + + /** + * Creates a new directory, failing if a file of that name already exists. + * + *

This method works in a similar manner to {@linkplain + * Files#createDirectory Files.createDirectory}. If the {@code path} + * parameter is an {@linkplain Path#isAbsolute absolute} path then it + * locates the directory to create. If the parameter is a relative path + * then it is located relative to this open directory. + * + *

The {@code attrs} parameter is optional with effects as specified + * for {@linkplain Files#createDirectory Files.createDirectory}. + * + * @param dir + * the path of the directory to create + * @param attrs + * an optional list of file attributes to set atomically when + * creating the directory + * + * @return the directory + * + * @throws ClosedDirectoryStreamException + * if the directory stream is closed + * @throws UnsupportedOperationException + * if the array contains an attribute that cannot be set atomically + * when creating the directory + * @throws FileAlreadyExistsException + * if a directory could not otherwise be created because a file of + * that name already exists (optional specific exception) + * @throws IOException + * if an I/O error occurs or the parent directory does not exist + * + * @since 26 + */ + Path createDirectory(Path dir, FileAttribute... attrs) + throws IOException; + + /** + * Creates a new link (directory entry) for an existing file (optional + * operation). + * + *

This method works in a similar manner to {@linkplain Files#createLink + * Files.createLink}. If the {@code link} parameter is an {@link + * Path#isAbsolute absolute} path then it locates the link file. If the + * {@code link} parameter is a relative path then it is located relative to + * this open directory. If the {@code existing} parameter is an absolute + * path then it locates the target file (the {@code targetdir} parameter is + * ignored). If the {@code existing} parameter is a relative path it is + * located relative to the open directory identified by the {@code + * targetdir} parameter. + * + * @param link + * the link (directory entry) to create + * @param targetdir + * the destination directory + * @param existing + * a path to an existing file + * + * @return the path to the link (directory entry) + * + * @throws ClosedDirectoryStreamException + * if the directory stream is closed + * @throws UnsupportedOperationException + * if the implementation does not support adding an existing file + * to a directory + * @throws FileAlreadyExistsException + * if the entry could not otherwise be created because a file of + * that name already exists (optional specific exception) + * @throws NoSuchFileException + * if the file specified by the combination of {@code targetdir} + * and {@code existing} does not exist + * @throws IOException + * if an I/O error occurs + * + * @since 26 + */ + Path createLink(T link, SecureDirectoryStream targetdir, T existing) + throws IOException; + + /** + * Creates a symbolic link to a target (optional operation). + * + *

This method works in a similar manner to {@linkplain Files#createSymbolicLink + * Files.createSymbolicLink}. If the {@code link} parameter is an {@link + * Path#isAbsolute absolute} path then it locates the link file. If the + * {@code link} parameter is a relative path then it is located relative to + * this open directory. The {@code target} parameter is the target of the + * link and behaves as specified for {@linkplain Files#createSymbolicLink + * Files.createSymbolicLink}. + * + * @param link + * the path of the symbolic link to create + * @param target + * the target of the symbolic link + * @param attrs + * the array of attributes to set atomically when creating the + * symbolic link + * + * @return the path to the symbolic link + * + * @throws ClosedDirectoryStreamException + * if the directory stream is closed + * @throws UnsupportedOperationException + * if the implementation does not support symbolic links or the + * array contains an attribute that cannot be set atomically when + * creating the symbolic link + * @throws FileAlreadyExistsException + * if a file with the name already exists (optional specific + * exception) + * @throws IOException + * if an I/O error occurs + */ + Path createSymbolicLink(Path link, Path target, FileAttribute... attrs) + throws IOException; + + /** + * Reads the target of a symbolic link (optional operation). + * + *

This method works in a similar manner to {@linkplain Files#readSymbolicLink + * Files.readSymbolicLink}. If the {@code link} parameter is an {@link + * Path#isAbsolute absolute} path then it locates the link file. If the + * {@code link} parameter is a relative path then it is located relative to + * this open directory. + * + * @param link + * the path to the symbolic link + * + * @return a {@code Path} object representing the target of the link + * + * @throws ClosedDirectoryStreamException + * if the directory stream is closed + * @throws UnsupportedOperationException + * if the implementation does not support symbolic links + * @throws NotLinkException + * if the target could otherwise not be read because the file + * is not a symbolic link (optional specific exception) + * @throws IOException + * if an I/O error occurs + */ + Path readSymbolicLink(Path link) throws IOException; + /** * Deletes a file. * @@ -185,8 +361,8 @@ public interface SecureDirectoryStream /** * Move a file from this directory to another directory. * - *

This method works in a similar manner to {@link Files#move move} - * method when the {@link StandardCopyOption#ATOMIC_MOVE ATOMIC_MOVE} option + *

This method works in a similar manner to {@link Files#move Files.move} + * when the {@link StandardCopyOption#ATOMIC_MOVE ATOMIC_MOVE} option * is specified. That is, this method moves a file as an atomic file system * operation. If the {@code srcpath} parameter is an {@link Path#isAbsolute * absolute} path then it locates the source file. If the parameter is a diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java index a1c68934604..84bae007df6 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixFileSystemProvider.java @@ -358,10 +358,10 @@ public abstract class UnixFileSystemProvider if (attrs == null) { break; } - UnixFileKey fileKey = attrs.fileKey(); if (!attrs.isSymbolicLink()) { break; } + UnixFileKey fileKey = attrs.fileKey(); if (!fileKeys.add(fileKey)) { throw new UnixException(ELOOP); } diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java index 77dc1851478..6d97ef0f312 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixNativeDispatcher.java @@ -133,6 +133,21 @@ class UnixNativeDispatcher { private static native void link0(long existingAddress, long newAddress) throws UnixException; + /** + * linkat(int fd1, const char *name1, int fd2, const char *name2, int flag) + */ + static void linkat(int dfd1, UnixPath path1, int dfd2, UnixPath path2) + throws UnixException + { + try (NativeBuffer buffer1 = copyToNativeBuffer(path1); + NativeBuffer buffer2 = copyToNativeBuffer(path2)) { + linkat0(dfd1, buffer1.address(), dfd2, buffer2.address()); + } + } + private static native void linkat0(int dfd1, long addr1, + int dfd2, long addr2) + throws UnixException; + /** * unlink(const char* path) */ @@ -199,6 +214,19 @@ class UnixNativeDispatcher { } private static native void mkdir0(long pathAddress, int mode) throws UnixException; + /** + * mkdirat(int dfd, const char *path, mode_t mode) + */ + static void mkdirat(int dfd, UnixPath path, int mode) + throws UnixException + { + try (NativeBuffer buffer = copyToNativeBuffer(path)) { + mkdirat0(dfd, buffer.address(), mode); + } + } + private static native void mkdirat0(int dfd, long pathAddress, int mode) + throws UnixException; + /** * rmdir(const char* path) */ @@ -221,6 +249,18 @@ class UnixNativeDispatcher { } private static native byte[] readlink0(long pathAddress) throws UnixException; + /** + * readlinkat(int fd, const char* path, char* buf, size_t bufsize) + * + * @return link target + */ + static byte[] readlinkat(int fd, UnixPath path) throws UnixException { + try (NativeBuffer buffer = copyToNativeBuffer(path)) { + return readlinkat0(fd, buffer.address()); + } + } + private static native byte[] readlinkat0(int fd, long pathAddress) throws UnixException; + /** * realpath(const char* path, char* resolved_name) * @@ -245,6 +285,20 @@ class UnixNativeDispatcher { private static native void symlink0(long name1, long name2) throws UnixException; + /** + * symlinkat(const char* name1, int fd, const char* name2) + */ + static void symlinkat(byte[] name1, int fd, UnixPath name2) + throws UnixException + { + try (NativeBuffer targetBuffer = NativeBuffers.asNativeBuffer(name1); + NativeBuffer linkBuffer = copyToNativeBuffer(name2)) { + symlinkat0(targetBuffer.address(), fd, linkBuffer.address()); + } + } + private static native void symlinkat0(long name1, int dfd, long name2) + throws UnixException; + /** * stat(const char* path, struct stat* buf) */ diff --git a/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java b/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java index 5c0693870e6..e047b3bb75b 100644 --- a/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java +++ b/src/java.base/unix/classes/sun/nio/fs/UnixSecureDirectoryStream.java @@ -27,11 +27,35 @@ package sun.nio.fs; import java.io.IOException; import java.nio.channels.SeekableByteChannel; -import java.nio.file.*; -import java.nio.file.attribute.*; -import java.util.*; +import java.nio.file.AtomicMoveNotSupportedException; +import java.nio.file.ClosedDirectoryStreamException; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; +import java.nio.file.LinkOption; +import java.nio.file.NotDirectoryException; +import java.nio.file.NotLinkException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.ProviderMismatchException; +import java.nio.file.SecureDirectoryStream; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.BasicFileAttributeView; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileOwnerAttributeView; +import java.nio.file.attribute.FileTime; +import java.nio.file.attribute.GroupPrincipal; +import java.nio.file.attribute.PosixFileAttributes; +import java.nio.file.attribute.PosixFileAttributeView; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.UserPrincipal; +import java.util.Iterator; +import java.util.Set; import java.util.concurrent.TimeUnit; +import static java.nio.file.StandardOpenOption.CREATE_NEW; +import static java.nio.file.StandardOpenOption.WRITE; import static sun.nio.fs.UnixNativeDispatcher.*; import static sun.nio.fs.UnixConstants.*; @@ -154,6 +178,129 @@ class UnixSecureDirectoryStream } } + @Override + public Path createFile(Path path, FileAttribute... attrs) + throws IOException + { + newByteChannel(path, Set.of(CREATE_NEW, WRITE), attrs).close(); + + return path; + } + + @Override + public Path createDirectory(Path dir, FileAttribute... attrs) + throws IOException + { + UnixPath file = getName(dir); + + int mode = UnixFileModeAttribute + .toUnixMode(UnixFileModeAttribute.ALL_PERMISSIONS, attrs); + + ds.readLock().lock(); + try { + if (!ds.isOpen()) + throw new ClosedDirectoryStreamException(); + try { + mkdirat(dfd, file, mode); + } catch (UnixException x) { + if (x.errno() == EISDIR) + throw new FileAlreadyExistsException(file.toString()); + x.rethrowAsIOException(file); + return null; // keep compiler happy + } + } finally { + ds.readLock().unlock(); + } + + return dir; + } + + @Override + public Path createLink(Path link, SecureDirectoryStream dir, + Path target) + throws IOException + { + UnixPath linkpath = UnixPath.toUnixPath(link); + UnixPath targetpath = UnixPath.toUnixPath(target); + + ds.readLock().lock(); + try { + if (!ds.isOpen()) + throw new ClosedDirectoryStreamException(); + try { + UnixSecureDirectoryStream that = (UnixSecureDirectoryStream)dir; + linkat(that.dfd, targetpath, this.dfd, linkpath); + } catch (UnixException x) { + x.rethrowAsIOException(linkpath, targetpath); + return null; // keep compiler happy + } + } finally { + ds.readLock().unlock(); + } + + return link; + } + + @Override + public Path createSymbolicLink(Path link, Path target, + FileAttribute... attrs) + throws IOException + { + UnixPath linkpath = UnixPath.toUnixPath(link); + UnixPath targetpath = UnixPath.toUnixPath(target); + + // no attributes supported when creating links + if (attrs.length > 0) { + UnixFileModeAttribute.toUnixMode(0, attrs); // may throw NPE or UOE + throw new UnsupportedOperationException("Initial file attributes" + + " not supported when creating symbolic link"); + } + + ds.readLock().lock(); + try { + if (!ds.isOpen()) + throw new ClosedDirectoryStreamException(); + try { + symlinkat(targetpath.asByteArray(), dfd, linkpath); + } catch (UnixException x) { + x.rethrowAsIOException(linkpath, targetpath); + return null; // keep compiler happy + } + } finally { + ds.readLock().unlock(); + } + + return link; + } + + @Override + public Path readSymbolicLink(Path link) throws IOException { + UnixPath linkpath = getName(link); + + ds.readLock().lock(); + try { + if (!ds.isOpen()) + throw new ClosedDirectoryStreamException(); + try { + UnixFileAttributes attrs = + UnixFileAttributes.getIfExists(linkpath, false); + if (attrs != null && !attrs.isSymbolicLink()) + throw new NotLinkException(linkpath.toString()); + } catch (UnixException x) { + x.rethrowAsIOException(linkpath); + } + try { + byte[] target = readlinkat(dfd, linkpath); + return new UnixPath(linkpath.getFileSystem(), target); + } catch (UnixException x) { + x.rethrowAsIOException(linkpath); + return null; // keep compiler happy + } + } finally { + ds.readLock().unlock(); + } + } + /** * Deletes file/directory in this directory. Works in a race-free manner * when invoked with flags. diff --git a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c index 60ccdfc45fc..17fa906f821 100644 --- a/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c +++ b/src/java.base/unix/native/libnio/fs/UnixNativeDispatcher.c @@ -989,6 +989,18 @@ Java_sun_nio_fs_UnixNativeDispatcher_mkdir0(JNIEnv* env, jclass this, } } +JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_mkdirat0(JNIEnv* env, jclass this, + jint dfd, jlong pathAddress, jint mode) +{ + const char* path = (const char*)jlong_to_ptr(pathAddress); + + /* EINTR not listed as a possible error */ + if (mkdirat(dfd, path, (mode_t)mode) == -1) { + throwUnixException(env, errno); + } +} + JNIEXPORT void JNICALL Java_sun_nio_fs_UnixNativeDispatcher_rmdir0(JNIEnv* env, jclass this, jlong pathAddress) @@ -1015,6 +1027,20 @@ Java_sun_nio_fs_UnixNativeDispatcher_link0(JNIEnv* env, jclass this, } } +JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_linkat0(JNIEnv* env, jclass this, + int dfd1, long addr1, + int dfd2, long addr2) +{ + int err; + const char* name1 = (const char*)jlong_to_ptr(addr1); + const char* name2 = (const char*)jlong_to_ptr(addr2); + + RESTARTABLE(linkat(dfd1, name1, dfd2, name2, AT_SYMLINK_FOLLOW), err); + if (err == -1) { + throwUnixException(env, errno); + } +} JNIEXPORT void JNICALL Java_sun_nio_fs_UnixNativeDispatcher_unlink0(JNIEnv* env, jclass this, @@ -1089,6 +1115,19 @@ Java_sun_nio_fs_UnixNativeDispatcher_symlink0(JNIEnv* env, jclass this, } } +JNIEXPORT void JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_symlinkat0(JNIEnv* env, jclass this, + jlong targetAddress, jint dfd, jlong linkAddress) +{ + const char* target = (const char*)jlong_to_ptr(targetAddress); + const char* link = (const char*)jlong_to_ptr(linkAddress); + + /* EINTR not listed as a possible error */ + if (symlinkat(target, dfd, link) == -1) { + throwUnixException(env, errno); + } +} + JNIEXPORT jbyteArray JNICALL Java_sun_nio_fs_UnixNativeDispatcher_readlink0(JNIEnv* env, jclass this, jlong pathAddress) @@ -1119,6 +1158,36 @@ Java_sun_nio_fs_UnixNativeDispatcher_readlink0(JNIEnv* env, jclass this, return result; } +JNIEXPORT jbyteArray JNICALL +Java_sun_nio_fs_UnixNativeDispatcher_readlinkat0(JNIEnv* env, jclass this, + jint fd, jlong pathAddress) +{ + jbyteArray result = NULL; + char target[PATH_MAX+1]; + const char* path = (const char*)jlong_to_ptr(pathAddress); + + /* EINTR not listed as a possible error */ + int n = readlinkat(fd, path, target, sizeof(target)); + if (n == -1) { + throwUnixException(env, errno); + } else { + jsize len; + if (n == sizeof(target)) { + /* Traditionally readlink(2) should not return more than */ + /* PATH_MAX bytes (no terminating null byte is appended). */ + throwUnixException(env, ENAMETOOLONG); + return NULL; + } + target[n] = '\0'; + len = (jsize)strlen(target); + result = (*env)->NewByteArray(env, len); + if (result != NULL) { + (*env)->SetByteArrayRegion(env, result, 0, len, (jbyte*)target); + } + } + return result; +} + JNIEXPORT jbyteArray JNICALL Java_sun_nio_fs_UnixNativeDispatcher_realpath0(JNIEnv* env, jclass this, jlong pathAddress) diff --git a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java index d5d4b1904ea..24f6c03e1a5 100644 --- a/test/jdk/java/nio/file/DirectoryStream/SecureDS.java +++ b/test/jdk/java/nio/file/DirectoryStream/SecureDS.java @@ -22,7 +22,7 @@ */ /* @test - * @bug 4313887 6838333 8343020 8357425 + * @bug 4313887 6838333 8343020 8357425 8356493 * @summary Unit test for java.nio.file.SecureDirectoryStream * @requires (os.family == "linux" | os.family == "mac" | os.family == "aix") * @library .. /test/lib @@ -31,21 +31,24 @@ */ import java.nio.file.*; -import static java.nio.file.Files.*; -import static java.nio.file.StandardOpenOption.*; -import static java.nio.file.LinkOption.*; import java.nio.file.attribute.*; import java.nio.channels.*; import java.io.IOException; import java.util.*; +import static java.nio.file.Files.*; +import static java.nio.file.StandardOpenOption.*; +import static java.nio.file.LinkOption.*; + import jdk.test.lib.Platform; public class SecureDS { static boolean supportsSymbolicLinks; public static void main(String[] args) throws IOException { - Path dir = TestUtil.createTemporaryDirectory(); + String cwd = System.getProperty("user.dir"); + Path dir = TestUtil.createTemporaryDirectory(cwd); + System.err.println("Top level test directory: " + dir); try { DirectoryStream stream = newDirectoryStream(dir); stream.close(); @@ -164,6 +167,141 @@ public class SecureDS { } catch (IOException x) { } } + // Test: createFile + Path somefile = Path.of("somefile"); + // - absolute - + Path absfile = dir.resolve(somefile); + stream.createFile(absfile); + assertTrue(exists(absfile)); + assertTrue(isRegularFile(absfile)); + try { + stream.createFile(absfile); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + stream.deleteFile(absfile); + // - relative - + Path relfile = dir2.resolve(somefile); + stream.createFile(relfile); + assertTrue(exists(relfile)); + assertTrue(isRegularFile(relfile)); + try { + stream.createFile(relfile); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + stream.deleteFile(relfile); + + // Test: createDirectory + Path somedir = Path.of("somedir"); + // - absolute - + Path absdir = dir.resolve(somedir); + stream.createDirectory(absdir); + assertTrue(exists(absdir)); + assertTrue(isDirectory(absdir)); + try { + stream.createDirectory(absdir); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + stream.deleteDirectory(absdir); + // - relative - + Path reldir = dir2.resolve(somedir); + stream.createDirectory(somedir); + assertTrue(exists(reldir)); + assertTrue(isDirectory(reldir)); + try { + stream.createDirectory(reldir); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + stream.deleteDirectory(reldir); + + // Test: createLink + Path somehardlink = Path.of("somehardlink"); + Path someexisting = Path.of("someexisting"); + + // - absolute - + Path abshardlink = dir.resolve(somehardlink); + Path absexisting = dir.resolve(someexisting); + createFile(absexisting); + String text1 = "I simply don't know what to say."; + writeString(absexisting, text1); + stream.createLink(abshardlink, stream, absexisting); + assertTrue(exists(abshardlink)); + assertTrue(text1.equals(readString(abshardlink))); + try { + stream.createLink(abshardlink, stream, absexisting); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + stream.deleteFile(absexisting); + try { + stream.createLink(abshardlink, stream, absexisting); + shouldNotGetHere(); + } catch (NoSuchFileException x) { + } + stream.deleteFile(abshardlink); + // - relative - + Path relhardlink = dir2.resolve(somehardlink); + Path relexisting = dir2.resolve(someexisting); + createFile(relexisting); + String text2 = "I still don't know what to say."; + writeString(relexisting, text2); + stream.createLink(somehardlink, stream, someexisting); + assertTrue(exists(relhardlink)); + assertTrue(text2.equals(readString(relhardlink))); + try { + stream.createLink(somehardlink, stream, someexisting); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + stream.deleteFile(relexisting); + try { + stream.createLink(somehardlink, stream, someexisting); + shouldNotGetHere(); + } catch (NoSuchFileException x) { + } + stream.deleteFile(relhardlink); + + if (supportsSymbolicLinks) { + // Tests: createSymbolicLink and readSymbolicLink + Path target = dir.resolve(Path.of("target")); + assertTrue(exists(createFile(target))); + Path somesymlink = Path.of("somesymlink"); + + // - absolute - + Path abssymlink = dir.resolve(somesymlink); + stream.createSymbolicLink(abssymlink, target); + assertTrue(exists(abssymlink, NOFOLLOW_LINKS)); + assertTrue(isSymbolicLink(abssymlink)); + try { + stream.createSymbolicLink(abssymlink, target); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + assertTrue(target.equals(stream.readSymbolicLink(abssymlink))); + try { + stream.readSymbolicLink(target); + shouldNotGetHere(); + } catch (NotLinkException x) { + } + delete(target); + stream.deleteFile(abssymlink); + // - relative - + Path relsymlink = dir2.resolve(somesymlink); + stream.createSymbolicLink(somesymlink, target); + assertTrue(exists(relsymlink, NOFOLLOW_LINKS)); + assertTrue(isSymbolicLink(relsymlink)); + try { + stream.createSymbolicLink(relsymlink, target); + shouldNotGetHere(); + } catch (FileAlreadyExistsException x) { + } + assertTrue(target.equals(stream.readSymbolicLink(relsymlink))); + stream.deleteFile(relsymlink); + } + // Test: delete if (supportsSymbolicLinks) { stream.deleteFile(link1Entry); @@ -346,6 +484,42 @@ public class SecureDS { stream.newDirectoryStream(null); shouldNotGetHere(); } catch (NullPointerException x) { } + try { + stream.createFile(null); + shouldNotGetHere(); + } catch (NullPointerException x) { } + try { + stream.createDirectory(null); + shouldNotGetHere(); + } catch (NullPointerException x) { } + Path link = Path.of("link"); + try { + stream.createLink(null, stream, file); + shouldNotGetHere(); + } catch (NullPointerException x) { } + try { + stream.createLink(link, null, file); + shouldNotGetHere(); + } catch (NullPointerException x) { } + try { + stream.createLink(link, stream, null); + shouldNotGetHere(); + } catch (NullPointerException x) { } + if (supportsSymbolicLinks) { + Path symlink = Path.of("symlink"); + try { + stream.createSymbolicLink(null, file); + shouldNotGetHere(); + } catch (NullPointerException x) { } + try { + stream.createSymbolicLink(symlink, null); + shouldNotGetHere(); + } catch (NullPointerException x) { } + try { + stream.readSymbolicLink(null); + shouldNotGetHere(); + } catch (NullPointerException x) { } + } try { stream.deleteFile(null); shouldNotGetHere(); @@ -372,6 +546,32 @@ public class SecureDS { stream.move(file, stream, file); shouldNotGetHere(); } catch (ClosedDirectoryStreamException x) { } + try { + stream.createFile(Path.of("nofile")); + shouldNotGetHere(); + } catch (ClosedDirectoryStreamException x) { } + try { + stream.createDirectory(Path.of("nodir")); + shouldNotGetHere(); + } catch (ClosedDirectoryStreamException x) { } + link = Path.of("nohardlink"); + Path target = Path.of("nohardtarget"); + try { + stream.createLink(link, null, target); + shouldNotGetHere(); + } catch (ClosedDirectoryStreamException x) { } + if (supportsSymbolicLinks) { + link = Path.of("nosymlink"); + target = Path.of("nosofttarget"); + try { + stream.createSymbolicLink(link, target); + shouldNotGetHere(); + } catch (ClosedDirectoryStreamException x) { } + try { + stream.readSymbolicLink(link); + shouldNotGetHere(); + } catch (ClosedDirectoryStreamException x) { } + } try { stream.deleteFile(file); shouldNotGetHere(); @@ -385,6 +585,10 @@ public class SecureDS { if (!b) throw new RuntimeException("Assertion failed"); } + static void assertFalse(boolean b) { + if (b) throw new RuntimeException("Assertion failed"); + } + static void shouldNotGetHere() { assertTrue(false); }