1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.android.timezone.distro;
17 
18 import java.io.File;
19 import java.io.FileInputStream;
20 import java.io.FileNotFoundException;
21 import java.io.FileOutputStream;
22 import java.io.IOException;
23 import java.util.Arrays;
24 import java.util.LinkedList;
25 
26 /**
27  * Utility methods for files operations.
28  */
29 public final class FileUtils {
30 
FileUtils()31     private FileUtils() {
32     }
33 
34     /**
35      * Creates a new {@link java.io.File} from the {@code parentDir} and {@code name}, but only if
36      * the resulting file would exist beneath {@code parentDir}. Useful if {@code name} could
37      * contain "/../" or symlinks. The returned object has a canonicalized path.
38      *
39      * @throws java.io.IOException if the file would not exist beneath {@code parentDir}
40      */
createSubFile(File parentDir, String name)41     public static File createSubFile(File parentDir, String name) throws IOException {
42         // The subFile must exist beneath parentDir. If name contains "/../" this may not be the
43         // case so we check.
44         File subFile = new File(parentDir, name).getCanonicalFile();
45         if (!subFile.getPath().startsWith(parentDir.getCanonicalPath())) {
46             throw new IOException(name + " must exist beneath " + parentDir +
47                     ". Canonicalized subpath: " + subFile);
48         }
49         return subFile;
50     }
51 
52     /**
53      * Makes sure a directory exists. If it doesn't exist, it is created. Parent directories are
54      * also created as needed. If {@code makeWorldReadable} is {@code true} the directory's default
55      * permissions will be set. Even when {@code makeWorldReadable} is {@code true}, only
56      * directories explicitly created will have their permissions set; existing directories are
57      * untouched.
58      *
59      * @throws IOException if the directory or one of its parents did not already exist and could
60      *     not be created
61      */
ensureDirectoriesExist(File dir, boolean makeWorldReadable)62     public static void ensureDirectoriesExist(File dir, boolean makeWorldReadable)
63             throws IOException {
64         LinkedList<File> dirs = new LinkedList<>();
65         File currentDir = dir;
66         do {
67             dirs.addFirst(currentDir);
68             currentDir = currentDir.getParentFile();
69         } while (currentDir != null);
70 
71         for (File dirToCheck : dirs) {
72             if (!dirToCheck.exists()) {
73                 if (!dirToCheck.mkdir()) {
74                     throw new IOException("Unable to create directory: " + dir);
75                 }
76                 if (makeWorldReadable) {
77                     makeDirectoryWorldAccessible(dirToCheck);
78                 }
79             } else if (!dirToCheck.isDirectory()) {
80                 throw new IOException(dirToCheck + " exists but is not a directory");
81             }
82         }
83     }
84 
makeDirectoryWorldAccessible(File directory)85     public static void makeDirectoryWorldAccessible(File directory) throws IOException {
86         if (!directory.isDirectory()) {
87             throw new IOException(directory + " must be a directory");
88         }
89         makeWorldReadable(directory);
90         if (!directory.setExecutable(true, false /* ownerOnly */)) {
91             throw new IOException("Unable to make " + directory + " world-executable");
92         }
93     }
94 
makeWorldReadable(File file)95     public static void makeWorldReadable(File file) throws IOException {
96         if (!file.setReadable(true, false /* ownerOnly */)) {
97             throw new IOException("Unable to make " + file + " world-readable");
98         }
99     }
100 
rename(File from, File to)101     public static void rename(File from, File to) throws IOException {
102         ensureFileDoesNotExist(to);
103         if (!from.renameTo(to)) {
104             throw new IOException("Unable to rename " + from + " to " + to);
105         }
106     }
107 
ensureFileDoesNotExist(File file)108     public static void ensureFileDoesNotExist(File file) throws IOException {
109         if (file.exists()) {
110             if (!file.isFile()) {
111                 throw new IOException(file + " is not a file");
112             }
113             doDelete(file);
114         }
115     }
116 
doDelete(File file)117     public static void doDelete(File file) throws IOException {
118         if (!file.delete()) {
119             throw new IOException("Unable to delete: " + file);
120         }
121     }
122 
isSymlink(File file)123     public static boolean isSymlink(File file) throws IOException {
124         String baseName = file.getName();
125         String canonicalPathExceptBaseName =
126                 new File(file.getParentFile().getCanonicalFile(), baseName).getPath();
127         return !file.getCanonicalPath().equals(canonicalPathExceptBaseName);
128     }
129 
deleteRecursive(File toDelete)130     public static void deleteRecursive(File toDelete) throws IOException {
131         if (toDelete.isDirectory()) {
132             for (File file : toDelete.listFiles()) {
133                 if (file.isDirectory() && !FileUtils.isSymlink(file)) {
134                     // The isSymlink() check is important so that we don't delete files in other
135                     // directories: only the symlink itself.
136                     deleteRecursive(file);
137                 } else {
138                     // Delete symlinks to directories or files.
139                     FileUtils.doDelete(file);
140                 }
141             }
142             String[] remainingFiles = toDelete.list();
143             if (remainingFiles.length != 0) {
144                 throw new IOException("Unable to delete files: " + Arrays
145                         .toString(remainingFiles));
146             }
147         }
148         FileUtils.doDelete(toDelete);
149     }
150 
filesExist(File rootDir, String... fileNames)151     public static boolean filesExist(File rootDir, String... fileNames) {
152         for (String fileName : fileNames) {
153             File file = new File(rootDir, fileName);
154             if (!file.exists()) {
155                 return false;
156             }
157         }
158         return true;
159     }
160 
161     /**
162      * Reads up to {@code maxBytes} bytes from the specified file. The returned array can be
163      * shorter than {@code maxBytes} if the file is shorter.
164      */
readBytes(File file, int maxBytes)165     public static byte[] readBytes(File file, int maxBytes) throws IOException {
166         if (maxBytes <= 0) {
167             throw new IllegalArgumentException("maxBytes ==" + maxBytes);
168         }
169 
170         try (FileInputStream in = new FileInputStream(file)) {
171             byte[] max = new byte[maxBytes];
172             int bytesRead = in.read(max, 0, maxBytes);
173             byte[] toReturn = new byte[bytesRead];
174             System.arraycopy(max, 0, toReturn, 0, bytesRead);
175             return toReturn;
176         }
177     }
178 
179     /**
180      * Creates an empty file.
181      *
182      * @param file the file to create
183      * @throws IOException if the file cannot be created
184      */
createEmptyFile(File file)185     public static void createEmptyFile(File file) throws IOException {
186         new FileOutputStream(file, false /* append */).close();
187     }
188 }
189