| /* | |
| * Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved. | |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. | |
| * | |
| * This code is free software; you can redistribute it and/or modify it | |
| * under the terms of the GNU General Public License version 2 only, as | |
| * published by the Free Software Foundation. | |
| * | |
| * This code is distributed in the hope that it will be useful, but WITHOUT | |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License | |
| * version 2 for more details (a copy is included in the LICENSE file that | |
| * accompanied this code). | |
| * | |
| * You should have received a copy of the GNU General Public License version | |
| * 2 along with this work; if not, write to the Free Software Foundation, | |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. | |
| * | |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA | |
| * or visit www.oracle.com if you need additional information or have any | |
| * questions. | |
| */ | |
| /* @test | |
| * @bug 8073061 | |
| * @requires (os.family == "linux") | (os.family == "mac") | |
| * @summary Test Files.copy and Files.move with numerous parameters | |
| * @run junit CopyMoveVariations | |
| */ | |
| import java.io.IOException; | |
| import java.io.UncheckedIOException; | |
| import java.nio.file.AccessDeniedException; | |
| import java.nio.file.CopyOption; | |
| import java.nio.file.FileAlreadyExistsException; | |
| import java.nio.file.Files; | |
| import java.nio.file.FileStore; | |
| import java.nio.file.LinkOption; | |
| import java.nio.file.Path; | |
| import java.nio.file.attribute.PosixFilePermission; | |
| import java.nio.file.attribute.PosixFilePermissions; | |
| import java.util.ArrayList; | |
| import java.util.HashSet; | |
| import java.util.List; | |
| import java.util.Set; | |
| import java.util.stream.Stream; | |
| import static java.nio.file.LinkOption.*; | |
| import static java.nio.file.StandardCopyOption.*; | |
| import org.junit.jupiter.api.Test; | |
| import org.junit.jupiter.api.condition.EnabledIf; | |
| import org.junit.jupiter.params.ParameterizedTest; | |
| import org.junit.jupiter.params.provider.Arguments; | |
| import org.junit.jupiter.params.provider.MethodSource; | |
| import org.junit.jupiter.params.provider.ValueSource; | |
| import static org.junit.jupiter.api.Assertions.*; | |
| public class CopyMoveVariations { | |
| enum OpType { | |
| COPY, | |
| MOVE | |
| } | |
| enum PathType { | |
| FILE, | |
| DIR, | |
| LINK | |
| } | |
| private static final boolean SUPPORTS_POSIX_PERMISSIONS; | |
| private static final String TMP_DIR = | |
| System.getProperty("copymove.tmp.dir", "."); | |
| static { | |
| Path tempFile = null; | |
| try { | |
| tempFile = Files.createTempFile(Path.of(TMP_DIR), "this", "that"); | |
| SUPPORTS_POSIX_PERMISSIONS = | |
| Files.getFileStore(tempFile).supportsFileAttributeView("posix"); | |
| } catch (IOException cause) { | |
| throw new UncheckedIOException(cause); | |
| } finally { | |
| if (tempFile != null) { | |
| try { | |
| Files.delete(tempFile); | |
| } catch (IOException ignore) { | |
| } | |
| } | |
| } | |
| } | |
| private static boolean supportsPosixPermissions() { | |
| return SUPPORTS_POSIX_PERMISSIONS; | |
| } | |
| private static boolean isSameFileStore(Path p1, Path p2) | |
| throws IOException { | |
| FileStore fs1 = p1.getFileSystem().provider().getFileStore(p1); | |
| FileStore fs2 = p2.getFileSystem().provider().getFileStore(p2); | |
| return fs1.equals(fs2); | |
| } | |
| private static Stream<Arguments> params() { | |
| List<Arguments> list = new ArrayList<Arguments>(); | |
| boolean[] falseAndTrue = new boolean[] {false, true}; | |
| for (PathType type : PathType.values()) { | |
| String[] modes = new String[] { | |
| "---------", "r--r--r--", "-w--w--w-", "rw-rw-rw-" | |
| }; | |
| for (String mode : modes) { | |
| for (boolean replaceExisting : falseAndTrue) { | |
| for (boolean targetExists : falseAndTrue) { | |
| list.add(Arguments.of(type, mode, replaceExisting, | |
| targetExists)); | |
| } | |
| } | |
| } | |
| } | |
| return list.stream(); | |
| } | |
| @ParameterizedTest | |
| @EnabledIf("supportsPosixPermissions") | |
| @MethodSource("params") | |
| void copyFollow(PathType type, String mode, boolean replaceExisting, | |
| boolean targetExists) throws IOException { | |
| op(OpType.COPY, type, mode, replaceExisting, targetExists, true); | |
| } | |
| @ParameterizedTest | |
| @EnabledIf("supportsPosixPermissions") | |
| @MethodSource("params") | |
| void copyNoFollow(PathType type, String mode, boolean replaceExisting, | |
| boolean targetExists) throws IOException { | |
| op(OpType.COPY, type, mode, replaceExisting, targetExists, false); | |
| } | |
| @ParameterizedTest | |
| @EnabledIf("supportsPosixPermissions") | |
| @MethodSource("params") | |
| void move(PathType type, String mode, boolean replaceExisting, | |
| boolean targetExists) throws IOException { | |
| op(OpType.MOVE, type, mode, replaceExisting, targetExists, false); | |
| } | |
| void op(OpType op, PathType type, String mode, boolean replaceExisting, | |
| boolean targetExists, boolean followLinks) throws IOException { | |
| Path source = null; | |
| Path target = null; | |
| Path linkTarget = null; | |
| Path tmpDir = Path.of(TMP_DIR); | |
| try { | |
| switch (type) { | |
| case FILE -> | |
| source = Files.createTempFile(tmpDir, "file", "dat"); | |
| case DIR -> | |
| source = Files.createTempDirectory(tmpDir, "dir"); | |
| case LINK -> { | |
| linkTarget = Files.createTempFile(tmpDir, "link", "target"); | |
| Path link = Path.of("link"); | |
| source = Files.createSymbolicLink(link, linkTarget); | |
| } | |
| } | |
| Set<PosixFilePermission> perms = | |
| PosixFilePermissions.fromString(mode); | |
| if (op == OpType.COPY && type == PathType.LINK && followLinks) | |
| Files.setPosixFilePermissions(linkTarget, perms); | |
| else | |
| Files.setPosixFilePermissions(source, perms); | |
| if (targetExists) | |
| target = Files.createTempFile(tmpDir, "file", "target"); | |
| else | |
| target = Path.of("target"); | |
| Set<CopyOption> optionSet = new HashSet(); | |
| if (replaceExisting) | |
| optionSet.add(REPLACE_EXISTING); | |
| if (op == OpType.COPY && !followLinks) | |
| optionSet.add(NOFOLLOW_LINKS); | |
| CopyOption[] options = optionSet.toArray(new CopyOption[0]); | |
| final Path src = source; | |
| final Path dst = target; | |
| if (type == PathType.FILE) { | |
| if (op == OpType.COPY) { | |
| try { | |
| Files.copy(source, target, options); | |
| assert Files.exists(target); | |
| } catch (AccessDeniedException ade) { | |
| assertTrue(mode.charAt(0) != 'r'); | |
| } catch (FileAlreadyExistsException faee) { | |
| assertTrue(targetExists && !replaceExisting); | |
| } | |
| if (targetExists && mode.charAt(0) == '-') | |
| assertTrue(Files.exists(target)); | |
| } else if (!replaceExisting && targetExists) { | |
| assertThrows(FileAlreadyExistsException.class, | |
| () -> Files.move(src, dst, options)); | |
| } else { | |
| try { | |
| Files.move(source, target, options); | |
| } catch (AccessDeniedException ade) { | |
| assertTrue(mode.charAt(0) != 'r'); | |
| } | |
| assert Files.exists(target); | |
| } | |
| } else if (type == PathType.DIR) { | |
| if (op == OpType.COPY) { | |
| try { | |
| Files.copy(source, target, options); | |
| assert Files.exists(target); | |
| } catch (AccessDeniedException ade) { | |
| assertTrue(mode.charAt(0) != 'r'); | |
| } catch (FileAlreadyExistsException faee) { | |
| assertTrue(targetExists && !replaceExisting); | |
| } | |
| if (targetExists && mode.charAt(0) == '-') | |
| assertTrue(Files.exists(target)); | |
| } else { | |
| try { | |
| Files.move(source, target, options); | |
| assert Files.exists(target); | |
| } catch (AccessDeniedException ade) { | |
| Path other = target.getParent(); | |
| if (other == null) | |
| other = Path.of(System.getProperty("user.dir")); | |
| if (isSameFileStore(source, other)) { | |
| // directories on same store should be renamed | |
| assertTrue(mode.charAt(1) != 'w'); | |
| } else { | |
| // directories on different stores will likely be | |
| // moved by a copy which requires read permission | |
| if (mode.charAt(1) == 'w') | |
| assertTrue(mode.charAt(0) != 'r'); | |
| else | |
| assertTrue(mode.charAt(1) != 'w'); | |
| } | |
| } catch (FileAlreadyExistsException faee) { | |
| assertTrue(targetExists && !replaceExisting); | |
| } | |
| } | |
| } else if (type == PathType.LINK) { | |
| if (op == OpType.COPY) { | |
| try { | |
| Files.copy(source, target, options); | |
| assert Files.exists(target); | |
| } catch (AccessDeniedException ade) { | |
| assertTrue(mode.charAt(0) != 'r'); | |
| } catch (FileAlreadyExistsException faee) { | |
| assertTrue(targetExists && !replaceExisting); | |
| } | |
| } else { | |
| try { | |
| Files.move(source, target, options); | |
| assert Files.exists(target); | |
| } catch (AccessDeniedException ade) { | |
| assertTrue(mode.charAt(0) != 'r'); | |
| } catch (FileAlreadyExistsException faee) { | |
| assertTrue(targetExists && !replaceExisting); | |
| } | |
| } | |
| } else { | |
| assert false; | |
| } | |
| } finally { | |
| try { | |
| if (source != null) | |
| Files.deleteIfExists(source); | |
| } catch (IOException x) { | |
| } | |
| try { | |
| if (target != null) | |
| Files.deleteIfExists(target); | |
| } catch (IOException x) { | |
| } | |
| try { | |
| if (linkTarget != null) | |
| Files.deleteIfExists(linkTarget); | |
| } catch (IOException x) { | |
| } | |
| } | |
| } | |
| } |