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
082   * @param dir
083   * @throws DiskErrorException
084   */
085  public static void checkDir(File dir) throws DiskErrorException {
086    if (!mkdirsWithExistsCheck(dir))
087      throw new DiskErrorException("can not create directory: " 
088                                   + dir.toString());
089        
090    if (!dir.isDirectory())
091      throw new DiskErrorException("not a directory: " 
092                                   + dir.toString());
093            
094    if (!dir.canRead())
095      throw new DiskErrorException("directory is not readable: " 
096                                   + dir.toString());
097            
098    if (!dir.canWrite())
099      throw new DiskErrorException("directory is not writable: " 
100                                   + dir.toString());
101  }
102
103  /**
104   * Create the directory or check permissions if it already exists.
105   *
106   * The semantics of mkdirsWithExistsAndPermissionCheck method is different
107   * from the mkdirs method provided in the Sun's java.io.File class in the
108   * following way:
109   * While creating the non-existent parent directories, this method checks for
110   * the existence of those directories if the mkdir fails at any point (since
111   * that directory might have just been created by some other process).
112   * If both mkdir() and the exists() check fails for any seemingly
113   * non-existent directory, then we signal an error; Sun's mkdir would signal
114   * an error (return false) if a directory it is attempting to create already
115   * exists or the mkdir fails.
116   *
117   * @param localFS local filesystem
118   * @param dir directory to be created or checked
119   * @param expected expected permission
120   * @throws IOException
121   */
122  public static void mkdirsWithExistsAndPermissionCheck(
123      LocalFileSystem localFS, Path dir, FsPermission expected)
124      throws IOException {
125    File directory = localFS.pathToFile(dir);
126    boolean created = false;
127
128    if (!directory.exists())
129      created = mkdirsWithExistsCheck(directory);
130
131    if (created || !localFS.getFileStatus(dir).getPermission().equals(expected))
132        localFS.setPermission(dir, expected);
133  }
134
135  /**
136   * Create the local directory if necessary, check permissions and also ensure
137   * it can be read from and written into.
138   *
139   * @param localFS local filesystem
140   * @param dir directory
141   * @param expected permission
142   * @throws DiskErrorException
143   * @throws IOException
144   */
145  public static void checkDir(LocalFileSystem localFS, Path dir,
146                              FsPermission expected)
147  throws DiskErrorException, IOException {
148    mkdirsWithExistsAndPermissionCheck(localFS, dir, expected);
149
150    FileStatus stat = localFS.getFileStatus(dir);
151    FsPermission actual = stat.getPermission();
152
153    if (!stat.isDirectory())
154      throw new DiskErrorException("not a directory: "+ dir.toString());
155
156    FsAction user = actual.getUserAction();
157    if (!user.implies(FsAction.READ))
158      throw new DiskErrorException("directory is not readable: "
159                                   + dir.toString());
160
161    if (!user.implies(FsAction.WRITE))
162      throw new DiskErrorException("directory is not writable: "
163                                   + dir.toString());
164
165    if (!user.implies(FsAction.EXECUTE))
166      throw new DiskErrorException("directory is not listable: "
167                                   + dir.toString());
168  }
169}