1/*
2 * Copyright (c) 1998, 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
24/*
25   @summary Common definitions for general exhaustive pathname tests
26   @author  Mark Reinhold
27 */
28
29import java.io.*;
30import java.util.*;
31import java.nio.file.*;
32
33
34public class General {
35
36    public static boolean debug = false;
37
38    private static boolean win32 = (File.separatorChar == '\\');
39
40    private static int gensymCounter = 0;
41
42    protected static final String userDir = System.getProperty("user.dir");
43    protected static final String workSubDir = "tmp";
44
45    protected static String baseDir = null;
46    protected static String relative = null;
47
48    /* Generate a filename unique to this run */
49    private static String gensym() {
50        return "x." + ++gensymCounter;
51    }
52
53    /**
54     * Create files and folders in the test working directory.
55     * The purpose is to make sure the test will not go out of
56     * its user dir when walking the file tree.
57     *
58     * @param  depth    The number of directory levels to be created under
59     *                  the user directory. It should be the maximum value
60     *                  of the depths passed to checkNames method (including
61     *                  direct or indirect calling) in a whole test.
62     */
63    protected static void initTestData(int depth) throws IOException {
64        File parent = new File(userDir + File.separator + workSubDir);
65        if (!parent.mkdir()) {
66            throw new IOException("Fail to create directory: " + parent);
67        }
68        for (int i = 0; i < depth; i++) {
69            File tmp = new File(parent, gensym());
70            tmp.createNewFile();
71            tmp = new File(parent, gensym());
72            if (tmp.mkdir())
73                parent = tmp;
74            else
75                throw new IOException("Fail to create directory, " + tmp);
76        }
77        baseDir = parent.getAbsolutePath();
78        relative = baseDir.substring(userDir.length() + 1);
79    }
80
81    /**
82     * Find a file in the given subdirectory, or descend into further
83     * subdirectories, if any, if no file is found here.  Return null if no
84     * file can be found anywhere beneath the given subdirectory.
85     * @param  dir     Directory at which we started
86     * @param  subdir  Subdirectory that we're exploring
87     * @param  dl      Listing of subdirectory
88     */
89    private static String findSomeFile(String dir, String subdir, String[] dl) {
90        for (int i = 0; i < dl.length; i++) {
91            File f = new File(subdir, dl[i]);
92            File df = new File(dir, f.getPath());
93            if (Files.isRegularFile(df.toPath(), LinkOption.NOFOLLOW_LINKS)) {
94                return f.getPath();
95            }
96        }
97        for (int i = 0; i < dl.length; i++) {
98            File f = (subdir.length() == 0) ? new File(dl[i])
99                                            : new File(subdir, dl[i]);
100            File df = new File(dir, f.getPath());
101            if (Files.isDirectory(df.toPath(), LinkOption.NOFOLLOW_LINKS)) {
102                String[] dl2 = df.list();
103                if (dl2 != null) {
104                    String ff = findSomeFile(dir, f.getPath(), dl2);
105                    if (ff != null) return ff;
106                }
107            }
108        }
109        return null;
110    }
111
112
113    /**
114     * Construct a string that names a file in the given directory.  If create
115     * is true, then create a file if none is found, and throw an exception if
116     * that is not possible; otherwise, return null if no file can be found.
117     */
118    private static String findSomeFile(String dir, boolean create) {
119        File d = new File(dir);
120        String[] dl = d.list();
121        if (dl == null) {
122            throw new RuntimeException("Can't list " + dir);
123        }
124        for (int i = 0; i < dl.length; i++) {
125            File f = new File(dir, dl[i]);
126            if (Files.isRegularFile(f.toPath(), LinkOption.NOFOLLOW_LINKS)) {
127                return dl[i];
128            }
129        }
130        String f = findSomeFile(dir, "", dl);
131        if (f != null) {
132            return f;
133        }
134        if (create) {
135            File nf = new File(d, gensym());
136            OutputStream os;
137            try {
138                os = new FileOutputStream(nf);
139                os.close();
140            } catch (IOException x) {
141                throw new RuntimeException("Can't create a file in " + dir);
142            }
143            return nf.getName();
144        }
145        return null;
146    }
147
148
149    /**
150     * Construct a string that names a subdirectory of the given directory.
151     * If create is true, then create a subdirectory if none is found, and
152     * throw an exception if that is not possible; otherwise, return null if
153     * no subdirectory can be found.
154     */
155    private static String findSomeDir(String dir, boolean create) {
156        File d = new File(dir);
157        String[] dl = d.list();
158        if (dl == null) {
159            throw new RuntimeException("Can't list " + dir);
160        }
161        for (int i = 0; i < dl.length; i++) {
162            File f = new File(d, dl[i]);
163            if (Files.isDirectory(f.toPath(), LinkOption.NOFOLLOW_LINKS)) {
164                String[] dl2 = f.list();
165                if (dl2 == null || dl2.length >= 250) {
166                    /* Heuristic to avoid scanning huge directories */
167                    continue;
168                }
169                return dl[i];
170            }
171        }
172        if (create) {
173            File sd = new File(d, gensym());
174            if (sd.mkdir()) return sd.getName();
175        }
176        return null;
177    }
178
179
180    /** Construct a string that does not name a file in the given directory */
181    private static String findNon(String dir) {
182        File d = new File(dir);
183        String[] x = new String[] { "foo", "bar", "baz" };
184        for (int i = 0; i < x.length; i++) {
185            File f = new File(d, x[i]);
186            if (!f.exists()) {
187                return x[i];
188            }
189        }
190        for (int i = 0; i < 1024; i++) {
191            String n = "xx" + Integer.toString(i);
192            File f = new File(d, n);
193            if (!f.exists()) {
194                return n;
195            }
196        }
197        throw new RuntimeException("Can't find a non-existent file in " + dir);
198    }
199
200
201    /** Ensure that the named file does not exist */
202    public static void ensureNon(String fn) {
203        if ((new File(fn)).exists()) {
204            throw new RuntimeException("Test path " + fn + " exists");
205        }
206    }
207
208
209    /** Tell whether the given character is a "slash" on this platform */
210    private static boolean isSlash(char x) {
211        if (x == File.separatorChar) return true;
212        if (win32 && (x == '/')) return true;
213        return false;
214    }
215
216
217    /**
218     * Trim trailing slashes from the given string, but leave singleton slashes
219     * alone (they denote root directories)
220     */
221    private static String trimTrailingSlashes(String s) {
222        int n = s.length();
223        if (n == 0) return s;
224        n--;
225        while ((n > 0) && isSlash(s.charAt(n))) {
226            if ((n >= 1) && s.charAt(n - 1) == ':') break;
227            n--;
228        }
229        return s.substring(0, n + 1);
230    }
231
232
233    /** Concatenate two paths, trimming slashes as needed */
234    private static String pathConcat(String a, String b) {
235        if (a.length() == 0) return b;
236        if (b.length() == 0) return a;
237        if (isSlash(a.charAt(a.length() - 1))
238            || isSlash(b.charAt(0))
239            || (win32 && (a.charAt(a.length() - 1) == ':'))) {
240            return a + b;
241        } else {
242            return a + File.separatorChar + b;
243        }
244    }
245
246
247
248    /** Hash table of input pathnames, used to detect duplicates */
249    private static Hashtable<String, String> checked = new Hashtable<>();
250
251    /**
252     * Check the given pathname.  Its canonical pathname should be the given
253     * answer.  If the path names a file that exists and is readable, then
254     * FileInputStream and RandomAccessFile should both be able to open it.
255     */
256    public static void check(String answer, String path) throws IOException {
257        String ans = trimTrailingSlashes(answer);
258        if (path.length() == 0) return;
259        if (checked.get(path) != null) {
260            System.err.println("DUP " + path);
261            return;
262        }
263        checked.put(path, path);
264
265        String cpath;
266        try {
267            File f = new File(path);
268            cpath = f.getCanonicalPath();
269            if (f.exists() && f.isFile() && f.canRead()) {
270                InputStream in = new FileInputStream(path);
271                in.close();
272                RandomAccessFile raf = new RandomAccessFile(path, "r");
273                raf.close();
274            }
275        } catch (IOException x) {
276            System.err.println(ans + " <-- " + path + " ==> " + x);
277            if (debug) return;
278            else throw x;
279        }
280        if (cpath.equals(ans)) {
281            System.err.println(ans + " <== " + path);
282        } else {
283            System.err.println(ans + " <-- " + path + " ==> " + cpath + " MISMATCH");
284            if (!debug) {
285                throw new RuntimeException("Mismatch: " + path + " ==> " + cpath +
286                                           ", should be " + ans);
287            }
288        }
289    }
290
291
292
293    /*
294     * The following three mutually-recursive methods generate and check a tree
295     * of filenames of arbitrary depth.  Each method has (at least) these
296     * arguments:
297     *
298     *     int depth         Remaining tree depth
299     *     boolean create    Controls whether test files and directories
300     *                       will be created as needed
301     *     String ans        Expected answer for the check method (above)
302     *     String ask        Input pathname to be passed to the check method
303     */
304
305
306    /** Check a single slash case, plus its children */
307    private static void checkSlash(int depth, boolean create,
308                                  String ans, String ask, String slash)
309        throws Exception
310    {
311        check(ans, ask + slash);
312        checkNames(depth, create,
313                   ans.endsWith(File.separator) ? ans : ans + File.separator,
314                   ask + slash);
315    }
316
317
318    /** Check slash cases for the given ask string */
319    public static void checkSlashes(int depth, boolean create,
320                                    String ans, String ask)
321        throws Exception
322    {
323        check(ans, ask);
324        if (depth == 0) return;
325
326        checkSlash(depth, create, ans, ask, "/");
327        checkSlash(depth, create, ans, ask, "//");
328        checkSlash(depth, create, ans, ask, "///");
329        if (win32) {
330            checkSlash(depth, create, ans, ask, "\\");
331            checkSlash(depth, create, ans, ask, "\\\\");
332            checkSlash(depth, create, ans, ask, "\\/");
333            checkSlash(depth, create, ans, ask, "/\\");
334            checkSlash(depth, create, ans, ask, "\\\\\\");
335        }
336    }
337
338
339    /** Check name cases for the given ask string */
340    public static void checkNames(int depth, boolean create,
341                                  String ans, String ask)
342        throws Exception
343    {
344        int d = depth - 1;
345        File f = new File(ans);
346        String n;
347
348        /* Normal name */
349        if (f.exists()) {
350            if (Files.isDirectory(f.toPath(), LinkOption.NOFOLLOW_LINKS) && f.list() != null) {
351                if ((n = findSomeFile(ans, create)) != null)
352                    checkSlashes(d, create, ans + n, ask + n);
353                if ((n = findSomeDir(ans, create)) != null)
354                    checkSlashes(d, create, ans + n, ask + n);
355            }
356            n = findNon(ans);
357            checkSlashes(d, create, ans + n, ask + n);
358        } else {
359            n = "foo" + depth;
360            checkSlashes(d, create, ans + n, ask + n);
361        }
362
363        /* "." */
364        checkSlashes(d, create, trimTrailingSlashes(ans), ask + ".");
365
366        /* ".." */
367        if ((n = f.getParent()) != null) {
368            String n2;
369            if (win32
370                && ((n2 = f.getParentFile().getParent()) != null)
371                && n2.equals("\\\\")) {
372                /* Win32 resolves \\foo\bar\.. to \\foo\bar */
373                checkSlashes(d, create, ans, ask + "..");
374            } else {
375                checkSlashes(d, create, n, ask + "..");
376            }
377        }
378        else {
379            if (win32)
380                checkSlashes(d, create, ans, ask + "..");
381            else {
382                // Fix for 4237875. We must ensure that we are sufficiently
383                // deep in the path hierarchy to test parents this high up
384                File thisPath = new File(ask);
385                File nextPath = new File(ask + "..");
386                if (!thisPath.getCanonicalPath().equals(nextPath.getCanonicalPath()))
387                    checkSlashes(d, create, ans + "..", ask + "..");
388            }
389        }
390    }
391}
392