1 /* 2 * Copyright 2013 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.jimfs; 18 19 import static com.google.common.base.Preconditions.checkNotNull; 20 import static com.google.common.base.Preconditions.checkState; 21 22 import com.google.common.collect.AbstractIterator; 23 import com.google.common.collect.ImmutableSet; 24 import java.io.IOException; 25 import java.nio.channels.SeekableByteChannel; 26 import java.nio.file.ClosedDirectoryStreamException; 27 import java.nio.file.CopyOption; 28 import java.nio.file.DirectoryIteratorException; 29 import java.nio.file.LinkOption; 30 import java.nio.file.OpenOption; 31 import java.nio.file.Path; 32 import java.nio.file.ProviderMismatchException; 33 import java.nio.file.SecureDirectoryStream; 34 import java.nio.file.attribute.FileAttribute; 35 import java.nio.file.attribute.FileAttributeView; 36 import java.util.Iterator; 37 import java.util.Set; 38 import org.checkerframework.checker.nullness.compatqual.NullableDecl; 39 40 /** 41 * Secure directory stream implementation that uses a {@link FileSystemView} with the stream's 42 * directory as its working directory. 43 * 44 * @author Colin Decker 45 */ 46 final class JimfsSecureDirectoryStream implements SecureDirectoryStream<Path> { 47 48 private final FileSystemView view; 49 private final Filter<? super Path> filter; 50 private final FileSystemState fileSystemState; 51 52 private boolean open = true; 53 private Iterator<Path> iterator = new DirectoryIterator(); 54 JimfsSecureDirectoryStream( FileSystemView view, Filter<? super Path> filter, FileSystemState fileSystemState)55 public JimfsSecureDirectoryStream( 56 FileSystemView view, Filter<? super Path> filter, FileSystemState fileSystemState) { 57 this.view = checkNotNull(view); 58 this.filter = checkNotNull(filter); 59 this.fileSystemState = fileSystemState; 60 fileSystemState.register(this); 61 } 62 path()63 private JimfsPath path() { 64 return view.getWorkingDirectoryPath(); 65 } 66 67 @Override iterator()68 public synchronized Iterator<Path> iterator() { 69 checkOpen(); 70 Iterator<Path> result = iterator; 71 checkState(result != null, "iterator() has already been called once"); 72 iterator = null; 73 return result; 74 } 75 76 @Override close()77 public synchronized void close() { 78 open = false; 79 fileSystemState.unregister(this); 80 } 81 checkOpen()82 protected synchronized void checkOpen() { 83 if (!open) { 84 throw new ClosedDirectoryStreamException(); 85 } 86 } 87 88 private final class DirectoryIterator extends AbstractIterator<Path> { 89 90 @NullableDecl private Iterator<Name> fileNames; 91 92 @Override computeNext()93 protected synchronized Path computeNext() { 94 checkOpen(); 95 96 try { 97 if (fileNames == null) { 98 fileNames = view.snapshotWorkingDirectoryEntries().iterator(); 99 } 100 101 while (fileNames.hasNext()) { 102 Name name = fileNames.next(); 103 Path path = view.getWorkingDirectoryPath().resolve(name); 104 105 if (filter.accept(path)) { 106 return path; 107 } 108 } 109 110 return endOfData(); 111 } catch (IOException e) { 112 throw new DirectoryIteratorException(e); 113 } 114 } 115 } 116 117 /** A stream filter that always returns true. */ 118 public static final Filter<Object> ALWAYS_TRUE_FILTER = 119 new Filter<Object>() { 120 @Override 121 public boolean accept(Object entry) throws IOException { 122 return true; 123 } 124 }; 125 126 @Override newDirectoryStream(Path path, LinkOption... options)127 public SecureDirectoryStream<Path> newDirectoryStream(Path path, LinkOption... options) 128 throws IOException { 129 checkOpen(); 130 JimfsPath checkedPath = checkPath(path); 131 132 // safe cast because a file system that supports SecureDirectoryStream always creates 133 // SecureDirectoryStreams 134 return (SecureDirectoryStream<Path>) 135 view.newDirectoryStream( 136 checkedPath, 137 ALWAYS_TRUE_FILTER, 138 Options.getLinkOptions(options), 139 path().resolve(checkedPath)); 140 } 141 142 @Override newByteChannel( Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs)143 public SeekableByteChannel newByteChannel( 144 Path path, Set<? extends OpenOption> options, FileAttribute<?>... attrs) throws IOException { 145 checkOpen(); 146 JimfsPath checkedPath = checkPath(path); 147 ImmutableSet<OpenOption> opts = Options.getOptionsForChannel(options); 148 return new JimfsFileChannel( 149 view.getOrCreateRegularFile(checkedPath, opts), opts, fileSystemState); 150 } 151 152 @Override deleteFile(Path path)153 public void deleteFile(Path path) throws IOException { 154 checkOpen(); 155 JimfsPath checkedPath = checkPath(path); 156 view.deleteFile(checkedPath, FileSystemView.DeleteMode.NON_DIRECTORY_ONLY); 157 } 158 159 @Override deleteDirectory(Path path)160 public void deleteDirectory(Path path) throws IOException { 161 checkOpen(); 162 JimfsPath checkedPath = checkPath(path); 163 view.deleteFile(checkedPath, FileSystemView.DeleteMode.DIRECTORY_ONLY); 164 } 165 166 @Override move(Path srcPath, SecureDirectoryStream<Path> targetDir, Path targetPath)167 public void move(Path srcPath, SecureDirectoryStream<Path> targetDir, Path targetPath) 168 throws IOException { 169 checkOpen(); 170 JimfsPath checkedSrcPath = checkPath(srcPath); 171 JimfsPath checkedTargetPath = checkPath(targetPath); 172 173 if (!(targetDir instanceof JimfsSecureDirectoryStream)) { 174 throw new ProviderMismatchException( 175 "targetDir isn't a secure directory stream associated with this file system"); 176 } 177 178 JimfsSecureDirectoryStream checkedTargetDir = (JimfsSecureDirectoryStream) targetDir; 179 180 view.copy( 181 checkedSrcPath, 182 checkedTargetDir.view, 183 checkedTargetPath, 184 ImmutableSet.<CopyOption>of(), 185 true); 186 } 187 188 @Override getFileAttributeView(Class<V> type)189 public <V extends FileAttributeView> V getFileAttributeView(Class<V> type) { 190 return getFileAttributeView(path().getFileSystem().getPath("."), type); 191 } 192 193 @Override getFileAttributeView( Path path, Class<V> type, LinkOption... options)194 public <V extends FileAttributeView> V getFileAttributeView( 195 Path path, Class<V> type, LinkOption... options) { 196 checkOpen(); 197 final JimfsPath checkedPath = checkPath(path); 198 final ImmutableSet<LinkOption> optionsSet = Options.getLinkOptions(options); 199 return view.getFileAttributeView( 200 new FileLookup() { 201 @Override 202 public File lookup() throws IOException { 203 checkOpen(); // per the spec, must check that the stream is open for each view operation 204 return view.lookUpWithLock(checkedPath, optionsSet).requireExists(checkedPath).file(); 205 } 206 }, 207 type); 208 } 209 210 private static JimfsPath checkPath(Path path) { 211 if (path instanceof JimfsPath) { 212 return (JimfsPath) path; 213 } 214 throw new ProviderMismatchException( 215 "path " + path + " is not associated with a Jimfs file system"); 216 } 217 } 218