8232092: (fs) Files::isWritable returns false on a writeable root directory (win)
Reviewed-by: alanb
This commit is contained in:
parent
5a9bd41e79
commit
abe2593772
src/java.base/windows/classes/sun/nio/fs
test/jdk/java/nio/file/Files
@ -82,7 +82,12 @@ class WindowsFileStore
|
||||
try {
|
||||
return createFromPath(target);
|
||||
} catch (WindowsException e) {
|
||||
if (e.lastError() != ERROR_DIR_NOT_ROOT)
|
||||
// GetVolumePathName might return the following error codes
|
||||
// when the drives were created using `subst`.
|
||||
// Try expanding the path again in such cases.
|
||||
if (e.lastError() != ERROR_DIR_NOT_ROOT &&
|
||||
e.lastError() != ERROR_INVALID_PARAMETER &&
|
||||
e.lastError() != ERROR_DIRECTORY)
|
||||
throw e;
|
||||
target = WindowsLinkSupport.getFinalPath(file);
|
||||
if (target == null)
|
||||
|
477
test/jdk/java/nio/file/Files/SubstDrive.java
Normal file
477
test/jdk/java/nio/file/Files/SubstDrive.java
Normal file
@ -0,0 +1,477 @@
|
||||
/*
|
||||
* Copyright (c) 2020 Microsoft Corporation. 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
|
||||
* under the terms of the GNU General Public License version 2 only, as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* This code is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||||
* version 2 for more details (a copy is included in the LICENSE file that
|
||||
* accompanied this code).
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License version
|
||||
* 2 along with this work; if not, write to the Free Software Foundation,
|
||||
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
|
||||
* or visit www.oracle.com if you need additional information or have any
|
||||
* questions.
|
||||
*
|
||||
*/
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Stream;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.*;
|
||||
|
||||
import static org.testng.Assert.*;
|
||||
import org.testng.annotations.BeforeClass;
|
||||
import org.testng.annotations.AfterClass;
|
||||
import org.testng.annotations.AfterMethod;
|
||||
import org.testng.annotations.Test;
|
||||
import org.testng.SkipException;
|
||||
|
||||
import jdk.test.lib.process.ProcessTools;
|
||||
import jdk.test.lib.process.OutputAnalyzer;
|
||||
|
||||
/* @test
|
||||
* @summary Test Files' public APIs with drives created using the subst command on Windows.
|
||||
* @requires (os.family == "windows")
|
||||
* @library /test/lib ..
|
||||
* @build SubstDrive
|
||||
* @run testng SubstDrive
|
||||
*/
|
||||
public class SubstDrive {
|
||||
|
||||
private static Path SUBST_DRIVE;
|
||||
private static Path TEST_TEMP_DIRECTORY;
|
||||
|
||||
/**
|
||||
* Setup for the test:
|
||||
* + Create a temporary directory where all subsequently created temp
|
||||
* directories will be in. This directory and all of its contents will be
|
||||
* deleted when the test finishes.
|
||||
* + Find a drive that is available for use with subst.
|
||||
*/
|
||||
@BeforeClass
|
||||
public void setup() throws IOException {
|
||||
TEST_TEMP_DIRECTORY = Files.createTempDirectory("tmp");
|
||||
System.out.printf("Test directory is at %s\n", TEST_TEMP_DIRECTORY);
|
||||
|
||||
Optional<Path> substDrive = findAvailableDrive(TEST_TEMP_DIRECTORY);
|
||||
if (!substDrive.isPresent()) {
|
||||
throw new SkipException(
|
||||
"Could not find any available drive to use with subst, skipping the tests");
|
||||
}
|
||||
SUBST_DRIVE = substDrive.get();
|
||||
System.out.printf("Using drive %s\n with subst", SUBST_DRIVE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the root temporary directory together with all of its contents
|
||||
* when all tests finish.
|
||||
*/
|
||||
@AfterClass
|
||||
public void removeRootTempDirectory() throws IOException {
|
||||
TestUtil.removeAll(TEST_TEMP_DIRECTORY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Each test method maps drive `SUBST_DRIVE` to a temporary directory,
|
||||
* unmap the drive after every test so that subsequent ones can reuse
|
||||
* the drive.
|
||||
*/
|
||||
@AfterMethod
|
||||
public void deleteSubstDrive() throws IOException {
|
||||
Stream<String> substitutedDrives = substFindMappedDrives();
|
||||
// Only delete `SUBST_DRIVE` if it is currently being substituted
|
||||
if (substitutedDrives.anyMatch(e -> e.contains(SUBST_DRIVE.toString()))) {
|
||||
substDelete(SUBST_DRIVE);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test whether files can be created in the substituted drive.
|
||||
*/
|
||||
@Test
|
||||
public void testCreateAndDeleteFile() throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
String fileContents = "Hello world!";
|
||||
Path p = Path.of(SUBST_DRIVE.toString(), "testFile.txt");
|
||||
Files.createFile(p);
|
||||
|
||||
assertTrue(Files.exists(p));
|
||||
|
||||
Files.writeString(p, fileContents);
|
||||
assertEquals(Files.readString(p), fileContents);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if we can delete the substituted drive (essentially just a directory).
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteSubstitutedDrive() throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
assertTrue(Files.exists(tempDirectory));
|
||||
Files.delete(SUBST_DRIVE);
|
||||
assertTrue(Files.notExists(tempDirectory));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the attributes returned by the Files' APIs are consistent when
|
||||
* using the actual path and the substituted path.
|
||||
*/
|
||||
@Test
|
||||
public void testAttributes() throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
assertTrue(Files.isSameFile(tempDirectory, SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isExecutable(tempDirectory),
|
||||
Files.isExecutable(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isReadable(tempDirectory),
|
||||
Files.isReadable(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isDirectory(tempDirectory),
|
||||
Files.isDirectory(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isHidden(tempDirectory),
|
||||
Files.isHidden(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isRegularFile(tempDirectory),
|
||||
Files.isRegularFile(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isSymbolicLink(tempDirectory),
|
||||
Files.isSymbolicLink(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.getOwner(tempDirectory),
|
||||
Files.getOwner(SUBST_DRIVE));
|
||||
|
||||
assertEquals(
|
||||
Files.isWritable(tempDirectory),
|
||||
Files.isWritable(SUBST_DRIVE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if setting attributes for a substituted path works the same way
|
||||
* as it would for a real path.
|
||||
*/
|
||||
@Test
|
||||
public void testGetSetAttributes() throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
Files.setAttribute(SUBST_DRIVE, "dos:hidden", true);
|
||||
assertTrue(Files.isHidden(SUBST_DRIVE));
|
||||
assertTrue(Files.isHidden(tempDirectory));
|
||||
|
||||
Files.setAttribute(tempDirectory, "dos:hidden", false);
|
||||
assertFalse(Files.isHidden(SUBST_DRIVE));
|
||||
assertFalse(Files.isHidden(tempDirectory));
|
||||
|
||||
Map<String, Object> attr1 = Files.readAttributes(SUBST_DRIVE, "*");
|
||||
Map<String, Object> attr2 = Files.readAttributes(tempDirectory, "*");
|
||||
assertEquals(attr1, attr2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the FileStores returned from using substituted path and real path
|
||||
* are the same.
|
||||
*/
|
||||
@Test
|
||||
public void testFileStore() throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
FileStore fileStore1 = Files.getFileStore(tempDirectory);
|
||||
FileStore fileStore2 = Files.getFileStore(SUBST_DRIVE);
|
||||
|
||||
assertEquals(
|
||||
fileStore1.getTotalSpace(),
|
||||
fileStore2.getTotalSpace());
|
||||
|
||||
assertEquals(
|
||||
fileStore1.getBlockSize(),
|
||||
fileStore2.getBlockSize());
|
||||
|
||||
assertEquals(
|
||||
fileStore1.getUnallocatedSpace(),
|
||||
fileStore2.getUnallocatedSpace());
|
||||
|
||||
assertEquals(
|
||||
fileStore1.getUsableSpace(),
|
||||
fileStore2.getUsableSpace());
|
||||
|
||||
assertEquals(
|
||||
fileStore1.name(),
|
||||
fileStore2.name());
|
||||
|
||||
assertEquals(
|
||||
fileStore1.type(),
|
||||
fileStore2.type());
|
||||
|
||||
assertEquals(
|
||||
SUBST_DRIVE.getFileSystem().getRootDirectories(),
|
||||
tempDirectory.getFileSystem().getRootDirectories());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if Files.copy works correctly on a substituted drive, and that
|
||||
* all of the attributes are the same.
|
||||
*/
|
||||
@Test
|
||||
public void testMoveAndCopySubstDrive() throws IOException {
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
Path tempDirectoryCopy = Path.of(tempDirectory.toString() + "_copy");
|
||||
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
Files.copy(SUBST_DRIVE, tempDirectoryCopy);
|
||||
|
||||
assertEquals(
|
||||
Files.isExecutable(SUBST_DRIVE),
|
||||
Files.isExecutable(tempDirectoryCopy));
|
||||
|
||||
assertEquals(
|
||||
Files.isReadable(SUBST_DRIVE),
|
||||
Files.isReadable(tempDirectoryCopy));
|
||||
|
||||
assertEquals(
|
||||
Files.isDirectory(SUBST_DRIVE),
|
||||
Files.isDirectory(tempDirectoryCopy));
|
||||
|
||||
assertEquals(
|
||||
Files.isHidden(SUBST_DRIVE),
|
||||
Files.isHidden(tempDirectoryCopy));
|
||||
|
||||
assertEquals(
|
||||
Files.isRegularFile(SUBST_DRIVE),
|
||||
Files.isRegularFile(tempDirectoryCopy));
|
||||
|
||||
assertEquals(
|
||||
Files.isWritable(SUBST_DRIVE),
|
||||
Files.isWritable(tempDirectoryCopy));
|
||||
|
||||
assertEquals(
|
||||
Files.getOwner(SUBST_DRIVE),
|
||||
Files.getOwner(tempDirectoryCopy));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if the attributes of a resolved symlink are the same as its target's
|
||||
* Note: requires administrator privileges.
|
||||
*/
|
||||
@Test
|
||||
public void testGetResolvedSymlinkAttribute() throws IOException {
|
||||
if (!TestUtil.supportsLinks(TEST_TEMP_DIRECTORY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
substCreate(SUBST_DRIVE, tempDirectory);
|
||||
|
||||
Path tempFile = Path.of(SUBST_DRIVE.toString(), "test.txt");
|
||||
String contents = "Hello world!";
|
||||
Files.writeString(tempFile, contents);
|
||||
assertEquals(Files.readString(tempFile), contents);
|
||||
|
||||
Path link = Path.of(SUBST_DRIVE.toString(), "link");
|
||||
Files.createSymbolicLink(link, tempFile);
|
||||
|
||||
assertEquals(Files.readString(link), contents);
|
||||
assertEquals(Files.isExecutable(link), Files.isExecutable(tempFile));
|
||||
assertEquals(Files.isReadable(link), Files.isReadable(tempFile));
|
||||
assertEquals(Files.isDirectory(link), Files.isDirectory(tempFile));
|
||||
assertEquals(Files.isHidden(link), Files.isHidden(tempFile));
|
||||
assertEquals(Files.isRegularFile(link), Files.isRegularFile(tempFile));
|
||||
assertEquals(Files.isWritable(link), Files.isWritable(tempFile));
|
||||
assertEquals(Files.getOwner(link), Files.getOwner(tempFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if files and directories can be created, moved, and cut when the
|
||||
* substituted drive is a symlink.
|
||||
* Note: requires administrator privileges.
|
||||
*/
|
||||
@Test
|
||||
public void testSubstWithSymlinkedDirectory() throws IOException {
|
||||
if (!TestUtil.supportsLinks(TEST_TEMP_DIRECTORY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
Path tempLink = Path.of(tempDirectory.toString() + "_link");
|
||||
Files.createSymbolicLink(tempLink, tempDirectory);
|
||||
|
||||
substCreate(SUBST_DRIVE, tempLink);
|
||||
|
||||
assertEquals(
|
||||
Files.readAttributes(SUBST_DRIVE, "*"),
|
||||
Files.readAttributes(tempDirectory, "*"));
|
||||
|
||||
assertTrue(Files.isWritable(SUBST_DRIVE));
|
||||
|
||||
Path tempFile = Files.createTempFile(SUBST_DRIVE, "prefix", "suffix");
|
||||
String contents = "Hello world!";
|
||||
Files.writeString(tempFile, contents);
|
||||
assertEquals(Files.readString(tempFile), contents);
|
||||
|
||||
Path tempDirectory2 = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
Path copy = Path.of(tempDirectory2.toString(), "copied");
|
||||
Files.copy(tempFile, copy);
|
||||
|
||||
assertTrue(Files.exists(copy));
|
||||
assertEquals(Files.readString(copy), contents);
|
||||
|
||||
Path cut = Path.of(tempDirectory2.toString(), "cut");
|
||||
Files.move(tempFile, cut);
|
||||
assertTrue(Files.notExists(tempFile));
|
||||
assertTrue(Files.exists(cut));
|
||||
assertEquals(Files.readString(cut), contents);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the substituted drive is a symlink, test if it has the same
|
||||
* attributes as its target.
|
||||
* Note: requires administrator privileges.
|
||||
*/
|
||||
@Test
|
||||
public void testMoveAndCopyFilesToSymlinkedDrive() throws IOException {
|
||||
if (!TestUtil.supportsLinks(TEST_TEMP_DIRECTORY)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Path tempDirectory = Files.createTempDirectory(TEST_TEMP_DIRECTORY, "tmp");
|
||||
Path tempLink = Path.of(tempDirectory.toString() + "_link");
|
||||
Files.createSymbolicLink(tempLink, tempDirectory);
|
||||
|
||||
substCreate(SUBST_DRIVE, tempLink);
|
||||
|
||||
assertEquals(
|
||||
Files.readAttributes(SUBST_DRIVE, "*"),
|
||||
Files.readAttributes(tempDirectory, "*"));
|
||||
|
||||
assertTrue(Files.isWritable(SUBST_DRIVE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a command and optionally prints stdout contents to
|
||||
* `customOutputStream`.
|
||||
*/
|
||||
private void runCmd(ProcessBuilder pb, PrintStream customOutputStream) {
|
||||
try {
|
||||
PrintStream ps = customOutputStream != null ?
|
||||
customOutputStream :
|
||||
System.out;
|
||||
OutputAnalyzer outputAnalyzer = ProcessTools.executeCommand(pb)
|
||||
.outputTo(ps)
|
||||
.errorTo(System.err);
|
||||
|
||||
int exitCode = outputAnalyzer.getExitValue();
|
||||
assertEquals(
|
||||
exitCode /* actual value */,
|
||||
0 /* expected value */,
|
||||
String.format(
|
||||
"Command `%s` failed with exit code %d",
|
||||
pb.command(),
|
||||
exitCode
|
||||
)
|
||||
);
|
||||
|
||||
} catch (Throwable t) {
|
||||
throw new RuntimeException(t);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to map a path to a drive letter using subst.
|
||||
* For reference, see:
|
||||
* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/subst
|
||||
*/
|
||||
private void substCreate(Path drive, Path path) {
|
||||
runCmd(
|
||||
new ProcessBuilder(
|
||||
"cmd", "/c", "subst", drive.toString(), path.toString()),
|
||||
null /* customOutputStream */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a drive mapping using subst.
|
||||
* For reference, see:
|
||||
* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/subst
|
||||
*/
|
||||
private void substDelete(Path drive) throws IOException {
|
||||
runCmd(
|
||||
new ProcessBuilder(
|
||||
"cmd", "/c", "subst", drive.toString(), "/D"),
|
||||
null /* customOutputStream */);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a list of strings that represents all the currently mapped drives.
|
||||
* For instance, with the following output of subst:
|
||||
* A:\: => path1
|
||||
* B:\: => path2
|
||||
* T:\: => path3
|
||||
* X:\: => path4
|
||||
* The function returns: ["A:\", "B:\", "T:\", "X:\"]
|
||||
* For reference, see:
|
||||
* https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/subst
|
||||
*/
|
||||
private Stream<String> substFindMappedDrives() throws UnsupportedEncodingException {
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
String utf8 = StandardCharsets.UTF_8.name();
|
||||
try (PrintStream ps = new PrintStream(baos, true, utf8)) {
|
||||
// subst without any arguments returns a list of drives that
|
||||
// are being substituted
|
||||
runCmd(new ProcessBuilder("cmd", "/c", "subst"), ps);
|
||||
String stdout = baos.toString(utf8);
|
||||
return stdout
|
||||
// split lines
|
||||
.lines()
|
||||
// only examine lines with "=>"
|
||||
.filter(line -> line.contains("=>"))
|
||||
// split each line into 2 components and take the first one
|
||||
.map(line -> line.split("=>")[0].trim());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* subst can fail if the drive to be mapped already exists. The method returns
|
||||
* a drive that is available.
|
||||
*/
|
||||
private Optional<Path> findAvailableDrive(Path tempDirectory) {
|
||||
for (char letter = 'Z'; letter >= 'A'; letter--) {
|
||||
try {
|
||||
Path p = Path.of(letter + ":");
|
||||
substCreate(p, tempDirectory);
|
||||
substDelete(p);
|
||||
return Optional.of(p);
|
||||
} catch (Throwable t) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user