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 
29 import java.io.*;
30 import java.util.*;
31 import java.nio.file.*;
32 
33 
34 public 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 */
gensym()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      */
initTestData(int depth)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      */
findSomeFile(String dir, String subdir, String[] dl)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      */
findSomeFile(String dir, boolean create)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      */
findSomeDir(String dir, boolean create)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 */
findNon(String dir)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 */
ensureNon(String fn)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 */
isSlash(char x)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      */
trimTrailingSlashes(String s)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 */
pathConcat(String a, String b)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      */
check(String answer, String path)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 */
checkSlash(int depth, boolean create, String ans, String ask, String slash)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 */
checkSlashes(int depth, boolean create, String ans, String ask)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 */
checkNames(int depth, boolean create, String ans, String ask)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