| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.netbeans.modules.masterfs; |
| |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Files; |
| import java.nio.file.Path; |
| import java.nio.file.Paths; |
| import java.util.Enumeration; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import org.netbeans.junit.NbTestCase; |
| import org.openide.filesystems.FileObject; |
| import org.openide.filesystems.FileUtil; |
| import org.openide.util.Utilities; |
| |
| /** |
| * |
| * @author jhavlin |
| */ |
| public class FileObjectCyclicSymlinksTest extends NbTestCase { |
| |
| private static final Logger LOG |
| = Logger.getLogger(FileObjectCyclicSymlinksTest.class.getName()); |
| |
| public FileObjectCyclicSymlinksTest(String name) { |
| super(name); |
| } |
| |
| @Override |
| protected void setUp() throws Exception { |
| deleteChildren(getWorkDir()); |
| clearWorkDir(); |
| } |
| |
| @Override |
| protected void tearDown() throws Exception { |
| deleteChildren(getWorkDir()); |
| clearWorkDir(); |
| } |
| |
| /** |
| * Test that symbolic links are handled correctly, without infinite |
| * recursion. |
| * |
| * Let's use the following data structure: |
| * <pre> |
| * f1 |
| * f2 |
| * f3 |
| * f4 |
| * f5 (symbolic link with target = f1) |
| * </pre> |
| * |
| * @throws java.io.IOException |
| */ |
| public void test218795() throws IOException { |
| |
| File rootF = getWorkDir(); |
| createDirTreeWithChangingNames(rootF, 5); |
| |
| FileObject rootFO = FileUtil.toFileObject(rootF); |
| assertNotNull(rootFO); |
| assertEquals(rootF, FileUtil.toFile(rootFO)); |
| rootFO.refresh(); |
| assertEquals(4, countRecursiveChildren(rootFO)); |
| } |
| |
| /** |
| * Count number of children returned by FileObject.getChildren(true). Limit |
| * the number to a reasonable constant. |
| */ |
| private int countRecursiveChildren(FileObject root) { |
| Enumeration<? extends FileObject> children = root.getChildren(true); |
| int count = 0; |
| LOG.log(Level.FINER, "Enumerating recursive children under {0}", root); |
| while (children.hasMoreElements()) { |
| FileObject child = children.nextElement(); |
| assertNotNull(child); |
| File f = FileUtil.toFile(child); |
| LOG.log(Level.FINER, "Found child: {0}", f.getPath()); |
| assertNotNull(f); |
| count++; |
| if (count > 100) { |
| break; |
| } |
| } |
| return count; |
| } |
| |
| /** |
| * Print info about skipped test. |
| */ |
| private void printSkipped() { |
| LOG.log(Level.INFO, "Skipping {0}", getName()); |
| } |
| |
| /** |
| * Create a directory "tree" where folder have increasing names, e.g. |
| * f1/f2/f3/f4. The last folder is a symlink to the first folder. |
| * |
| * @param root Root directory |
| * @param depth Depth of the tree. |
| * @return The symbolic link on success, null otherwise. |
| */ |
| private File createDirTreeWithChangingNames(File root, int depth) { |
| return createDirTree(root, depth, "f", true); |
| } |
| |
| /** |
| * Create directory tree with one cyclic symbolic link. |
| */ |
| private File createDirTree(File root, int depth, String name, |
| boolean appendLevelToName) { |
| |
| assert depth > 1; |
| File parent = root; |
| File first = null; |
| |
| for (int i = 1; i <= depth; i++) { |
| File file = new File(parent, name + (appendLevelToName ? i : "")); |
| if (i == depth) { |
| Path newLink = Paths.get(Utilities.toURI(file)); |
| Path target = Paths.get(Utilities.toURI(first)); |
| try { |
| Files.createSymbolicLink(newLink, target); |
| return file; |
| } catch (IOException ex) { |
| return null; |
| } |
| } else { |
| file.mkdir(); |
| parent = file; |
| if (i == 1) { |
| first = file; |
| } |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Test directory tree with a indirect cyclic link. |
| * <pre> |
| * a |
| * b |
| * c ( -> a) |
| * d |
| * e ( -> c) |
| * </pre> |
| * |
| * @throws java.io.IOException |
| */ |
| public void testIndirectSymbolicLink() throws IOException { |
| File a = new File(getWorkDir(), "a"); |
| File b = new File(a, "b"); |
| File c = new File(b, "c"); |
| File d = new File(a, "d"); |
| File e = new File(d, "e"); |
| assertTrue(b.mkdirs()); |
| assertTrue(d.mkdirs()); |
| try { |
| Files.createSymbolicLink(c.toPath(), a.toPath()); |
| Files.createSymbolicLink(e.toPath(), c.toPath()); |
| FileObject fo = FileUtil.toFileObject(getWorkDir()); |
| assertEquals(3, countRecursiveChildren(fo)); |
| } catch (IOException ex) { |
| printSkipped(); |
| } |
| } |
| |
| /** |
| * Test allowed (non-cyclic) symlink. |
| * <pre> |
| * a |
| * b |
| * c ( -> d) |
| * d |
| * e |
| * f |
| * </pre> |
| * |
| * @throws java.io.IOException |
| */ |
| public void testAllowedSymbolicLink() throws IOException { |
| File a = new File(getWorkDir(), "a"); |
| File b = new File(a, "b"); |
| File c = new File(b, "c"); |
| File d = new File(a, "d"); |
| File e = new File(d, "e"); |
| File f = new File(e, "f"); |
| assertTrue(b.mkdirs()); |
| assertTrue(f.mkdirs()); |
| try { |
| Files.createSymbolicLink(c.toPath(), d.toPath()); |
| assertEquals(3, countRecursiveChildren(FileUtil.toFileObject(b))); |
| } catch (IOException ex) { |
| printSkipped(); |
| } |
| } |
| |
| /** |
| * Delete all child files and directories in a directory. |
| */ |
| private void deleteChildren(File root) throws IOException { |
| for (File f : root.listFiles()) { |
| deleteFile(f); |
| } |
| } |
| |
| /** |
| * Recursively delete directory, do not throw an exception if a file cannot |
| * be deleted, do not traverse symbolic links. |
| */ |
| private static void deleteFile(File file) throws IOException { |
| if (file.isDirectory() && file.equals(file.getCanonicalFile()) |
| && !Files.isSymbolicLink(file.toPath())) { |
| // file is a directory - delete sub files first |
| File files[] = file.listFiles(); |
| for (File file1 : files) { |
| deleteFile(file1); |
| } |
| } |
| // file is a File :-) |
| file.delete(); |
| } |
| } |