/* * Copyright 2013 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.jimfs; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.base.Preconditions.checkState; import com.google.common.collect.AbstractIterator; import com.google.common.collect.ImmutableSet; import java.io.IOException; import java.nio.channels.SeekableByteChannel; import java.nio.file.ClosedDirectoryStreamException; import java.nio.file.CopyOption; import java.nio.file.DirectoryIteratorException; import java.nio.file.LinkOption; import java.nio.file.OpenOption; import java.nio.file.Path; import java.nio.file.ProviderMismatchException; import java.nio.file.SecureDirectoryStream; import java.nio.file.attribute.FileAttribute; import java.nio.file.attribute.FileAttributeView; import java.util.Iterator; import java.util.Set; import org.checkerframework.checker.nullness.compatqual.NullableDecl; /** * Secure directory stream implementation that uses a {@link FileSystemView} with the stream's * directory as its working directory. * * @author Colin Decker */ final class JimfsSecureDirectoryStream implements SecureDirectoryStream { private final FileSystemView view; private final Filter filter; private final FileSystemState fileSystemState; private boolean open = true; private Iterator iterator = new DirectoryIterator(); public JimfsSecureDirectoryStream( FileSystemView view, Filter filter, FileSystemState fileSystemState) { this.view = checkNotNull(view); this.filter = checkNotNull(filter); this.fileSystemState = fileSystemState; fileSystemState.register(this); } private JimfsPath path() { return view.getWorkingDirectoryPath(); } @Override public synchronized Iterator iterator() { checkOpen(); Iterator result = iterator; checkState(result != null, "iterator() has already been called once"); iterator = null; return result; } @Override public synchronized void close() { open = false; fileSystemState.unregister(this); } protected synchronized void checkOpen() { if (!open) { throw new ClosedDirectoryStreamException(); } } private final class DirectoryIterator extends AbstractIterator { @NullableDecl private Iterator fileNames; @Override protected synchronized Path computeNext() { checkOpen(); try { if (fileNames == null) { fileNames = view.snapshotWorkingDirectoryEntries().iterator(); } while (fileNames.hasNext()) { Name name = fileNames.next(); Path path = view.getWorkingDirectoryPath().resolve(name); if (filter.accept(path)) { return path; } } return endOfData(); } catch (IOException e) { throw new DirectoryIteratorException(e); } } } /** A stream filter that always returns true. */ public static final Filter ALWAYS_TRUE_FILTER = new Filter() { @Override public boolean accept(Object entry) throws IOException { return true; } }; @Override public SecureDirectoryStream newDirectoryStream(Path path, LinkOption... options) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); // safe cast because a file system that supports SecureDirectoryStream always creates // SecureDirectoryStreams return (SecureDirectoryStream) view.newDirectoryStream( checkedPath, ALWAYS_TRUE_FILTER, Options.getLinkOptions(options), path().resolve(checkedPath)); } @Override public SeekableByteChannel newByteChannel( Path path, Set options, FileAttribute... attrs) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); ImmutableSet opts = Options.getOptionsForChannel(options); return new JimfsFileChannel( view.getOrCreateRegularFile(checkedPath, opts), opts, fileSystemState); } @Override public void deleteFile(Path path) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); view.deleteFile(checkedPath, FileSystemView.DeleteMode.NON_DIRECTORY_ONLY); } @Override public void deleteDirectory(Path path) throws IOException { checkOpen(); JimfsPath checkedPath = checkPath(path); view.deleteFile(checkedPath, FileSystemView.DeleteMode.DIRECTORY_ONLY); } @Override public void move(Path srcPath, SecureDirectoryStream targetDir, Path targetPath) throws IOException { checkOpen(); JimfsPath checkedSrcPath = checkPath(srcPath); JimfsPath checkedTargetPath = checkPath(targetPath); if (!(targetDir instanceof JimfsSecureDirectoryStream)) { throw new ProviderMismatchException( "targetDir isn't a secure directory stream associated with this file system"); } JimfsSecureDirectoryStream checkedTargetDir = (JimfsSecureDirectoryStream) targetDir; view.copy( checkedSrcPath, checkedTargetDir.view, checkedTargetPath, ImmutableSet.of(), true); } @Override public V getFileAttributeView(Class type) { return getFileAttributeView(path().getFileSystem().getPath("."), type); } @Override public V getFileAttributeView( Path path, Class type, LinkOption... options) { checkOpen(); final JimfsPath checkedPath = checkPath(path); final ImmutableSet optionsSet = Options.getLinkOptions(options); return view.getFileAttributeView( new FileLookup() { @Override public File lookup() throws IOException { checkOpen(); // per the spec, must check that the stream is open for each view operation return view.lookUpWithLock(checkedPath, optionsSet).requireExists(checkedPath).file(); } }, type); } private static JimfsPath checkPath(Path path) { if (path instanceof JimfsPath) { return (JimfsPath) path; } throw new ProviderMismatchException( "path " + path + " is not associated with a Jimfs file system"); } }