| /* | |
| * Copyright (c) 2005, 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 | |
| * 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. | |
| */ | |
| /** | |
| * @test | |
| * @bug 4057701 6286712 6364377 8181919 8349092 | |
| * @requires (os.family == "linux" | os.family == "mac" | | |
| * os.family == "windows") | |
| * @summary Basic functionality of File.get-X-Space methods. | |
| * @library .. /test/lib | |
| * @build jdk.test.lib.Platform | |
| * @run main/othervm/native GetXSpace | |
| */ | |
| import java.io.BufferedReader; | |
| import java.io.File; | |
| import java.io.InputStreamReader; | |
| import java.io.IOException; | |
| import java.nio.file.Files; | |
| import java.nio.file.FileStore; | |
| import java.nio.file.NoSuchFileException; | |
| import java.nio.file.Path; | |
| import java.util.ArrayList; | |
| import java.util.regex.Matcher; | |
| import java.util.regex.Pattern; | |
| import jdk.test.lib.Platform; | |
| import jdk.test.lib.Platform; | |
| import static java.lang.System.err; | |
| import static java.lang.System.out; | |
| public class GetXSpace { | |
| static { | |
| System.loadLibrary("GetXSpace"); | |
| } | |
| private static final Pattern DF_PATTERN = Pattern.compile("([^\\s]+)\\s+(\\d+)\\s+(\\d+)\\s+(\\d+)\\s+\\d+%\\s+([^\\s].*)\n"); | |
| private static int fail = 0; | |
| private static int pass = 0; | |
| private static Throwable first; | |
| static void reset() { | |
| fail = 0; | |
| pass = 0; | |
| first = null; | |
| } | |
| static void pass() { | |
| pass++; | |
| } | |
| static void fail(String p) { | |
| setFirst(p); | |
| System.err.format("FAILED: %s%n", p); | |
| fail++; | |
| } | |
| static void fail(String p, long exp, String cmp, long got) { | |
| String s = String.format("'%s': %d %s %d", p, exp, cmp, got); | |
| setFirst(s); | |
| System.err.format("FAILED: %s%n", s); | |
| fail++; | |
| } | |
| private static void fail(String p, Class ex) { | |
| String s = String.format("'%s': expected %s - FAILED%n", p, ex.getName()); | |
| setFirst(s); | |
| System.err.format("FAILED: %s%n", s); | |
| fail++; | |
| } | |
| private static void setFirst(String s) { | |
| if (first == null) { | |
| first = new RuntimeException(s); | |
| } | |
| } | |
| private static class Space { | |
| private final String name; | |
| private final long size; | |
| private final long total; | |
| private final long free; | |
| private final long available; | |
| Space(String name) { | |
| this.name = name; | |
| long[] sizes = new long[4]; | |
| if (Platform.isWindows() & isCDDrive(name)) { | |
| try { | |
| getCDDriveSpace(name, sizes); | |
| } catch (IOException e) { | |
| e.printStackTrace(); | |
| throw new RuntimeException("can't get CDDrive sizes"); | |
| } | |
| } else { | |
| if (getSpace0(name, sizes)) | |
| System.err.println("WARNING: total space is estimated"); | |
| } | |
| this.size = sizes[0]; | |
| this.total = sizes[1]; | |
| this.free = sizes[2]; | |
| this.available = sizes[3]; | |
| } | |
| String name() { return name; } | |
| long size() { return size; } | |
| long total() { return total; } | |
| long available() { return available; } | |
| long free() { return free; } | |
| boolean woomFree(long freeSpace) { | |
| return ((freeSpace >= (available / 10)) && | |
| (freeSpace <= (available * 10))); | |
| } | |
| public String toString() { | |
| return String.format("%s (%d/%d/%d)", name, total, free, available); | |
| } | |
| } | |
| private static void diskFree() throws IOException { | |
| ArrayList<Space> al = new ArrayList<>(); | |
| String cmd = "fsutil volume diskFree C:\\"; | |
| StringBuilder sb = new StringBuilder(); | |
| Process p = Runtime.getRuntime().exec(cmd); | |
| try (BufferedReader in = p.inputReader()) { | |
| String s; | |
| int i = 0; | |
| while ((s = in.readLine()) != null) { | |
| // skip header | |
| if (i++ == 0) continue; | |
| sb.append(s).append("\n"); | |
| } | |
| } | |
| out.println(sb); | |
| } | |
| private static ArrayList<String> paths() throws IOException { | |
| ArrayList<String> al = new ArrayList<>(); | |
| File[] roots = File.listRoots(); | |
| long[] space = new long[4]; | |
| for (File root : roots) { | |
| String path = root.toString(); | |
| al.add(path); | |
| } | |
| return al; | |
| } | |
| private static void compare(Space s) { | |
| File f = new File(s.name()); | |
| long ts = f.getTotalSpace(); | |
| long fs = f.getFreeSpace(); | |
| long us = f.getUsableSpace(); | |
| // Verify inequalities us <= fs <= ts (JDK-8349092) | |
| if (fs > ts) | |
| throw new RuntimeException(f + " free space " + fs + " > total space " + ts); | |
| if (us > fs) | |
| throw new RuntimeException(f + " usable space " + us + " > free space " + fs); | |
| out.format("%s (%d):%n", s.name(), s.size()); | |
| String fmt = " %-4s total = %12d free = %12d usable = %12d%n"; | |
| String method = Platform.isWindows() & isCDDrive(s.name()) ? "getCDDriveSpace" : "getSpace0"; | |
| out.format(fmt, method, s.total(), s.free(), s.available()); | |
| out.format(fmt, "getXSpace", ts, fs, us); | |
| // If the file system can dynamically change size, this check will fail. | |
| // This can happen on macOS for the /dev files system. | |
| if (ts != s.total() | |
| && (!Platform.isOSX() || !s.name().equals("/dev"))) { | |
| long blockSize = 1; | |
| long numBlocks = 0; | |
| try { | |
| FileStore fileStore = Files.getFileStore(f.toPath()); | |
| blockSize = fileStore.getBlockSize(); | |
| numBlocks = fileStore.getTotalSpace()/blockSize; | |
| } catch (NoSuchFileException nsfe) { | |
| // On Linux, ignore the NSFE if the path is one of the | |
| // /run/user/$UID mounts created by pam_systemd(8) as it | |
| // might be deleted during the test | |
| if (!Platform.isLinux() || !s.name().contains("/run/user")) | |
| throw new RuntimeException(nsfe); | |
| } catch (IOException e) { | |
| throw new RuntimeException(e); | |
| } | |
| if (Platform.isWindows()) { | |
| if (ts > s.total()) { | |
| fail(s.name() + " total space", ts, ">", s.total()); | |
| } | |
| } else if (ts != s.total()) { | |
| fail(s.name() + " total space", ts, "!=", s.total()); | |
| } | |
| } else { | |
| pass(); | |
| } | |
| // unix usable space is from statvfs.f_bavail | |
| long tsp = (!Platform.isWindows() ? us : fs); | |
| if (!s.woomFree(tsp)) { | |
| fail(s.name(), s.available(), "??", tsp); | |
| } else { | |
| pass(); | |
| } | |
| // | |
| // Invariants are: | |
| // total space <= size | |
| // total space == size (Unix) | |
| // free space <= total space (if no quotas in effect) (Windows) | |
| // free space < size (if quotas in effect) (Windows) | |
| // usable space <= total space | |
| // usable space <= free space | |
| // | |
| // total space <= size | |
| if (ts > s.size()) { | |
| fail(s.name() + " size", ts, ">", s.size()); | |
| } else { | |
| pass(); | |
| } | |
| // On Unix the total space should always be the volume size | |
| if (Platform.isWindows()) { | |
| // ts != s.size() indicates that quotas are in effect | |
| if (ts == s.size() && fs > s.total()) { | |
| fail(s.name() + " free space", fs, ">", s.total()); | |
| } else if (ts < s.size() && fs > s.size()) { | |
| fail(s.name() + " free space (quota)", fs, ">", s.size()); | |
| } else { | |
| pass(); | |
| } | |
| } else { // not Windows | |
| if (ts != s.size()) { | |
| fail(s.name() + " total space", ts, "!=", s.size()); | |
| } else { | |
| pass(); | |
| } | |
| } | |
| // usable space <= total space | |
| if (us > s.total()) { | |
| fail(s.name() + " usable space", us, ">", s.total()); | |
| } else { | |
| pass(); | |
| } | |
| // usable space <= free space | |
| if (us > s.free()) { | |
| // free and usable change dynamically | |
| System.err.println("Warning: us > s.free()"); | |
| if (1.0 - Math.abs((double)s.free()/(double)us) > 0.01) { | |
| fail(s.name() + " usable vs. free space", us, ">", s.free()); | |
| } else { | |
| pass(); | |
| } | |
| } else { | |
| pass(); | |
| } | |
| } | |
| private static String FILE_PREFIX = "/getSpace."; | |
| private static void compareZeroNonExist() { | |
| File f; | |
| while (true) { | |
| f = new File(FILE_PREFIX + Math.random()); | |
| if (f.exists()) { | |
| continue; | |
| } | |
| break; | |
| } | |
| long [] s = { f.getTotalSpace(), f.getFreeSpace(), f.getUsableSpace() }; | |
| for (int i = 0; i < s.length; i++) { | |
| if (s[i] != 0L) { | |
| fail(f.getName(), s[i], "!=", 0L); | |
| } else { | |
| pass(); | |
| } | |
| } | |
| } | |
| private static void compareZeroExist() { | |
| try { | |
| File f = File.createTempFile("tmp", null, new File(".")); | |
| long [] s = { f.getTotalSpace(), f.getFreeSpace(), f.getUsableSpace() }; | |
| for (int i = 0; i < s.length; i++) { | |
| if (s[i] == 0L) { | |
| fail(f.getName(), s[i], "==", 0L); | |
| } else { | |
| pass(); | |
| } | |
| } | |
| } catch (IOException x) { | |
| x.printStackTrace(); | |
| fail("Couldn't create temp file for test"); | |
| } | |
| } | |
| private static int testFile(Path dir) { | |
| String dirName = dir.toString(); | |
| out.format("--- Testing %s%n", dirName); | |
| compare(new Space(dir.getRoot().toString())); | |
| if (fail != 0) { | |
| err.format("%d tests: %d failure(s); first: %s%n", | |
| fail + pass, fail, first); | |
| } else { | |
| out.format("all %d tests passed%n", fail + pass); | |
| } | |
| return fail != 0 ? 1 : 0; | |
| } | |
| private static int testVolumes() { | |
| out.println("--- Testing volumes"); | |
| // Find all of the partitions on the machine and verify that the sizes | |
| // returned by File::getXSpace are equivalent to those from getSpace0 or getCDDriveSpace | |
| ArrayList<String> l; | |
| try { | |
| l = paths(); | |
| if (Platform.isWindows()) { | |
| diskFree(); | |
| } | |
| } catch (IOException x) { | |
| throw new RuntimeException("can't get file system information", x); | |
| } | |
| if (l.size() == 0) | |
| throw new RuntimeException("no partitions?"); | |
| for (var p : l) { | |
| Space s = new Space(p); | |
| compare(s); | |
| compareZeroNonExist(); | |
| compareZeroExist(); | |
| } | |
| if (fail != 0) { | |
| err.format("%d tests: %d failure(s); first: %s%n", | |
| fail + pass, fail, first); | |
| } else { | |
| out.format("all %d tests passed%n", fail + pass); | |
| } | |
| return fail != 0 ? 1 : 0; | |
| } | |
| private static void perms(File file, boolean allow) throws IOException { | |
| file.setExecutable(allow, false); | |
| file.setReadable(allow, false); | |
| file.setWritable(allow, false); | |
| } | |
| private static void deny(Path path) throws IOException { | |
| perms(path.toFile(), false); | |
| } | |
| private static void allow(Path path) throws IOException { | |
| perms(path.toFile(), true); | |
| } | |
| public static void main(String[] args) throws Exception { | |
| int failedTests = testVolumes(); | |
| reset(); | |
| Path tmpDir = Files.createTempDirectory(null); | |
| Path tmpSubdir = Files.createTempDirectory(tmpDir, null); | |
| Path tmpFile = Files.createTempFile(tmpSubdir, "foo", null); | |
| deny(tmpSubdir); | |
| failedTests += testFile(tmpFile); | |
| allow(tmpSubdir); | |
| Files.delete(tmpFile); | |
| Files.delete(tmpSubdir); | |
| Files.delete(tmpDir); | |
| if (failedTests > 0) { | |
| throw new RuntimeException(failedTests + " test(s) failed"); | |
| } | |
| } | |
| // | |
| // root the root of the volume | |
| // size[0] total size: number of bytes in the volume | |
| // size[1] total space: number of bytes visible to the caller | |
| // size[2] free space: number of free bytes in the volume | |
| // size[3] usable space: number of bytes available to the caller | |
| // | |
| private static native boolean getSpace0(String root, long[] space); | |
| private static native boolean isCDDrive(String root); | |
| private static void getCDDriveSpace(String root, long[] sizes) | |
| throws IOException { | |
| String[] cmd = new String[] {"df", "-k", "-P", root}; | |
| Process p = Runtime.getRuntime().exec(cmd); | |
| StringBuilder sb = new StringBuilder(); | |
| try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()))) { | |
| String s; | |
| int i = 0; | |
| while ((s = in.readLine()) != null) { | |
| // skip header | |
| if (i++ == 0) continue; | |
| sb.append(s).append("\n"); | |
| } | |
| } | |
| out.println(sb); | |
| Matcher m = DF_PATTERN.matcher(sb); | |
| int j = 0; | |
| while (j < sb.length()) { | |
| if (m.find(j)) { | |
| sizes[0] = Long.parseLong(m.group(2)) * 1024; | |
| sizes[1] = Long.parseLong(m.group(3)) * 1024; | |
| sizes[2] = sizes[0] - sizes[1]; | |
| sizes[3] = Long.parseLong(m.group(4)) * 1024; | |
| j = m.end(); | |
| } else { | |
| throw new RuntimeException("unrecognized df output format: " | |
| + "charAt(" + j + ") = '" | |
| + sb.charAt(j) + "'"); | |
| } | |
| } | |
| } | |
| } |