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;
023
024import org.apache.hadoop.classification.InterfaceAudience;
025import org.apache.hadoop.classification.InterfaceStability;
026import org.apache.hadoop.fs.FileUtil;
027import org.apache.hadoop.fs.LocalFileSystem;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.fs.permission.FsPermission;
030
031/**
032 * Class that provides utility functions for checking disk problem
033 */
034@InterfaceAudience.Private
035@InterfaceStability.Unstable
036public class DiskChecker {
037  public static class DiskErrorException extends IOException {
038    public DiskErrorException(String msg) {
039      super(msg);
040    }
041
042    public DiskErrorException(String msg, Throwable cause) {
043      super(msg, cause);
044    }
045  }
046    
047  public static class DiskOutOfSpaceException extends IOException {
048    public DiskOutOfSpaceException(String msg) {
049      super(msg);
050    }
051  }
052      
053  /** 
054   * The semantics of mkdirsWithExistsCheck method is different from the mkdirs
055   * method provided in the Sun's java.io.File class in the following way:
056   * While creating the non-existent parent directories, this method checks for
057   * the existence of those directories if the mkdir fails at any point (since
058   * that directory might have just been created by some other process).
059   * If both mkdir() and the exists() check fails for any seemingly 
060   * non-existent directory, then we signal an error; Sun's mkdir would signal
061   * an error (return false) if a directory it is attempting to create already
062   * exists or the mkdir fails.
063   * @param dir
064   * @return true on success, false on failure
065   */
066  public static boolean mkdirsWithExistsCheck(File dir) {
067    if (dir.mkdir() || dir.exists()) {
068      return true;
069    }
070    File canonDir = null;
071    try {
072      canonDir = dir.getCanonicalFile();
073    } catch (IOException e) {
074      return false;
075    }
076    String parent = canonDir.getParent();
077    return (parent != null) && 
078           (mkdirsWithExistsCheck(new File(parent)) &&
079                                      (canonDir.mkdir() || canonDir.exists()));
080  }
081
082  /**
083   * Recurse down a directory tree, checking all child directories.
084   * @param dir
085   * @throws DiskErrorException
086   */
087  public static void checkDirs(File dir) throws DiskErrorException {
088    checkDir(dir);
089    for (File child : dir.listFiles()) {
090      if (child.isDirectory()) {
091        checkDirs(child);
092      }
093    }
094  }
095  
096  /**
097   * Create the directory if it doesn't exist and check that dir is readable,
098   * writable and executable
099   *  
100   * @param dir
101   * @throws DiskErrorException
102   */
103  public static void checkDir(File dir) throws DiskErrorException {
104    if (!mkdirsWithExistsCheck(dir)) {
105      throw new DiskErrorException("Cannot create directory: "
106                                   + dir.toString());
107    }
108    checkDirAccess(dir);
109  }
110
111  /**
112   * Create the directory or check permissions if it already exists.
113   *
114   * The semantics of mkdirsWithExistsAndPermissionCheck method is different
115   * from the mkdirs method provided in the Sun's java.io.File class in the
116   * following way:
117   * While creating the non-existent parent directories, this method checks for
118   * the existence of those directories if the mkdir fails at any point (since
119   * that directory might have just been created by some other process).
120   * If both mkdir() and the exists() check fails for any seemingly
121   * non-existent directory, then we signal an error; Sun's mkdir would signal
122   * an error (return false) if a directory it is attempting to create already
123   * exists or the mkdir fails.
124   *
125   * @param localFS local filesystem
126   * @param dir directory to be created or checked
127   * @param expected expected permission
128   * @throws IOException
129   */
130  public static void mkdirsWithExistsAndPermissionCheck(
131      LocalFileSystem localFS, Path dir, FsPermission expected)
132      throws IOException {
133    File directory = localFS.pathToFile(dir);
134    boolean created = false;
135
136    if (!directory.exists())
137      created = mkdirsWithExistsCheck(directory);
138
139    if (created || !localFS.getFileStatus(dir).getPermission().equals(expected))
140        localFS.setPermission(dir, expected);
141  }
142
143  /**
144   * Create the local directory if necessary, check permissions and also ensure
145   * it can be read from and written into.
146   *
147   * @param localFS local filesystem
148   * @param dir directory
149   * @param expected permission
150   * @throws DiskErrorException
151   * @throws IOException
152   */
153  public static void checkDir(LocalFileSystem localFS, Path dir,
154                              FsPermission expected)
155  throws DiskErrorException, IOException {
156    mkdirsWithExistsAndPermissionCheck(localFS, dir, expected);
157    checkDirAccess(localFS.pathToFile(dir));
158  }
159
160  /**
161   * Checks that the given file is a directory and that the current running
162   * process can read, write, and execute it.
163   * 
164   * @param dir File to check
165   * @throws DiskErrorException if dir is not a directory, not readable, not
166   *   writable, or not executable
167   */
168  private static void checkDirAccess(File dir) throws DiskErrorException {
169    if (!dir.isDirectory()) {
170      throw new DiskErrorException("Not a directory: "
171                                   + dir.toString());
172    }
173
174    checkAccessByFileMethods(dir);
175  }
176
177  /**
178   * Checks that the current running process can read, write, and execute the
179   * given directory by using methods of the File object.
180   * 
181   * @param dir File to check
182   * @throws DiskErrorException if dir is not readable, not writable, or not
183   *   executable
184   */
185  private static void checkAccessByFileMethods(File dir)
186      throws DiskErrorException {
187    if (!FileUtil.canRead(dir)) {
188      throw new DiskErrorException("Directory is not readable: "
189                                   + dir.toString());
190    }
191
192    if (!FileUtil.canWrite(dir)) {
193      throw new DiskErrorException("Directory is not writable: "
194                                   + dir.toString());
195    }
196
197    if (!FileUtil.canExecute(dir)) {
198      throw new DiskErrorException("Directory is not executable: "
199                                   + dir.toString());
200    }
201  }
202}