1 /*
   2  * Copyright (c) 2005, 2020, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 4057701 6286712 6364377
  27  * @requires (os.family == "linux" | os.family == "mac" |
  28  *            os.family == "windows")
  29  * @summary Basic functionality of File.get-X-Space methods.
  30  */
  31 
  32 import java.io.BufferedReader;
  33 import java.io.File;
  34 import java.io.FilePermission;
  35 import java.io.InputStreamReader;
  36 import java.io.IOException;
  37 import java.nio.file.Files;
  38 import java.nio.file.Path;
  39 import java.security.Permission;
  40 import java.util.ArrayList;
  41 import java.util.regex.Matcher;
  42 import java.util.regex.Pattern;
  43 
  44 import static java.lang.System.err;
  45 import static java.lang.System.out;
  46 
  47 public class GetXSpace {
  48 
  49     private static SecurityManager [] sma = { null, new Allow(), new DenyFSA(),
  50                                               new DenyRead() };
  51 
  52     private static final String OS_NAME = System.getProperty("os.name");
  53     private static final boolean IS_MAC = OS_NAME.startsWith("Mac");
  54     private static final boolean IS_WIN = OS_NAME.startsWith("Windows");
  55 
  56     // FileSystem Total Used Available Use% MountedOn
  57     private static final Pattern DF_PATTERN = Pattern.compile("([^\\s]+)\\s+(\\d+)\\s+\\d+\\s+(\\d+)\\s+\\d+%\\s+([^\\s].*)\n");
  58 
  59     private static int fail = 0;
  60     private static int pass = 0;
  61     private static Throwable first;
  62 
  63     static void reset() {
  64         fail = 0;
  65         pass = 0;
  66         first = null;
  67     }
  68 
  69     static void pass() {
  70         pass++;
  71     }
  72 
  73     static void fail(String p) {
  74         setFirst(p);
  75         System.err.format("FAILED: %s%n", p);
  76         fail++;
  77     }
  78 
  79     static void fail(String p, long exp, String cmp, long got) {
  80         String s = String.format("'%s': %d %s %d", p, exp, cmp, got);
  81         setFirst(s);
  82         System.err.format("FAILED: %s%n", s);
  83         fail++;
  84     }
  85 
  86     private static void fail(String p, Class ex) {
  87         String s = String.format("'%s': expected %s - FAILED%n", p, ex.getName());
  88         setFirst(s);
  89         System.err.format("FAILED: %s%n", s);
  90         fail++;
  91     }
  92 
  93     private static void setFirst(String s) {
  94         if (first == null) {
  95             first = new RuntimeException(s);
  96         }
  97     }
  98 
  99     private static class Space {
 100         private static final long KSIZE = 1024;
 101         private final String name;
 102         private final long total;
 103         private final long free;
 104 
 105         Space(String total, String free, String name) {
 106             try {
 107                 this.total = Long.valueOf(total) * KSIZE;
 108                 this.free = Long.valueOf(free) * KSIZE;
 109             } catch (NumberFormatException x) {
 110                 throw new RuntimeException("the regex should have caught this", x);
 111             }
 112             this.name = name;
 113         }
 114 
 115         String name() { return name; }
 116         long total() { return total; }
 117         long free() { return free; }
 118         boolean woomFree(long freeSpace) {
 119             return ((freeSpace >= (free / 10)) && (freeSpace <= (free * 10)));
 120         }
 121         public String toString() {
 122             return String.format("%s (%d/%d)", name, free, total);
 123         }
 124     }
 125 
 126     private static ArrayList<Space> space(String f) throws IOException {
 127         ArrayList<Space> al = new ArrayList<>();
 128 
 129         String cmd = "df -k -P" + (f == null ? "" : " " + f);
 130         StringBuilder sb = new StringBuilder();
 131         Process p = Runtime.getRuntime().exec(cmd);
 132         try (BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()))) {
 133             String s;
 134             int i = 0;
 135             while ((s = in.readLine()) != null) {
 136                 // skip header
 137                 if (i++ == 0) continue;
 138                 sb.append(s).append("\n");
 139             }
 140         }
 141         out.println(sb);
 142 
 143         Matcher m = DF_PATTERN.matcher(sb);
 144         int j = 0;
 145         while (j < sb.length()) {
 146             if (m.find(j)) {
 147                 // swap can change while this test is running
 148                 if (!m.group(1).equals("swap")) {
 149                     String name = f;
 150                     if (name == null) {
 151                         // cygwin's df lists windows path as FileSystem (1st group)
 152                         name = IS_WIN ? m.group(1) : m.group(4);
 153                     }
 154                     al.add(new Space(m.group(2), m.group(3), name));;
 155                 }
 156                 j = m.end() + 1;
 157             } else {
 158                 throw new RuntimeException("unrecognized df output format: "
 159                                            + "charAt(" + j + ") = '"
 160                                            + sb.charAt(j) + "'");
 161             }
 162         }
 163 
 164         if (al.size() == 0) {
 165             // df did not produce output
 166             String name = (f == null ? "" : f);
 167             al.add(new Space("0", "0", name));
 168         }
 169         return al;
 170     }
 171 
 172     private static void tryCatch(Space s) {
 173         out.format("%s:%n", s.name());
 174         File f = new File(s.name());
 175         SecurityManager sm = System.getSecurityManager();
 176         if (sm instanceof Deny) {
 177             String fmt = "  %14s: \"%s\" thrown as expected%n";
 178             try {
 179                 f.getTotalSpace();
 180                 fail(s.name(), SecurityException.class);
 181             } catch (SecurityException x) {
 182                 out.format(fmt, "getTotalSpace", x);
 183                 pass();
 184             }
 185             try {
 186                 f.getFreeSpace();
 187                 fail(s.name(), SecurityException.class);
 188             } catch (SecurityException x) {
 189                 out.format(fmt, "getFreeSpace", x);
 190                 pass();
 191             }
 192             try {
 193                 f.getUsableSpace();
 194                 fail(s.name(), SecurityException.class);
 195             } catch (SecurityException x) {
 196                 out.format(fmt, "getUsableSpace", x);
 197                 pass();
 198             }
 199         }
 200     }
 201 
 202     private static void compare(Space s) {
 203         File f = new File(s.name());
 204         long ts = f.getTotalSpace();
 205         long fs = f.getFreeSpace();
 206         long us = f.getUsableSpace();
 207 
 208         out.format("%s:%n", s.name());
 209         String fmt = "  %-4s total= %12d free = %12d usable = %12d%n";
 210         out.format(fmt, "df", s.total(), 0, s.free());
 211         out.format(fmt, "getX", ts, fs, us);
 212 
 213         // if the file system can dynamically change size, this check will fail
 214         if (ts != s.total()) {
 215             long bs = 1;
 216             try {
 217                 bs = Files.getFileStore(f.toPath()).getBlockSize();
 218             } catch (IOException e) {
 219                 throw new RuntimeException(e);
 220             }
 221             // On macOS, the number of 1024 byte blocks might be incorrectly
 222             // calculated by 'df' using integer division by 2 of the number of
 223             // 512 byte blocks, resulting in a size smaller than the actual
 224             // value when the number of blocks is odd.
 225             if (!IS_MAC || bs != 512 || ts - s.total() != 512) {
 226                 fail(s.name(), s.total(), "!=", ts);
 227             }
 228         } else {
 229             pass();
 230         }
 231 
 232         // unix df returns statvfs.f_bavail
 233         long tsp = (!IS_WIN ? us : fs);
 234         if (!s.woomFree(tsp)) {
 235             fail(s.name(), s.free(), "??", tsp);
 236         } else {
 237             pass();
 238         }
 239 
 240         if (fs > s.total()) {
 241             fail(s.name(), s.total(), ">", fs);
 242         } else {
 243             pass();
 244         }
 245 
 246         if (us > s.total()) {
 247             fail(s.name(), s.total(), ">", us);
 248         } else {
 249             pass();
 250         }
 251     }
 252 
 253     private static String FILE_PREFIX = "/getSpace.";
 254     private static void compareZeroNonExist() {
 255         File f;
 256         while (true) {
 257             f = new File(FILE_PREFIX + Math.random());
 258             if (f.exists()) {
 259                 continue;
 260             }
 261             break;
 262         }
 263 
 264         long [] s = { f.getTotalSpace(), f.getFreeSpace(), f.getUsableSpace() };
 265 
 266         for (int i = 0; i < s.length; i++) {
 267             if (s[i] != 0L) {
 268                 fail(f.getName(), s[i], "!=", 0L);
 269             } else {
 270                 pass();
 271             }
 272         }
 273     }
 274 
 275     private static void compareZeroExist() {
 276         try {
 277             File f = File.createTempFile("tmp", null, new File("."));
 278 
 279             long [] s = { f.getTotalSpace(), f.getFreeSpace(), f.getUsableSpace() };
 280 
 281             for (int i = 0; i < s.length; i++) {
 282                 if (s[i] == 0L) {
 283                     fail(f.getName(), s[i], "==", 0L);
 284                 } else {
 285                     pass();
 286                 }
 287             }
 288         } catch (IOException x) {
 289             x.printStackTrace();
 290             fail("Couldn't create temp file for test");
 291         }
 292     }
 293 
 294     private static class Allow extends SecurityManager {
 295         public void checkRead(String file) {}
 296         public void checkPermission(Permission p) {}
 297         public void checkPermission(Permission p, Object context) {}
 298     }
 299 
 300     private static class Deny extends SecurityManager {
 301         public void checkPermission(Permission p) {
 302             if (p.implies(new RuntimePermission("setSecurityManager"))
 303                 || p.implies(new RuntimePermission("getProtectionDomain")))
 304               return;
 305             super.checkPermission(p);
 306         }
 307 
 308         public void checkPermission(Permission p, Object context) {
 309             if (p.implies(new RuntimePermission("setSecurityManager"))
 310                 || p.implies(new RuntimePermission("getProtectionDomain")))
 311               return;
 312             super.checkPermission(p, context);
 313         }
 314     }
 315 
 316     private static class DenyFSA extends Deny {
 317         private String err = "sorry - getFileSystemAttributes";
 318 
 319         public void checkPermission(Permission p) {
 320             if (p.implies(new RuntimePermission("getFileSystemAttributes")))
 321                 throw new SecurityException(err);
 322             super.checkPermission(p);
 323         }
 324 
 325         public void checkPermission(Permission p, Object context) {
 326             if (p.implies(new RuntimePermission("getFileSystemAttributes")))
 327                 throw new SecurityException(err);
 328             super.checkPermission(p, context);
 329         }
 330     }
 331 
 332     private static class DenyRead extends Deny {
 333         private String err = "sorry - checkRead()";
 334 
 335         public void checkRead(String file) {
 336             throw new SecurityException(err);
 337         }
 338     }
 339 
 340     private static int testFile(Path dir) {
 341         String dirName = dir.toString();
 342         out.format("--- Testing %s%n", dirName);
 343         ArrayList<Space> l;
 344         try {
 345             l = space(dirName);
 346         } catch (IOException x) {
 347             throw new RuntimeException(dirName + " can't get file system information", x);
 348         }
 349         compare(l.get(0));
 350 
 351         if (fail != 0) {
 352             err.format("%d tests: %d failure(s); first: %s%n",
 353                 fail + pass, fail, first);
 354         } else {
 355             out.format("all %d tests passed%n", fail + pass);
 356         }
 357 
 358         return fail != 0 ? 1 : 0;
 359     }
 360 
 361     private static int testDF() {
 362         out.println("--- Testing df");
 363         // Find all of the partitions on the machine and verify that the size
 364         // returned by "df" is equivalent to File.getXSpace() values.
 365         ArrayList<Space> l;
 366         try {
 367             l = space(null);
 368         } catch (IOException x) {
 369             throw new RuntimeException("can't get file system information", x);
 370         }
 371         if (l.size() == 0)
 372             throw new RuntimeException("no partitions?");
 373 
 374         for (int i = 0; i < sma.length; i++) {
 375             System.setSecurityManager(sma[i]);
 376             SecurityManager sm = System.getSecurityManager();
 377             if (sma[i] != null && sm == null)
 378                 throw new RuntimeException("Test configuration error "
 379                                            + " - can't set security manager");
 380 
 381             out.format("%nSecurityManager = %s%n" ,
 382                        (sm == null ? "null" : sm.getClass().getName()));
 383             for (var s : l) {
 384                 if (sm instanceof Deny) {
 385                     tryCatch(s);
 386                 } else {
 387                     compare(s);
 388                     compareZeroNonExist();
 389                     compareZeroExist();
 390                 }
 391             }
 392         }
 393 
 394         System.setSecurityManager(null);
 395 
 396         if (fail != 0) {
 397             err.format("%d tests: %d failure(s); first: %s%n",
 398                 fail + pass, fail, first);
 399         } else {
 400             out.format("all %d tests passed%n", fail + pass);
 401         }
 402 
 403         return fail != 0 ? 1 : 0;
 404     }
 405 
 406     private static void perms(File file, boolean allow) throws IOException {
 407         file.setExecutable(allow, false);
 408         file.setReadable(allow, false);
 409         file.setWritable(allow, false);
 410     }
 411 
 412     private static void deny(Path path) throws IOException {
 413         perms(path.toFile(), false);
 414     }
 415 
 416     private static void allow(Path path) throws IOException {
 417         perms(path.toFile(), true);
 418     }
 419 
 420     public static void main(String[] args) throws Exception {
 421         int failedTests = testDF();
 422         reset();
 423 
 424         Path tmpDir = Files.createTempDirectory(null);
 425         Path tmpSubdir = Files.createTempDirectory(tmpDir, null);
 426         Path tmpFile = Files.createTempFile(tmpSubdir, "foo", null);
 427 
 428         deny(tmpSubdir);
 429         failedTests += testFile(tmpFile);
 430 
 431         allow(tmpSubdir);
 432         Files.delete(tmpFile);
 433         Files.delete(tmpSubdir);
 434         Files.delete(tmpDir);
 435 
 436         if (failedTests > 0) {
 437             throw new RuntimeException(failedTests + " test(s) failed");
 438         }
 439     }
 440 }