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    
019    package org.apache.hadoop.util;
020    
021    import java.io.File;
022    import java.io.IOException;
023    
024    import org.apache.hadoop.classification.InterfaceAudience;
025    import org.apache.hadoop.classification.InterfaceStability;
026    import org.apache.hadoop.fs.FileStatus;
027    import org.apache.hadoop.fs.LocalFileSystem;
028    import org.apache.hadoop.fs.Path;
029    import org.apache.hadoop.fs.permission.FsAction;
030    import 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
037    public 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    }