symlink support in vfs & SOE protection

This commit is contained in:
Dmitry Avdeev
2011-11-02 16:48:13 +04:00
parent a41ce7546a
commit 4d37e5ad95
12 changed files with 279 additions and 85 deletions
@@ -20,7 +20,11 @@ import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
public class VfsUtilCore {
/**
@@ -151,4 +155,29 @@ public class VfsUtilCore {
}
return stream;
}
public static void visitChildrenRecursively(@NotNull VirtualFile file, VirtualFileVisitor visitor) {
visitChildrenRecursively(file, visitor, null);
}
private static void visitChildrenRecursively(@NotNull VirtualFile file,
VirtualFileVisitor visitor,
@Nullable Set<VirtualFile> visitedSymlinks) {
if (!visitor.visitFile(file)) return;
if (file.isSymLink()) {
if (visitedSymlinks == null) {
visitedSymlinks = new HashSet<VirtualFile>();
}
if (!visitedSymlinks.add(file)) {
visitor.afterChildrenVisited(file);
return;
}
}
VirtualFile[] children = file.getChildren();
for (VirtualFile child : children) {
visitChildrenRecursively(child, visitor, visitedSymlinks);
}
visitor.afterChildrenVisited(file);
}
}
@@ -212,6 +212,11 @@ public abstract class VirtualFile extends UserDataHolderBase implements Modifica
return false;
}
@Nullable
public String resolveSymLink() {
return null;
}
/**
* Checks whether this file is a special (e.g. FIFO or device) file.
*
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* 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.intellij.openapi.vfs;
/**
* @author Dmitry Avdeev
* Date: 10/31/11
*/
public abstract class VirtualFileVisitor {
public abstract boolean visitFile(VirtualFile file);
public void afterChildrenVisited(VirtualFile file) {}
}
@@ -353,7 +353,7 @@ public class DirectoryIndexImpl extends DirectoryIndex implements ProjectCompone
}
if (!parentInfo.getOrderEntries().isEmpty()) {
state.fillMapWithOrderEntries(file, parentInfo.getOrderEntries(), null, null, null, parentInfo, null);
state.fillMapWithOrderEntries(file, parentInfo.getOrderEntries(), null, null, null, parentInfo);
}
return state;
}
@@ -427,29 +427,59 @@ public class DirectoryIndexImpl extends DirectoryIndex implements ProjectCompone
return info;
}
private void fillMapWithModuleContent(VirtualFile dir, Module module, VirtualFile contentRoot) {
if (isExcluded(contentRoot, dir)) return;
if (isIgnored(dir)) return;
private void fillMapWithModuleContent(VirtualFile root, final Module module, final VirtualFile contentRoot) {
DirectoryInfo info = getOrCreateDirInfo(dir);
VfsUtilCore.visitChildrenRecursively(root, new DirectoryVisitor() {
@Override
protected DirectoryInfo updateInfo(VirtualFile file) {
if (isExcluded(contentRoot, file)) return null;
if (isIgnored(file)) return null;
if (info.module != null) { // module contents overlap
DirectoryInfo parentInfo = myDirToInfoMap.get(dir.getParent());
if (parentInfo == null || !info.module.equals(parentInfo.module)) return; // content of another module is below this module's content
}
DirectoryInfo info = getOrCreateDirInfo(file);
VirtualFile[] children = dir.getChildren();
for (VirtualFile child : children) {
if (child.isDirectory()) {
fillMapWithModuleContent(child, module, contentRoot);
if (info.module != null) { // module contents overlap
DirectoryInfo parentInfo = myDirToInfoMap.get(file.getParent());
if (parentInfo == null || !info.module.equals(parentInfo.module)) return null;
}
return info;
}
}
// important to change module AFTER processing children - to handle overlapping modules
info.module = module;
info.contentRoot = contentRoot;
@Override
protected void afterChildrenVisited(DirectoryInfo info) {
info.module = module;
info.contentRoot = contentRoot;
}
});
}
private abstract class DirectoryVisitor extends VirtualFileVisitor {
private Stack<DirectoryInfo> myDirectoryInfoStack = new Stack<DirectoryInfo>();
@Override
public boolean visitFile(VirtualFile file) {
if (!file.isDirectory()) return false;
DirectoryInfo info = updateInfo(file);
if (info != null) {
myDirectoryInfoStack.push(info);
return true;
}
return false;
}
@Override
public void afterChildrenVisited(VirtualFile file) {
afterChildrenVisited(myDirectoryInfoStack.pop());
}
@Nullable
protected abstract DirectoryInfo updateInfo(VirtualFile file);
protected void afterChildrenVisited(DirectoryInfo info) {}
}
private boolean isExcluded(VirtualFile root, VirtualFile dir) {
Set<String> excludes = myExcludeRootsMap.get(root);
return excludes != null && excludes.contains(dir.getUrl());
@@ -494,28 +524,45 @@ public class DirectoryIndexImpl extends DirectoryIndex implements ProjectCompone
}
}
private void fillMapWithModuleSource(VirtualFile dir, Module module, String packageName, VirtualFile sourceRoot, boolean isTestSource) {
DirectoryInfo info = myDirToInfoMap.get(dir);
if (info == null) return;
if (!module.equals(info.module)) return;
private void fillMapWithModuleSource(final VirtualFile dir, final Module module, final String packageName, final VirtualFile sourceRoot, final boolean isTestSource) {
VfsUtilCore.visitChildrenRecursively(dir, new DirectoryVisitor() {
private Stack<String> myPackages = new Stack<String>();
if (info.isInModuleSource) { // module sources overlap
String definedPackage = myDirToPackageName.get(dir);
if (definedPackage != null && definedPackage.length() == 0) return; // another source root starts here
}
@Override
protected DirectoryInfo updateInfo(VirtualFile file) {
DirectoryInfo info = myDirToInfoMap.get(file);
if (info == null) return null;
if (!module.equals(info.module)) return null;
info.isInModuleSource = true;
info.isTestSource = isTestSource;
info.sourceRoot = sourceRoot;
setPackageName(dir, packageName);
if (info.isInModuleSource) { // module sources overlap
String definedPackage = myDirToPackageName.get(file);
if (definedPackage != null && definedPackage.length() == 0) return null; // another source root starts here
}
VirtualFile[] children = dir.getChildren();
for (VirtualFile child : children) {
if (child.isDirectory()) {
String childPackageName = getPackageNameForSubdir(packageName, child.getName());
fillMapWithModuleSource(child, module, childPackageName, sourceRoot, isTestSource);
info.isInModuleSource = true;
info.isTestSource = isTestSource;
info.sourceRoot = sourceRoot;
String currentPackage;
if (myPackages.isEmpty()) {
currentPackage = packageName;
}
else {
currentPackage = getPackageNameForSubdir(myPackages.peek(), file.getName());
}
myPackages.push(currentPackage);
setPackageName(file, currentPackage);
return info;
}
}
@Override
protected void afterChildrenVisited(DirectoryInfo info) {
super.afterChildrenVisited(info);
myPackages.pop();
}
});
}
private void initLibrarySources(Module module, ProgressIndicator progress) {
@@ -623,7 +670,7 @@ public class DirectoryIndexImpl extends DirectoryIndex implements ProjectCompone
VirtualFile[] sourceRoots = orderEntry.getFiles(OrderRootType.SOURCES);
for (VirtualFile sourceRoot : sourceRoots) {
fillMapWithOrderEntries(sourceRoot, oneEntryList, entryModule, null, null, null, null);
fillMapWithOrderEntries(sourceRoot, oneEntryList, entryModule, null, null, null);
}
}
else if (orderEntry instanceof LibraryOrderEntry || orderEntry instanceof JdkOrderEntry) {
@@ -645,19 +692,19 @@ public class DirectoryIndexImpl extends DirectoryIndex implements ProjectCompone
for (Map.Entry<VirtualFile, Collection<OrderEntry>> mapEntry : depEntries.entrySet()) {
final VirtualFile vRoot = mapEntry.getKey();
final Collection<OrderEntry> entries = mapEntry.getValue();
fillMapWithOrderEntries(vRoot, entries, null, null, null, null, null);
fillMapWithOrderEntries(vRoot, entries, null, null, null, null);
}
for (Map.Entry<VirtualFile, Collection<OrderEntry>> mapEntry : libClassRootEntries.entrySet()) {
final VirtualFile vRoot = mapEntry.getKey();
final Collection<OrderEntry> entries = mapEntry.getValue();
fillMapWithOrderEntries(vRoot, entries, null, vRoot, null, null, null);
fillMapWithOrderEntries(vRoot, entries, null, vRoot, null, null);
}
for (Map.Entry<VirtualFile, Collection<OrderEntry>> mapEntry : libSourceRootEntries.entrySet()) {
final VirtualFile vRoot = mapEntry.getKey();
final Collection<OrderEntry> entries = mapEntry.getValue();
fillMapWithOrderEntries(vRoot, entries, null, null, vRoot, null, null);
fillMapWithOrderEntries(vRoot, entries, null, null, vRoot, null);
}
}
@@ -690,41 +737,50 @@ public class DirectoryIndexImpl extends DirectoryIndex implements ProjectCompone
}
}
private void fillMapWithOrderEntries(VirtualFile dir,
Collection<OrderEntry> orderEntries,
Module module,
VirtualFile libraryClassRoot,
VirtualFile librarySourceRoot,
DirectoryInfo parentInfo,
final List<OrderEntry> oldParentEntries) {
if (isIgnored(dir)) return;
private void fillMapWithOrderEntries(final VirtualFile root,
final Collection<OrderEntry> orderEntries,
final Module module,
final VirtualFile libraryClassRoot,
final VirtualFile librarySourceRoot,
final DirectoryInfo parentInfo) {
VfsUtilCore.visitChildrenRecursively(root, new DirectoryVisitor() {
DirectoryInfo info = myDirToInfoMap.get(dir); // do not create it here!
if (info == null) return;
private Stack<List<OrderEntry>> myEntries = new Stack<List<OrderEntry>>();
if (module != null) {
if (info.module != module) return;
if (!info.isInModuleSource) return;
}
else if (libraryClassRoot != null) {
if (info.libraryClassRoot != libraryClassRoot) return;
if (info.isInModuleSource) return;
}
else if (librarySourceRoot != null) {
if (!info.isInLibrarySource) return;
if (info.sourceRoot != librarySourceRoot) return;
if (info.libraryClassRoot != null) return;
}
@Override
protected DirectoryInfo updateInfo(VirtualFile dir) {
if (isIgnored(dir)) return null;
final List<OrderEntry> oldEntries = info.getOrderEntries();
info.addOrderEntries(orderEntries, parentInfo, oldParentEntries);
DirectoryInfo info = myDirToInfoMap.get(dir); // do not create it here!
if (info == null) return null;
final VirtualFile[] children = dir.getChildren();
for (VirtualFile child : children) {
if (child.isDirectory()) {
fillMapWithOrderEntries(child, orderEntries, module, libraryClassRoot, librarySourceRoot, info, oldEntries);
if (module != null) {
if (info.module != module) return null;
if (!info.isInModuleSource) return null;
}
else if (libraryClassRoot != null) {
if (info.libraryClassRoot != libraryClassRoot) return null;
if (info.isInModuleSource) return null;
}
else if (librarySourceRoot != null) {
if (!info.isInLibrarySource) return null;
if (info.sourceRoot != librarySourceRoot) return null;
if (info.libraryClassRoot != null) return null;
}
List<OrderEntry> oldParentEntries = myEntries.isEmpty() ? null : myEntries.peek();
final List<OrderEntry> oldEntries = info.getOrderEntries();
myEntries.push(oldEntries);
info.addOrderEntries(orderEntries, parentInfo, oldParentEntries);
return info;
}
}
@Override
protected void afterChildrenVisited(DirectoryInfo info) {
myEntries.pop();
}
});
}
private void doInitialize(boolean reverseAllSets/* for testing order independence*/) {
@@ -17,26 +17,34 @@
package com.intellij.openapi.roots.impl;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import org.jetbrains.annotations.NotNull;
public class FileIndexImplUtil {
private FileIndexImplUtil() {
}
public static boolean iterateRecursively(@NotNull VirtualFile root, @NotNull VirtualFileFilter filter, @NotNull ContentIterator iterator){
if (!root.isValid() || !filter.accept(root)) return true;
public static boolean iterateRecursively(@NotNull final VirtualFile root, @NotNull final VirtualFileFilter filter, @NotNull final ContentIterator iterator){
if (!iterator.processFile(root)) return false;
try {
VfsUtilCore.visitChildrenRecursively(root, new VirtualFileVisitor() {
@Override
public boolean visitFile(VirtualFile file) {
if (!file.isValid() || !filter.accept(file)) return true;
if (root.isDirectory()){
VirtualFile[] children = root.getChildren();
for (VirtualFile child : children) {
if (!iterateRecursively(child, filter, iterator)) return false;
}
if (!iterator.processFile(file)) throw new StopItException();
return true;
}
});
return true;
}
catch (StopItException e) {
return false;
}
return true;
}
private static class StopItException extends RuntimeException {}
}
@@ -42,6 +42,8 @@ public interface FileSystemInterface {
void setWritable(@NotNull VirtualFile file, boolean writableFlag) throws IOException;
boolean isSymLink(@NotNull VirtualFile file);
@Nullable
String resolveSymLink(@NotNull VirtualFile file);
boolean isSpecialFile(@NotNull VirtualFile file);
@@ -57,6 +57,11 @@ public abstract class NewVirtualFileSystem extends VirtualFileSystem implements
return false;
}
@Override
public String resolveSymLink(@NotNull VirtualFile file) {
return null;
}
@Override
public boolean isSpecialFile(@NotNull final VirtualFile file) {
return false;
@@ -185,6 +185,11 @@ public class JarHandler extends JarHandlerBase implements FileSystemInterface {
return false;
}
@Override
public String resolveSymLink(@NotNull VirtualFile file) {
return null;
}
@Override
public boolean isSpecialFile(@NotNull VirtualFile file) {
return false;
@@ -178,6 +178,11 @@ public abstract class LocalFileSystemBase extends LocalFileSystem {
return SymLinkUtil.isSymLink(file.getPath());
}
@Override
public String resolveSymLink(@NotNull VirtualFile file) {
return SymLinkUtil.resolveSymLink(file.getPath());
}
@Override
public boolean isSpecialFile(@NotNull final VirtualFile file) {
final File ioFile = convertToIOFile(file);
@@ -0,0 +1,44 @@
/*
* Copyright 2000-2011 JetBrains s.r.o.
*
* 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.intellij.openapi.vfs.newvfs.impl;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.newvfs.NewVirtualFileSystem;
import com.intellij.openapi.vfs.newvfs.VfsImplUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Dmitry Avdeev
* Date: 10/31/11
*/
public class SymlinkDirectory extends VirtualDirectoryImpl {
@Nullable
private final VirtualFile myTarget;
public SymlinkDirectory(@NotNull String name, final VirtualDirectoryImpl parent, @NotNull NewVirtualFileSystem fs, final int id) {
super(name, parent, fs, id);
String path = fs.resolveSymLink(this);
myTarget = path == null ? null : VfsImplUtil.findFileByPath(fs, path);
}
@NotNull
@Override
public VirtualFile[] getChildren() {
return myTarget == null ? VirtualFile.EMPTY_ARRAY : myTarget.getChildren();
}
}
@@ -68,7 +68,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
private final NewVirtualFileSystem myFS;
// guarded by this
private Object myChildren; // Either HashMap<String, VFile> or VFile[]
protected Object myChildren; // Either HashMap<String, VFile> or VFile[]
public VirtualDirectoryImpl(@NotNull String name, final VirtualDirectoryImpl parent, @NotNull NewVirtualFileSystem fs, final int id) {
super(name, parent, id);
@@ -165,7 +165,7 @@ public class VirtualDirectoryImpl extends VirtualFileSystemEntry {
final VirtualFileSystemEntry child;
final NewVirtualFileSystem fs = getFileSystem();
if (PersistentFS.isDirectory(id)) {
child = new VirtualDirectoryImpl(name, this, fs, id);
child = PersistentFS.isSymLink(id) ? new SymlinkDirectory(name, this, fs, id) : new VirtualDirectoryImpl(name, this, fs, id);
}
else {
child = new VirtualFileImpl(name, this, id);
@@ -24,9 +24,7 @@ import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.application.ex.ApplicationEx;
import com.intellij.openapi.components.ApplicationComponent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.ShutDownTracker;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.*;
import com.intellij.openapi.util.io.BufferExposingByteArrayInputStream;
import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream;
import com.intellij.openapi.util.io.ByteSequence;
@@ -322,6 +320,11 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
return (FSRecords.getFlags(id) & IS_DIRECTORY_FLAG) != 0;
}
public static boolean isSymLink(final int id) {
assert id > 0;
return (FSRecords.getFlags(id) & IS_SYMLINK) != 0;
}
private static int getParent(final int id) {
assert id > 0;
return FSRecords.getParent(id);
@@ -363,6 +366,11 @@ public class PersistentFS extends ManagingFS implements ApplicationComponent {
return (FSRecords.getFlags(getFileId(file)) & IS_SYMLINK) != 0;
}
@Override
public String resolveSymLink(@NotNull VirtualFile file) {
throw new UnsupportedOperationException();
}
@Override
public boolean isSpecialFile(@NotNull VirtualFile file) {
return (FSRecords.getFlags(getFileId(file)) & IS_SPECIAL) != 0;