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