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}