1 /*
   2  * Copyright (c) 2019, 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 /* @test
  25  * @bug 8231254
  26  * @requires os.family == "mac"
  27  * @summary Check access and basic NIO APIs on APFS for macOS version >= 10.15
  28  */
  29 import java.io.BufferedReader;
  30 import java.io.FileInputStream;
  31 import java.io.InputStream;
  32 import java.io.InputStreamReader;
  33 import java.io.IOException;
  34 import java.io.Reader;
  35 import java.nio.ByteBuffer;
  36 import java.nio.channels.SeekableByteChannel;
  37 import java.nio.file.DirectoryStream;
  38 import java.nio.file.Files;
  39 import java.nio.file.Path;
  40 import java.nio.file.Paths;
  41 import java.nio.file.StandardOpenOption;
  42 import java.nio.file.attribute.FileTime;
  43 import java.util.Arrays;
  44 import java.util.Iterator;
  45 import java.util.Random;
  46 
  47 public class MacVolumesTest {
  48     private static final String SYSTEM_VOLUME = "/";
  49     private static final String DATA_VOLUME = "/System/Volumes/Data";
  50     private static final String FIRMLINKS = "/usr/share/firmlinks";
  51 
  52     private static final void checkSystemVolume() throws IOException {
  53         System.out.format("--- Checking system volume %s ---%n", SYSTEM_VOLUME);
  54         Path root = Paths.get(SYSTEM_VOLUME);
  55         if (!Files.getFileStore(root).isReadOnly()) {
  56             throw new RuntimeException("Root volume is not read-only");
  57         }
  58 
  59         Path tempDir;
  60         try {
  61             tempDir = Files.createTempDirectory(root, "tempDir");
  62             throw new RuntimeException("Created temporary directory in root");
  63         } catch (IOException ignore) {
  64         }
  65 
  66         Path tempFile;
  67         try {
  68             tempFile = Files.createTempFile(root, "tempFile", null);
  69             throw new RuntimeException("Created temporary file in root");
  70         } catch (IOException ignore) {
  71         }
  72 
  73         Path path = null;
  74         Path etc = Paths.get(SYSTEM_VOLUME, "etc");
  75         if (Files.isWritable(etc)) {
  76             throw new RuntimeException("System path " + etc + " is writable");
  77         }
  78         try (DirectoryStream<Path> ds = Files.newDirectoryStream(etc)) {
  79             Iterator<Path> paths = ds.iterator();
  80             while (paths.hasNext()) {
  81                 Path p = paths.next();
  82                 if (Files.isReadable(p) && Files.isRegularFile(p)) {
  83                     path = p;
  84                     break;
  85                 }
  86             }
  87         }
  88         if (path == null) {
  89             System.err.println("No root test file found: skipping file test");
  90             return;
  91         }
  92         System.out.format("Using root test file %s%n", path);
  93 
  94         if (Files.isWritable(path)) {
  95             throw new RuntimeException("Test file " + path + " is writable");
  96         }
  97 
  98         FileTime creationTime =
  99             (FileTime)Files.getAttribute(path, "basic:creationTime");
 100         System.out.format("%s creation time: %s%n", path, creationTime);
 101 
 102         long size = Files.size(path);
 103         int capacity = (int)Math.min(1024, size);
 104         ByteBuffer buf = ByteBuffer.allocate(capacity);
 105         try (SeekableByteChannel sbc = Files.newByteChannel(path)) {
 106             int n = sbc.read(buf);
 107             System.out.format("Read %d bytes from %s%n", n, path);
 108         }
 109     }
 110 
 111     private static final void checkDataVolume() throws IOException {
 112         System.out.format("--- Checking data volume %s ---%n", DATA_VOLUME);
 113         Path data = Paths.get(DATA_VOLUME, "private", "tmp");
 114         if (Files.getFileStore(data).isReadOnly()) {
 115             throw new RuntimeException("Data volume is read-only");
 116         }
 117 
 118         Path tempDir = Files.createTempDirectory(data, "tempDir");
 119         tempDir.toFile().deleteOnExit();
 120         System.out.format("Temporary directory: %s%n", tempDir);
 121         if (!Files.isWritable(tempDir)) {
 122             throw new RuntimeException("Temporary directory is not writable");
 123         }
 124 
 125         Path tempFile = Files.createTempFile(tempDir, "tempFile", null);
 126         tempFile.toFile().deleteOnExit();
 127         System.out.format("Temporary file: %s%n", tempFile);
 128         if (!Files.isWritable(tempFile)) {
 129             throw new RuntimeException("Temporary file is not writable");
 130         }
 131 
 132         byte[] bytes = new byte[42];
 133         new Random().nextBytes(bytes);
 134         try (SeekableByteChannel sbc = Files.newByteChannel(tempFile,
 135             StandardOpenOption.WRITE)) {
 136             ByteBuffer src = ByteBuffer.wrap(bytes);
 137             if (sbc.write(src) != bytes.length) {
 138                 throw new RuntimeException("Incorrect number of bytes written");
 139             }
 140         }
 141 
 142         try (SeekableByteChannel sbc = Files.newByteChannel(tempFile)) {
 143             ByteBuffer dst = ByteBuffer.allocate(bytes.length);
 144             if (sbc.read(dst) != bytes.length) {
 145                 throw new RuntimeException("Incorrect number of bytes read");
 146             }
 147             if (!Arrays.equals(dst.array(), bytes)) {
 148                 throw new RuntimeException("Bytes read != bytes written");
 149             }
 150         }
 151     }
 152 
 153     static void checkFirmlinks() throws IOException {
 154         System.out.format("--- Checking firmlinks %s ---%n", FIRMLINKS);
 155         Path firmlinks = Paths.get(FIRMLINKS);
 156         if (!Files.exists(firmlinks)) {
 157             System.err.format("%s does not exist: skipping firmlinks test%n",
 158                 firmlinks);
 159             return;
 160         } else if (!Files.isReadable(firmlinks)) {
 161             throw new RuntimeException(String.format("%s is not readable",
 162                 firmlinks));
 163         }
 164 
 165         try (BufferedReader br = Files.newBufferedReader(firmlinks)) {
 166             String line;
 167             while ((line = br.readLine()) != null) {
 168                 String file = line.split("\\s")[0];
 169                 Path path = Paths.get(file);
 170                 if (!Files.exists(path)) {
 171                     System.err.format("Firmlink %s does not exist: skipping%n",
 172                         file);
 173                     continue;
 174                 }
 175                 if (Files.getFileStore(path).isReadOnly()) {
 176                     String msg = String.format("%s is read-only%n", file);
 177                     throw new RuntimeException(msg);
 178                 } else {
 179                     System.out.format("Firmlink %s OK%n", file);
 180                 }
 181             }
 182         }
 183     }
 184 
 185     public static void main(String[] args) throws Exception {
 186         String[] osv = System.getProperty("os.version").split("\\.");
 187         int major = Integer.valueOf(osv[0]);
 188         int minor = Integer.valueOf(osv[1]);
 189         if (major < 10 || (major == 10 && minor < 15)) {
 190             System.out.format("macOS version %d.%d too old: skipping test%n",
 191                 major, minor);
 192             return;
 193         }
 194 
 195         // Check system volume for read-only.
 196         checkSystemVolume();
 197 
 198         // Check data volume for read-write.
 199         checkDataVolume();
 200 
 201         // Check firmlinks for read-write.
 202         checkFirmlinks();
 203     }
 204 }