001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018 019package org.apache.hadoop.util; 020 021import java.io.File; 022import java.io.IOException; 023import java.nio.file.DirectoryStream; 024import java.nio.file.DirectoryIteratorException; 025import java.nio.file.Files; 026 027import org.apache.hadoop.classification.InterfaceAudience; 028import org.apache.hadoop.classification.InterfaceStability; 029import org.apache.hadoop.fs.FileUtil; 030import org.apache.hadoop.fs.LocalFileSystem; 031import org.apache.hadoop.fs.Path; 032import org.apache.hadoop.fs.permission.FsPermission; 033 034/** 035 * Class that provides utility functions for checking disk problem 036 */ 037@InterfaceAudience.Private 038@InterfaceStability.Unstable 039public class DiskChecker { 040 public static class DiskErrorException extends IOException { 041 public DiskErrorException(String msg) { 042 super(msg); 043 } 044 045 public DiskErrorException(String msg, Throwable cause) { 046 super(msg, cause); 047 } 048 } 049 050 public static class DiskOutOfSpaceException extends IOException { 051 public DiskOutOfSpaceException(String msg) { 052 super(msg); 053 } 054 } 055 056 /** 057 * The semantics of mkdirsWithExistsCheck method is different from the mkdirs 058 * method provided in the Sun's java.io.File class in the following way: 059 * While creating the non-existent parent directories, this method checks for 060 * the existence of those directories if the mkdir fails at any point (since 061 * that directory might have just been created by some other process). 062 * If both mkdir() and the exists() check fails for any seemingly 063 * non-existent directory, then we signal an error; Sun's mkdir would signal 064 * an error (return false) if a directory it is attempting to create already 065 * exists or the mkdir fails. 066 * @param dir 067 * @return true on success, false on failure 068 */ 069 public static boolean mkdirsWithExistsCheck(File dir) { 070 if (dir.mkdir() || dir.exists()) { 071 return true; 072 } 073 File canonDir = null; 074 try { 075 canonDir = dir.getCanonicalFile(); 076 } catch (IOException e) { 077 return false; 078 } 079 String parent = canonDir.getParent(); 080 return (parent != null) && 081 (mkdirsWithExistsCheck(new File(parent)) && 082 (canonDir.mkdir() || canonDir.exists())); 083 } 084 085 /** 086 * Recurse down a directory tree, checking all child directories. 087 * @param dir 088 * @throws DiskErrorException 089 */ 090 public static void checkDirs(File dir) throws DiskErrorException { 091 checkDir(dir); 092 IOException ex = null; 093 try (DirectoryStream<java.nio.file.Path> stream = 094 Files.newDirectoryStream(dir.toPath())) { 095 for (java.nio.file.Path entry: stream) { 096 File child = entry.toFile(); 097 if (child.isDirectory()) { 098 checkDirs(child); 099 } 100 } 101 } catch (DirectoryIteratorException de) { 102 ex = de.getCause(); 103 } catch (IOException ie) { 104 ex = ie; 105 } 106 if (ex != null) { 107 throw new DiskErrorException("I/O error when open a directory: " 108 + dir.toString(), ex); 109 } 110 } 111 112 /** 113 * Create the directory if it doesn't exist and check that dir is readable, 114 * writable and executable 115 * 116 * @param dir 117 * @throws DiskErrorException 118 */ 119 public static void checkDir(File dir) throws DiskErrorException { 120 if (!mkdirsWithExistsCheck(dir)) { 121 throw new DiskErrorException("Cannot create directory: " 122 + dir.toString()); 123 } 124 checkDirAccess(dir); 125 } 126 127 /** 128 * Create the directory or check permissions if it already exists. 129 * 130 * The semantics of mkdirsWithExistsAndPermissionCheck method is different 131 * from the mkdirs method provided in the Sun's java.io.File class in the 132 * following way: 133 * While creating the non-existent parent directories, this method checks for 134 * the existence of those directories if the mkdir fails at any point (since 135 * that directory might have just been created by some other process). 136 * If both mkdir() and the exists() check fails for any seemingly 137 * non-existent directory, then we signal an error; Sun's mkdir would signal 138 * an error (return false) if a directory it is attempting to create already 139 * exists or the mkdir fails. 140 * 141 * @param localFS local filesystem 142 * @param dir directory to be created or checked 143 * @param expected expected permission 144 * @throws IOException 145 */ 146 public static void mkdirsWithExistsAndPermissionCheck( 147 LocalFileSystem localFS, Path dir, FsPermission expected) 148 throws IOException { 149 File directory = localFS.pathToFile(dir); 150 boolean created = false; 151 152 if (!directory.exists()) 153 created = mkdirsWithExistsCheck(directory); 154 155 if (created || !localFS.getFileStatus(dir).getPermission().equals(expected)) 156 localFS.setPermission(dir, expected); 157 } 158 159 /** 160 * Create the local directory if necessary, check permissions and also ensure 161 * it can be read from and written into. 162 * 163 * @param localFS local filesystem 164 * @param dir directory 165 * @param expected permission 166 * @throws DiskErrorException 167 * @throws IOException 168 */ 169 public static void checkDir(LocalFileSystem localFS, Path dir, 170 FsPermission expected) 171 throws DiskErrorException, IOException { 172 mkdirsWithExistsAndPermissionCheck(localFS, dir, expected); 173 checkDirAccess(localFS.pathToFile(dir)); 174 } 175 176 /** 177 * Checks that the given file is a directory and that the current running 178 * process can read, write, and execute it. 179 * 180 * @param dir File to check 181 * @throws DiskErrorException if dir is not a directory, not readable, not 182 * writable, or not executable 183 */ 184 private static void checkDirAccess(File dir) throws DiskErrorException { 185 if (!dir.isDirectory()) { 186 throw new DiskErrorException("Not a directory: " 187 + dir.toString()); 188 } 189 190 checkAccessByFileMethods(dir); 191 } 192 193 /** 194 * Checks that the current running process can read, write, and execute the 195 * given directory by using methods of the File object. 196 * 197 * @param dir File to check 198 * @throws DiskErrorException if dir is not readable, not writable, or not 199 * executable 200 */ 201 private static void checkAccessByFileMethods(File dir) 202 throws DiskErrorException { 203 if (!FileUtil.canRead(dir)) { 204 throw new DiskErrorException("Directory is not readable: " 205 + dir.toString()); 206 } 207 208 if (!FileUtil.canWrite(dir)) { 209 throw new DiskErrorException("Directory is not writable: " 210 + dir.toString()); 211 } 212 213 if (!FileUtil.canExecute(dir)) { 214 throw new DiskErrorException("Directory is not executable: " 215 + dir.toString()); 216 } 217 } 218}