FileUtils.java revision 2636:abbba823f850
1/*
2 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package jdk.test.lib.util;
25
26import jdk.test.lib.Platform;
27
28import java.io.IOException;
29import java.nio.file.DirectoryNotEmptyException;
30import java.nio.file.FileVisitResult;
31import java.nio.file.Files;
32import java.nio.file.NoSuchFileException;
33import java.nio.file.Path;
34import java.nio.file.SimpleFileVisitor;
35import java.nio.file.attribute.BasicFileAttributes;
36import java.util.ArrayList;
37import java.util.List;
38import java.util.concurrent.TimeUnit;
39
40
41/**
42 * Common library for various test file utility functions.
43 */
44public final class FileUtils {
45    private static final boolean IS_WINDOWS = Platform.isWindows();
46    private static final int RETRY_DELETE_MILLIS = IS_WINDOWS ? 500 : 0;
47    private static final int MAX_RETRY_DELETE_TIMES = IS_WINDOWS ? 15 : 0;
48
49    /**
50     * Deletes a file, retrying if necessary.
51     *
52     * @param path  the file to delete
53     *
54     * @throws NoSuchFileException
55     *         if the file does not exist (optional specific exception)
56     * @throws DirectoryNotEmptyException
57     *         if the file is a directory and could not otherwise be deleted
58     *         because the directory is not empty (optional specific exception)
59     * @throws IOException
60     *         if an I/O error occurs
61     */
62    public static void deleteFileWithRetry(Path path) throws IOException {
63        try {
64            deleteFileWithRetry0(path);
65        } catch (InterruptedException x) {
66            throw new IOException("Interrupted while deleting.", x);
67        }
68    }
69
70    /**
71     * Deletes a file, retrying if necessary.
72     * No exception thrown if file doesn't exist.
73     *
74     * @param path  the file to delete
75     *
76     * @throws NoSuchFileException
77     *         if the file does not exist (optional specific exception)
78     * @throws DirectoryNotEmptyException
79     *         if the file is a directory and could not otherwise be deleted
80     *         because the directory is not empty (optional specific exception)
81     * @throws IOException
82     *         if an I/O error occurs
83     */
84    public static void deleteFileIfExistsWithRetry(Path path) throws IOException {
85        try {
86            if (Files.exists(path)) {
87                deleteFileWithRetry0(path);
88            }
89        } catch (InterruptedException x) {
90            throw new IOException("Interrupted while deleting.", x);
91        }
92    }
93
94    private static void deleteFileWithRetry0(Path path)
95            throws IOException, InterruptedException {
96        int times = 0;
97        IOException ioe = null;
98        while (true) {
99            try {
100                Files.delete(path);
101                while (Files.exists(path)) {
102                    times++;
103                    if (times > MAX_RETRY_DELETE_TIMES) {
104                        throw new IOException("File still exists after " + times + " waits.");
105                    }
106                    Thread.sleep(RETRY_DELETE_MILLIS);
107                }
108                break;
109            } catch (NoSuchFileException | DirectoryNotEmptyException x) {
110                throw x;
111            } catch (IOException x) {
112                // Backoff/retry in case another process is accessing the file
113                times++;
114                if (ioe == null) {
115                    ioe = x;
116                } else {
117                    ioe.addSuppressed(x);
118                }
119
120                if (times > MAX_RETRY_DELETE_TIMES) {
121                    throw ioe;
122                }
123                Thread.sleep(RETRY_DELETE_MILLIS);
124            }
125        }
126    }
127
128    /**
129     * Deletes a directory and its subdirectories, retrying if necessary.
130     *
131     * @param dir  the directory to delete
132     *
133     * @throws  IOException
134     *          If an I/O error occurs. Any such exceptions are caught
135     *          internally. If only one is caught, then it is re-thrown.
136     *          If more than one exception is caught, then the second and
137     *          following exceptions are added as suppressed exceptions of the
138     *          first one caught, which is then re-thrown.
139     */
140    public static void deleteFileTreeWithRetry(Path dir) throws IOException {
141        IOException ioe = null;
142        final List<IOException> excs = deleteFileTreeUnchecked(dir);
143        if (!excs.isEmpty()) {
144            ioe = excs.remove(0);
145            for (IOException x : excs) {
146                ioe.addSuppressed(x);
147            }
148        }
149        if (ioe != null) {
150            throw ioe;
151        }
152    }
153
154    public static List<IOException> deleteFileTreeUnchecked(Path dir) {
155        final List<IOException> excs = new ArrayList<>();
156        try {
157            java.nio.file.Files.walkFileTree(dir, new SimpleFileVisitor<>() {
158                @Override
159                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
160                    try {
161                        deleteFileWithRetry0(file);
162                    } catch (IOException x) {
163                        excs.add(x);
164                    } catch (InterruptedException x) {
165                        excs.add(new IOException("Interrupted while deleting.", x));
166                        return FileVisitResult.TERMINATE;
167                    }
168                    return FileVisitResult.CONTINUE;
169                }
170                @Override
171                public FileVisitResult postVisitDirectory(Path dir, IOException exc) {
172                    try {
173                        deleteFileWithRetry0(dir);
174                    } catch (IOException x) {
175                        excs.add(x);
176                    } catch (InterruptedException x) {
177                        excs.add(new IOException("Interrupted while deleting.", x));
178                        return FileVisitResult.TERMINATE;
179                    }
180                    return FileVisitResult.CONTINUE;
181                }
182                @Override
183                public FileVisitResult visitFileFailed(Path file, IOException exc) {
184                    excs.add(exc);
185                    return FileVisitResult.CONTINUE;
186                }
187            });
188        } catch (IOException x) {
189            excs.add(x);
190        }
191        return excs;
192    }
193
194    /**
195     * Checks whether all file systems are accessible. This is performed
196     * by checking free disk space on all mounted file systems via a
197     * separate, spawned process. File systems are considered to be
198     * accessible if this process completes successfully before a given
199     * fixed duration has elapsed.
200     *
201     * @implNote On Unix this executes the {@code df} command in a separate
202     * process and on Windows always returns {@code true}.
203     */
204    public static boolean areFileSystemsAccessible() throws IOException {
205        boolean areFileSystemsAccessible = true;
206        if (!IS_WINDOWS) {
207            // try to check whether 'df' hangs
208            System.out.println("\n--- df output ---");
209            System.out.flush();
210            Process proc = new ProcessBuilder("df").inheritIO().start();
211            try {
212                proc.waitFor(90, TimeUnit.SECONDS);
213            } catch (InterruptedException ignored) {
214            }
215            try {
216                int exitValue = proc.exitValue();
217                if (exitValue != 0) {
218                    System.err.printf("df process exited with %d != 0%n",
219                        exitValue);
220                    areFileSystemsAccessible = false;
221                }
222            } catch (IllegalThreadStateException ignored) {
223                System.err.println("df command apparently hung");
224                areFileSystemsAccessible = false;
225            }
226        }
227        return areFileSystemsAccessible;
228    }
229}
230