View Javadoc

1   /**
2    * Copyright 2010 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase.util;
21  
22  import java.io.DataInputStream;
23  import java.io.EOFException;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.lang.reflect.Method;
27  import java.net.URI;
28  import java.net.URISyntaxException;
29  import java.util.ArrayList;
30  import java.util.HashMap;
31  import java.util.List;
32  import java.util.Map;
33  import java.util.regex.Pattern;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.hadoop.conf.Configuration;
38  import org.apache.hadoop.fs.BlockLocation;
39  import org.apache.hadoop.fs.FSDataInputStream;
40  import org.apache.hadoop.fs.FSDataOutputStream;
41  import org.apache.hadoop.fs.FileStatus;
42  import org.apache.hadoop.fs.FileSystem;
43  import org.apache.hadoop.fs.Path;
44  import org.apache.hadoop.fs.PathFilter;
45  import org.apache.hadoop.fs.permission.FsAction;
46  import org.apache.hadoop.fs.permission.FsPermission;
47  import org.apache.hadoop.hbase.HBaseFileSystem;
48  import org.apache.hadoop.hbase.HColumnDescriptor;
49  import org.apache.hadoop.hbase.HConstants;
50  import org.apache.hadoop.hbase.HDFSBlocksDistribution;
51  import org.apache.hadoop.hbase.HRegionInfo;
52  import org.apache.hadoop.hbase.RemoteExceptionHandler;
53  import org.apache.hadoop.hbase.master.HMaster;
54  import org.apache.hadoop.hbase.regionserver.HRegion;
55  import org.apache.hadoop.hbase.security.User;
56  import org.apache.hadoop.hdfs.DistributedFileSystem;
57  import org.apache.hadoop.io.SequenceFile;
58  import org.apache.hadoop.security.AccessControlException;
59  import org.apache.hadoop.security.UserGroupInformation;
60  import org.apache.hadoop.util.ReflectionUtils;
61  import org.apache.hadoop.util.StringUtils;
62  
63  /**
64   * Utility methods for interacting with the underlying file system.
65   */
66  public abstract class FSUtils {
67    private static final Log LOG = LogFactory.getLog(FSUtils.class);
68  
69    /** Full access permissions (starting point for a umask) */
70    private static final String FULL_RWX_PERMISSIONS = "777";
71  
72    protected FSUtils() {
73      super();
74    }
75  
76    public static FSUtils getInstance(FileSystem fs, Configuration conf) {
77      String scheme = fs.getUri().getScheme();
78      if (scheme == null) {
79        LOG.warn("Could not find scheme for uri " +
80            fs.getUri() + ", default to hdfs");
81        scheme = "hdfs";
82      }
83      Class<?> fsUtilsClass = conf.getClass("hbase.fsutil." +
84          scheme + ".impl", FSHDFSUtils.class); // Default to HDFS impl
85      FSUtils fsUtils = (FSUtils)ReflectionUtils.newInstance(fsUtilsClass, conf);
86      return fsUtils;
87    }
88  
89    /**
90     * Delete if exists.
91     * @param fs filesystem object
92     * @param dir directory to delete
93     * @return True if deleted <code>dir</code>
94     * @throws IOException e
95     */
96    public static boolean deleteDirectory(final FileSystem fs, final Path dir)
97    throws IOException {
98      return fs.exists(dir) && fs.delete(dir, true);
99    }
100 
101   /**
102    * Check if directory exists.  If it does not, create it.
103    * @param fs filesystem object
104    * @param dir path to check
105    * @return Path
106    * @throws IOException e
107    */
108   public Path checkdir(final FileSystem fs, final Path dir) throws IOException {
109     if (!fs.exists(dir)) {
110       HBaseFileSystem.makeDirOnFileSystem(fs, dir);
111     }
112     return dir;
113   }
114 
115   /**
116    * Create the specified file on the filesystem. By default, this will:
117    * <ol>
118    * <li>overwrite the file if it exists</li>
119    * <li>apply the umask in the configuration (if it is enabled)</li>
120    * <li>use the fs configured buffer size (or {@value DEFAULT_BUFFER_SIZE} if
121    * not set)</li>
122    * <li>use the default replication</li>
123    * <li>use the default block size</li>
124    * <li>not track progress</li>
125    * </ol>
126    *
127    * @param fs {@link FileSystem} on which to write the file
128    * @param path {@link Path} to the file to write
129    * @return output stream to the created file
130    * @throws IOException if the file cannot be created
131    */
132   public static FSDataOutputStream create(FileSystem fs, Path path,
133       FsPermission perm) throws IOException {
134     return create(fs, path, perm, true);
135   }
136 
137   /**
138    * Create the specified file on the filesystem. By default, this will:
139    * <ol>
140    * <li>apply the umask in the configuration (if it is enabled)</li>
141    * <li>use the fs configured buffer size (or {@value DEFAULT_BUFFER_SIZE} if
142    * not set)</li>
143    * <li>use the default replication</li>
144    * <li>use the default block size</li>
145    * <li>not track progress</li>
146    * </ol>
147    *
148    * @param fs {@link FileSystem} on which to write the file
149    * @param path {@link Path} to the file to write
150    * @param perm
151    * @param overwrite Whether or not the created file should be overwritten.
152    * @return output stream to the created file
153    * @throws IOException if the file cannot be created
154    */
155   public static FSDataOutputStream create(FileSystem fs, Path path, FsPermission perm,
156       boolean overwrite) throws IOException {
157     LOG.debug("Creating file=" + path + " with permission=" + perm);
158     return HBaseFileSystem.createPathWithPermsOnFileSystem(fs, path, perm, overwrite);
159   }
160 
161   /**
162    * Get the file permissions specified in the configuration, if they are
163    * enabled.
164    *
165    * @param fs filesystem that the file will be created on.
166    * @param conf configuration to read for determining if permissions are
167    *          enabled and which to use
168    * @param permssionConfKey property key in the configuration to use when
169    *          finding the permission
170    * @return the permission to use when creating a new file on the fs. If
171    *         special permissions are not specified in the configuration, then
172    *         the default permissions on the the fs will be returned.
173    */
174   public static FsPermission getFilePermissions(final FileSystem fs,
175       final Configuration conf, final String permssionConfKey) {
176     boolean enablePermissions = conf.getBoolean(
177         HConstants.ENABLE_DATA_FILE_UMASK, false);
178 
179     if (enablePermissions) {
180       try {
181         FsPermission perm = new FsPermission(FULL_RWX_PERMISSIONS);
182         // make sure that we have a mask, if not, go default.
183         String mask = conf.get(permssionConfKey);
184         if (mask == null)
185           return FsPermission.getDefault();
186         // appy the umask
187         FsPermission umask = new FsPermission(mask);
188         return perm.applyUMask(umask);
189       } catch (IllegalArgumentException e) {
190         LOG.warn(
191             "Incorrect umask attempted to be created: "
192                 + conf.get(permssionConfKey)
193                 + ", using default file permissions.", e);
194         return FsPermission.getDefault();
195       }
196     }
197     return FsPermission.getDefault();
198   }
199 
200   /**
201    * Checks to see if the specified file system is available
202    *
203    * @param fs filesystem
204    * @throws IOException e
205    */
206   public static void checkFileSystemAvailable(final FileSystem fs)
207   throws IOException {
208     if (!(fs instanceof DistributedFileSystem)) {
209       return;
210     }
211     IOException exception = null;
212     DistributedFileSystem dfs = (DistributedFileSystem) fs;
213     try {
214       if (dfs.exists(new Path("/"))) {
215         return;
216       }
217     } catch (IOException e) {
218       exception = RemoteExceptionHandler.checkIOException(e);
219     }
220     try {
221       fs.close();
222     } catch (Exception e) {
223       LOG.error("file system close failed: ", e);
224     }
225     IOException io = new IOException("File system is not available");
226     io.initCause(exception);
227     throw io;
228   }
229 
230   /**
231    * We use reflection because {@link DistributedFileSystem#setSafeMode(
232    * FSConstants.SafeModeAction action, boolean isChecked)} is not in hadoop 1.1
233    * 
234    * @param dfs
235    * @return whether we're in safe mode
236    * @throws IOException
237    */
238   private static boolean isInSafeMode(DistributedFileSystem dfs) throws IOException {
239     boolean inSafeMode = false;
240     try {
241       Method m = DistributedFileSystem.class.getMethod("setSafeMode", new Class<?> []{
242           org.apache.hadoop.hdfs.protocol.FSConstants.SafeModeAction.class, boolean.class});
243       inSafeMode = (Boolean) m.invoke(dfs,
244         org.apache.hadoop.hdfs.protocol.FSConstants.SafeModeAction.SAFEMODE_GET, true);
245     } catch (Exception e) {
246       if (e instanceof IOException) throw (IOException) e;
247       
248       // Check whether dfs is on safemode.
249       inSafeMode = dfs.setSafeMode(
250         org.apache.hadoop.hdfs.protocol.FSConstants.SafeModeAction.SAFEMODE_GET);      
251     }
252     return inSafeMode;    
253   }
254   
255   /**
256    * Check whether dfs is in safemode.
257    * @param conf
258    * @throws IOException
259    */
260   public static void checkDfsSafeMode(final Configuration conf)
261   throws IOException {
262     boolean isInSafeMode = false;
263     FileSystem fs = FileSystem.get(conf);
264     if (fs instanceof DistributedFileSystem) {
265       DistributedFileSystem dfs = (DistributedFileSystem)fs;
266       isInSafeMode = isInSafeMode(dfs);
267     }
268     if (isInSafeMode) {
269       throw new IOException("File system is in safemode, it can't be written now");
270     }
271   }
272 
273   /**
274    * Verifies current version of file system
275    *
276    * @param fs filesystem object
277    * @param rootdir root hbase directory
278    * @return null if no version file exists, version string otherwise.
279    * @throws IOException e
280    */
281   public static String getVersion(FileSystem fs, Path rootdir)
282   throws IOException {
283     Path versionFile = new Path(rootdir, HConstants.VERSION_FILE_NAME);
284     String version = null;
285     if (fs.exists(versionFile)) {
286       FSDataInputStream s =
287         fs.open(versionFile);
288       try {
289         version = DataInputStream.readUTF(s);
290       } catch (EOFException eof) {
291         LOG.warn("Version file was empty, odd, will try to set it.");
292       } finally {
293         s.close();
294       }
295     }
296     return version;
297   }
298 
299   /**
300    * Verifies current version of file system
301    *
302    * @param fs file system
303    * @param rootdir root directory of HBase installation
304    * @param message if true, issues a message on System.out
305    *
306    * @throws IOException e
307    */
308   public static void checkVersion(FileSystem fs, Path rootdir,
309       boolean message) throws IOException {
310     checkVersion(fs, rootdir, message, 0,
311     		HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS);
312   }
313 
314   /**
315    * Verifies current version of file system
316    *
317    * @param fs file system
318    * @param rootdir root directory of HBase installation
319    * @param message if true, issues a message on System.out
320    * @param wait wait interval
321    * @param retries number of times to retry
322    *
323    * @throws IOException e
324    */
325   public static void checkVersion(FileSystem fs, Path rootdir,
326       boolean message, int wait, int retries) throws IOException {
327     String version = getVersion(fs, rootdir);
328 
329     if (version == null) {
330       if (!rootRegionExists(fs, rootdir)) {
331         // rootDir is empty (no version file and no root region)
332         // just create new version file (HBASE-1195)
333         FSUtils.setVersion(fs, rootdir, wait, retries);
334         return;
335       }
336     } else if (version.compareTo(HConstants.FILE_SYSTEM_VERSION) == 0)
337         return;
338 
339     // version is deprecated require migration
340     // Output on stdout so user sees it in terminal.
341     String msg = "HBase file layout needs to be upgraded."
342       + "  You have version " + version
343       + " and I want version " + HConstants.FILE_SYSTEM_VERSION
344       + ".  Is your hbase.rootdir valid?  If so, you may need to run "
345       + "'hbase hbck -fixVersionFile'.";
346     if (message) {
347       System.out.println("WARNING! " + msg);
348     }
349     throw new FileSystemVersionException(msg);
350   }
351 
352   /**
353    * Sets version of file system
354    *
355    * @param fs filesystem object
356    * @param rootdir hbase root
357    * @throws IOException e
358    */
359   public static void setVersion(FileSystem fs, Path rootdir)
360   throws IOException {
361     setVersion(fs, rootdir, HConstants.FILE_SYSTEM_VERSION, 0,
362     		HConstants.DEFAULT_VERSION_FILE_WRITE_ATTEMPTS);
363   }
364 
365   /**
366    * Sets version of file system
367    *
368    * @param fs filesystem object
369    * @param rootdir hbase root
370    * @param wait time to wait for retry
371    * @param retries number of times to retry before failing
372    * @throws IOException e
373    */
374   public static void setVersion(FileSystem fs, Path rootdir, int wait, int retries)
375   throws IOException {
376     setVersion(fs, rootdir, HConstants.FILE_SYSTEM_VERSION, wait, retries);
377   }
378 
379   /**
380    * Return the number of bytes that large input files should be optimally
381    * be split into to minimize i/o time.
382    *
383    * use reflection to search for getDefaultBlockSize(Path f)
384    * if the method doesn't exist, fall back to using getDefaultBlockSize()
385    *
386    * @param fs filesystem object
387    * @return the default block size for the path's filesystem
388    * @throws IOException e
389    */
390   public static long getDefaultBlockSize(final FileSystem fs, final Path path) throws IOException {
391     Method m = null;
392     Class<? extends FileSystem> cls = fs.getClass();
393     try {
394       m = cls.getMethod("getDefaultBlockSize", new Class<?>[] { Path.class });
395     } catch (NoSuchMethodException e) {
396       LOG.info("FileSystem doesn't support getDefaultBlockSize");
397     } catch (SecurityException e) {
398       LOG.info("Doesn't have access to getDefaultBlockSize on FileSystems", e);
399       m = null; // could happen on setAccessible()
400     }
401     if (m == null) {
402       return fs.getDefaultBlockSize();
403     } else {
404       try {
405         Object ret = m.invoke(fs, path);
406         return ((Long)ret).longValue();
407       } catch (Exception e) {
408         throw new IOException(e);
409       }
410     }
411   }
412 
413   /*
414    * Get the default replication.
415    *
416    * use reflection to search for getDefaultReplication(Path f)
417    * if the method doesn't exist, fall back to using getDefaultReplication()
418    *
419    * @param fs filesystem object
420    * @param f path of file
421    * @return default replication for the path's filesystem
422    * @throws IOException e
423    */
424   public static short getDefaultReplication(final FileSystem fs, final Path path) throws IOException {
425     Method m = null;
426     Class<? extends FileSystem> cls = fs.getClass();
427     try {
428       m = cls.getMethod("getDefaultReplication", new Class<?>[] { Path.class });
429     } catch (NoSuchMethodException e) {
430       LOG.info("FileSystem doesn't support getDefaultReplication");
431     } catch (SecurityException e) {
432       LOG.info("Doesn't have access to getDefaultReplication on FileSystems", e);
433       m = null; // could happen on setAccessible()
434     }
435     if (m == null) {
436       return fs.getDefaultReplication();
437     } else {
438       try {
439         Object ret = m.invoke(fs, path);
440         return ((Number)ret).shortValue();
441       } catch (Exception e) {
442         throw new IOException(e);
443       }
444     }
445   }
446 
447   /**
448    * Returns the default buffer size to use during writes.
449    *
450    * The size of the buffer should probably be a multiple of hardware
451    * page size (4096 on Intel x86), and it determines how much data is
452    * buffered during read and write operations.
453    *
454    * @param fs filesystem object
455    * @return default buffer size to use during writes
456    */
457   public static int getDefaultBufferSize(final FileSystem fs) {
458     return fs.getConf().getInt("io.file.buffer.size", 4096);
459   }
460 
461   /**
462    * Sets version of file system
463    *
464    * @param fs filesystem object
465    * @param rootdir hbase root directory
466    * @param version version to set
467    * @param wait time to wait for retry
468    * @param retries number of times to retry before throwing an IOException
469    * @throws IOException e
470    */
471   public static void setVersion(FileSystem fs, Path rootdir, String version,
472       int wait, int retries) throws IOException {
473     Path versionFile = new Path(rootdir, HConstants.VERSION_FILE_NAME);
474     while (true) {
475       try {
476         FSDataOutputStream s = fs.create(versionFile);
477         s.writeUTF(version);
478         LOG.debug("Created version file at " + rootdir.toString() +
479             " set its version at:" + version);
480         s.close();
481         return;
482       } catch (IOException e) {
483         if (retries > 0) {
484           LOG.warn("Unable to create version file at " + rootdir.toString() +
485               ", retrying: " + e.getMessage());
486           fs.delete(versionFile, false);
487           try {
488             if (wait > 0) {
489               Thread.sleep(wait);
490             }
491           } catch (InterruptedException ex) {
492             // ignore
493           }
494           retries--;
495         } else {
496           throw e;
497         }
498       }
499     }
500   }
501 
502   /**
503    * Checks that a cluster ID file exists in the HBase root directory
504    * @param fs the root directory FileSystem
505    * @param rootdir the HBase root directory in HDFS
506    * @param wait how long to wait between retries
507    * @return <code>true</code> if the file exists, otherwise <code>false</code>
508    * @throws IOException if checking the FileSystem fails
509    */
510   public static boolean checkClusterIdExists(FileSystem fs, Path rootdir,
511       int wait) throws IOException {
512     while (true) {
513       try {
514         Path filePath = new Path(rootdir, HConstants.CLUSTER_ID_FILE_NAME);
515         return fs.exists(filePath);
516       } catch (IOException ioe) {
517         if (wait > 0) {
518           LOG.warn("Unable to check cluster ID file in " + rootdir.toString() +
519               ", retrying in "+wait+"msec: "+StringUtils.stringifyException(ioe));
520           try {
521             Thread.sleep(wait);
522           } catch (InterruptedException ie) {
523             Thread.interrupted();
524             break;
525           }
526         } else {
527           throw ioe;
528         }
529       }
530     }
531     return false;
532   }
533 
534   /**
535    * Returns the value of the unique cluster ID stored for this HBase instance.
536    * @param fs the root directory FileSystem
537    * @param rootdir the path to the HBase root directory
538    * @return the unique cluster identifier
539    * @throws IOException if reading the cluster ID file fails
540    */
541   public static String getClusterId(FileSystem fs, Path rootdir)
542       throws IOException {
543     Path idPath = new Path(rootdir, HConstants.CLUSTER_ID_FILE_NAME);
544     String clusterId = null;
545     if (fs.exists(idPath)) {
546       FSDataInputStream in = fs.open(idPath);
547       try {
548         clusterId = in.readUTF();
549       } catch (EOFException eof) {
550         LOG.warn("Cluster ID file "+idPath.toString()+" was empty");
551       } finally{
552         in.close();
553       }
554     } else {
555       LOG.warn("Cluster ID file does not exist at " + idPath.toString());
556     }
557     return clusterId;
558   }
559 
560   /**
561    * Writes a new unique identifier for this cluster to the "hbase.id" file
562    * in the HBase root directory
563    * @param fs the root directory FileSystem
564    * @param rootdir the path to the HBase root directory
565    * @param clusterId the unique identifier to store
566    * @param wait how long (in milliseconds) to wait between retries
567    * @throws IOException if writing to the FileSystem fails and no wait value
568    */
569   public static void setClusterId(FileSystem fs, Path rootdir, String clusterId,
570       int wait) throws IOException {
571     while (true) {
572       try {
573         Path filePath = new Path(rootdir, HConstants.CLUSTER_ID_FILE_NAME);
574         FSDataOutputStream s = fs.create(filePath);
575         s.writeUTF(clusterId);
576         s.close();
577         if (LOG.isDebugEnabled()) {
578           LOG.debug("Created cluster ID file at " + filePath.toString() +
579               " with ID: " + clusterId);
580         }
581         return;
582       } catch (IOException ioe) {
583         if (wait > 0) {
584           LOG.warn("Unable to create cluster ID file in " + rootdir.toString() +
585               ", retrying in "+wait+"msec: "+StringUtils.stringifyException(ioe));
586           try {
587             Thread.sleep(wait);
588           } catch (InterruptedException ie) {
589             Thread.interrupted();
590             break;
591           }
592         } else {
593           throw ioe;
594         }
595       }
596     }
597   }
598 
599   /**
600    * Verifies root directory path is a valid URI with a scheme
601    *
602    * @param root root directory path
603    * @return Passed <code>root</code> argument.
604    * @throws IOException if not a valid URI with a scheme
605    */
606   public static Path validateRootPath(Path root) throws IOException {
607     try {
608       URI rootURI = new URI(root.toString());
609       String scheme = rootURI.getScheme();
610       if (scheme == null) {
611         throw new IOException("Root directory does not have a scheme");
612       }
613       return root;
614     } catch (URISyntaxException e) {
615       IOException io = new IOException("Root directory path is not a valid " +
616         "URI -- check your " + HConstants.HBASE_DIR + " configuration");
617       io.initCause(e);
618       throw io;
619     }
620   }
621 
622   /**
623    * If DFS, check safe mode and if so, wait until we clear it.
624    * @param conf configuration
625    * @param wait Sleep between retries
626    * @throws IOException e
627    */
628   public static void waitOnSafeMode(final Configuration conf,
629     final long wait)
630   throws IOException {
631     FileSystem fs = FileSystem.get(conf);
632     if (!(fs instanceof DistributedFileSystem)) return;
633     DistributedFileSystem dfs = (DistributedFileSystem)fs;
634     // Make sure dfs is not in safe mode
635     while (isInSafeMode(dfs)) {
636       LOG.info("Waiting for dfs to exit safe mode...");
637       try {
638         Thread.sleep(wait);
639       } catch (InterruptedException e) {
640         //continue
641       }
642     }
643   }
644 
645   /**
646    * Return the 'path' component of a Path.  In Hadoop, Path is an URI.  This
647    * method returns the 'path' component of a Path's URI: e.g. If a Path is
648    * <code>hdfs://example.org:9000/hbase_trunk/TestTable/compaction.dir</code>,
649    * this method returns <code>/hbase_trunk/TestTable/compaction.dir</code>.
650    * This method is useful if you want to print out a Path without qualifying
651    * Filesystem instance.
652    * @param p Filesystem Path whose 'path' component we are to return.
653    * @return Path portion of the Filesystem
654    */
655   public static String getPath(Path p) {
656     return p.toUri().getPath();
657   }
658 
659   /**
660    * @param c configuration
661    * @return Path to hbase root directory: i.e. <code>hbase.rootdir</code> from
662    * configuration as a qualified Path.
663    * @throws IOException e
664    */
665   public static Path getRootDir(final Configuration c) throws IOException {
666     Path p = new Path(c.get(HConstants.HBASE_DIR));
667     FileSystem fs = p.getFileSystem(c);
668     return p.makeQualified(fs);
669   }
670 
671   public static void setRootDir(final Configuration c, final Path root) throws IOException {
672     c.set(HConstants.HBASE_DIR, root.toString());
673   }
674 
675   /**
676    * Checks if root region exists
677    *
678    * @param fs file system
679    * @param rootdir root directory of HBase installation
680    * @return true if exists
681    * @throws IOException e
682    */
683   public static boolean rootRegionExists(FileSystem fs, Path rootdir)
684   throws IOException {
685     Path rootRegionDir =
686       HRegion.getRegionDir(rootdir, HRegionInfo.ROOT_REGIONINFO);
687     return fs.exists(rootRegionDir);
688   }
689 
690   /**
691    * Compute HDFS blocks distribution of a given file, or a portion of the file
692    * @param fs file system
693    * @param status file status of the file
694    * @param start start position of the portion
695    * @param length length of the portion
696    * @return The HDFS blocks distribution
697    */
698   static public HDFSBlocksDistribution computeHDFSBlocksDistribution(
699     final FileSystem fs, FileStatus status, long start, long length)
700     throws IOException {
701     HDFSBlocksDistribution blocksDistribution = new HDFSBlocksDistribution();
702     BlockLocation [] blockLocations =
703       fs.getFileBlockLocations(status, start, length);
704     for(BlockLocation bl : blockLocations) {
705       String [] hosts = bl.getHosts();
706       long len = bl.getLength();
707       blocksDistribution.addHostsAndBlockWeight(hosts, len);
708     }
709 
710     return blocksDistribution;
711   }
712 
713 
714 
715   /**
716    * Runs through the hbase rootdir and checks all stores have only
717    * one file in them -- that is, they've been major compacted.  Looks
718    * at root and meta tables too.
719    * @param fs filesystem
720    * @param hbaseRootDir hbase root directory
721    * @return True if this hbase install is major compacted.
722    * @throws IOException e
723    */
724   public static boolean isMajorCompacted(final FileSystem fs,
725       final Path hbaseRootDir)
726   throws IOException {
727     // Presumes any directory under hbase.rootdir is a table.
728     FileStatus [] tableDirs = fs.listStatus(hbaseRootDir, new DirFilter(fs));
729     for (FileStatus tableDir : tableDirs) {
730       // Skip the .log directory.  All others should be tables.  Inside a table,
731       // there are compaction.dir directories to skip.  Otherwise, all else
732       // should be regions.  Then in each region, should only be family
733       // directories.  Under each of these, should be one file only.
734       Path d = tableDir.getPath();
735       if (d.getName().equals(HConstants.HREGION_LOGDIR_NAME)) {
736         continue;
737       }
738       FileStatus[] regionDirs = fs.listStatus(d, new DirFilter(fs));
739       for (FileStatus regionDir : regionDirs) {
740         Path dd = regionDir.getPath();
741         if (dd.getName().equals(HConstants.HREGION_COMPACTIONDIR_NAME)) {
742           continue;
743         }
744         // Else its a region name.  Now look in region for families.
745         FileStatus[] familyDirs = fs.listStatus(dd, new DirFilter(fs));
746         for (FileStatus familyDir : familyDirs) {
747           Path family = familyDir.getPath();
748           // Now in family make sure only one file.
749           FileStatus[] familyStatus = fs.listStatus(family);
750           if (familyStatus.length > 1) {
751             LOG.debug(family.toString() + " has " + familyStatus.length +
752                 " files.");
753             return false;
754           }
755         }
756       }
757     }
758     return true;
759   }
760 
761   // TODO move this method OUT of FSUtils. No dependencies to HMaster
762   /**
763    * Returns the total overall fragmentation percentage. Includes .META. and
764    * -ROOT- as well.
765    *
766    * @param master  The master defining the HBase root and file system.
767    * @return A map for each table and its percentage.
768    * @throws IOException When scanning the directory fails.
769    */
770   public static int getTotalTableFragmentation(final HMaster master)
771   throws IOException {
772     Map<String, Integer> map = getTableFragmentation(master);
773     return map != null && map.size() > 0 ? map.get("-TOTAL-") : -1;
774   }
775 
776   /**
777    * Runs through the HBase rootdir and checks how many stores for each table
778    * have more than one file in them. Checks -ROOT- and .META. too. The total
779    * percentage across all tables is stored under the special key "-TOTAL-".
780    *
781    * @param master  The master defining the HBase root and file system.
782    * @return A map for each table and its percentage.
783    * @throws IOException When scanning the directory fails.
784    */
785   public static Map<String, Integer> getTableFragmentation(
786     final HMaster master)
787   throws IOException {
788     Path path = getRootDir(master.getConfiguration());
789     // since HMaster.getFileSystem() is package private
790     FileSystem fs = path.getFileSystem(master.getConfiguration());
791     return getTableFragmentation(fs, path);
792   }
793 
794   /**
795    * Runs through the HBase rootdir and checks how many stores for each table
796    * have more than one file in them. Checks -ROOT- and .META. too. The total
797    * percentage across all tables is stored under the special key "-TOTAL-".
798    *
799    * @param fs  The file system to use.
800    * @param hbaseRootDir  The root directory to scan.
801    * @return A map for each table and its percentage.
802    * @throws IOException When scanning the directory fails.
803    */
804   public static Map<String, Integer> getTableFragmentation(
805     final FileSystem fs, final Path hbaseRootDir)
806   throws IOException {
807     Map<String, Integer> frags = new HashMap<String, Integer>();
808     int cfCountTotal = 0;
809     int cfFragTotal = 0;
810     DirFilter df = new DirFilter(fs);
811     // presumes any directory under hbase.rootdir is a table
812     FileStatus [] tableDirs = fs.listStatus(hbaseRootDir, df);
813     for (FileStatus tableDir : tableDirs) {
814       // Skip the .log directory.  All others should be tables.  Inside a table,
815       // there are compaction.dir directories to skip.  Otherwise, all else
816       // should be regions.  Then in each region, should only be family
817       // directories.  Under each of these, should be one file only.
818       Path d = tableDir.getPath();
819       if (d.getName().equals(HConstants.HREGION_LOGDIR_NAME)) {
820         continue;
821       }
822       int cfCount = 0;
823       int cfFrag = 0;
824       FileStatus[] regionDirs = fs.listStatus(d, df);
825       for (FileStatus regionDir : regionDirs) {
826         Path dd = regionDir.getPath();
827         if (dd.getName().equals(HConstants.HREGION_COMPACTIONDIR_NAME)) {
828           continue;
829         }
830         // else its a region name, now look in region for families
831         FileStatus[] familyDirs = fs.listStatus(dd, df);
832         for (FileStatus familyDir : familyDirs) {
833           cfCount++;
834           cfCountTotal++;
835           Path family = familyDir.getPath();
836           // now in family make sure only one file
837           FileStatus[] familyStatus = fs.listStatus(family);
838           if (familyStatus.length > 1) {
839             cfFrag++;
840             cfFragTotal++;
841           }
842         }
843       }
844       // compute percentage per table and store in result list
845       frags.put(d.getName(), Math.round((float) cfFrag / cfCount * 100));
846     }
847     // set overall percentage for all tables
848     frags.put("-TOTAL-", Math.round((float) cfFragTotal / cfCountTotal * 100));
849     return frags;
850   }
851 
852   /**
853    * Expects to find -ROOT- directory.
854    * @param fs filesystem
855    * @param hbaseRootDir hbase root directory
856    * @return True if this a pre020 layout.
857    * @throws IOException e
858    */
859   public static boolean isPre020FileLayout(final FileSystem fs,
860     final Path hbaseRootDir)
861   throws IOException {
862     Path mapfiles = new Path(new Path(new Path(new Path(hbaseRootDir, "-ROOT-"),
863       "70236052"), "info"), "mapfiles");
864     return fs.exists(mapfiles);
865   }
866 
867   /**
868    * Runs through the hbase rootdir and checks all stores have only
869    * one file in them -- that is, they've been major compacted.  Looks
870    * at root and meta tables too.  This version differs from
871    * {@link #isMajorCompacted(FileSystem, Path)} in that it expects a
872    * pre-0.20.0 hbase layout on the filesystem.  Used migrating.
873    * @param fs filesystem
874    * @param hbaseRootDir hbase root directory
875    * @return True if this hbase install is major compacted.
876    * @throws IOException e
877    */
878   public static boolean isMajorCompactedPre020(final FileSystem fs,
879       final Path hbaseRootDir)
880   throws IOException {
881     // Presumes any directory under hbase.rootdir is a table.
882     FileStatus [] tableDirs = fs.listStatus(hbaseRootDir, new DirFilter(fs));
883     for (FileStatus tableDir : tableDirs) {
884       // Inside a table, there are compaction.dir directories to skip.
885       // Otherwise, all else should be regions.  Then in each region, should
886       // only be family directories.  Under each of these, should be a mapfile
887       // and info directory and in these only one file.
888       Path d = tableDir.getPath();
889       if (d.getName().equals(HConstants.HREGION_LOGDIR_NAME)) {
890         continue;
891       }
892       FileStatus[] regionDirs = fs.listStatus(d, new DirFilter(fs));
893       for (FileStatus regionDir : regionDirs) {
894         Path dd = regionDir.getPath();
895         if (dd.getName().equals(HConstants.HREGION_COMPACTIONDIR_NAME)) {
896           continue;
897         }
898         // Else its a region name.  Now look in region for families.
899         FileStatus[] familyDirs = fs.listStatus(dd, new DirFilter(fs));
900         for (FileStatus familyDir : familyDirs) {
901           Path family = familyDir.getPath();
902           FileStatus[] infoAndMapfile = fs.listStatus(family);
903           // Assert that only info and mapfile in family dir.
904           if (infoAndMapfile.length != 0 && infoAndMapfile.length != 2) {
905             LOG.debug(family.toString() +
906                 " has more than just info and mapfile: " + infoAndMapfile.length);
907             return false;
908           }
909           // Make sure directory named info or mapfile.
910           for (int ll = 0; ll < 2; ll++) {
911             if (infoAndMapfile[ll].getPath().getName().equals("info") ||
912                 infoAndMapfile[ll].getPath().getName().equals("mapfiles"))
913               continue;
914             LOG.debug("Unexpected directory name: " +
915                 infoAndMapfile[ll].getPath());
916             return false;
917           }
918           // Now in family, there are 'mapfile' and 'info' subdirs.  Just
919           // look in the 'mapfile' subdir.
920           FileStatus[] familyStatus =
921               fs.listStatus(new Path(family, "mapfiles"));
922           if (familyStatus.length > 1) {
923             LOG.debug(family.toString() + " has " + familyStatus.length +
924                 " files.");
925             return false;
926           }
927         }
928       }
929     }
930     return true;
931   }
932 
933   /**
934    * A {@link PathFilter} that returns only regular files.
935    */
936   static class FileFilter implements PathFilter {
937     private final FileSystem fs;
938 
939     public FileFilter(final FileSystem fs) {
940       this.fs = fs;
941     }
942 
943     @Override
944     public boolean accept(Path p) {
945       try {
946         return fs.isFile(p);
947       } catch (IOException e) {
948         LOG.debug("unable to verify if path=" + p + " is a regular file", e);
949         return false;
950       }
951     }
952   }
953 
954   /**
955    * A {@link PathFilter} that returns directories.
956    */
957   public static class DirFilter implements PathFilter {
958     private final FileSystem fs;
959 
960     public DirFilter(final FileSystem fs) {
961       this.fs = fs;
962     }
963 
964     @Override
965     public boolean accept(Path p) {
966       boolean isValid = false;
967       try {
968         if (HConstants.HBASE_NON_USER_TABLE_DIRS.contains(p)) {
969           isValid = false;
970         } else {
971           isValid = this.fs.getFileStatus(p).isDir();
972         }
973       } catch (IOException e) {
974         e.printStackTrace();
975       }
976       return isValid;
977     }
978   }
979 
980   /**
981    * Heuristic to determine whether is safe or not to open a file for append
982    * Looks both for dfs.support.append and use reflection to search
983    * for SequenceFile.Writer.syncFs() or FSDataOutputStream.hflush()
984    * @param conf
985    * @return True if append support
986    */
987   public static boolean isAppendSupported(final Configuration conf) {
988     boolean append = conf.getBoolean("dfs.support.append", false);
989     if (append) {
990       try {
991         // TODO: The implementation that comes back when we do a createWriter
992         // may not be using SequenceFile so the below is not a definitive test.
993         // Will do for now (hdfs-200).
994         SequenceFile.Writer.class.getMethod("syncFs", new Class<?> []{});
995         append = true;
996       } catch (SecurityException e) {
997       } catch (NoSuchMethodException e) {
998         append = false;
999       }
1000     }
1001     if (!append) {
1002       // Look for the 0.21, 0.22, new-style append evidence.
1003       try {
1004         FSDataOutputStream.class.getMethod("hflush", new Class<?> []{});
1005         append = true;
1006       } catch (NoSuchMethodException e) {
1007         append = false;
1008       }
1009     }
1010     return append;
1011   }
1012 
1013   /**
1014    * @param conf
1015    * @return True if this filesystem whose scheme is 'hdfs'.
1016    * @throws IOException
1017    */
1018   public static boolean isHDFS(final Configuration conf) throws IOException {
1019     FileSystem fs = FileSystem.get(conf);
1020     String scheme = fs.getUri().getScheme();
1021     return scheme.equalsIgnoreCase("hdfs");
1022   }
1023 
1024   /**
1025    * Recover file lease. Used when a file might be suspect
1026    * to be had been left open by another process.
1027    * @param fs FileSystem handle
1028    * @param p Path of file to recover lease
1029    * @param conf Configuration handle
1030    * @throws IOException
1031    */
1032   public abstract void recoverFileLease(final FileSystem fs, final Path p,
1033       Configuration conf) throws IOException;
1034 
1035   /**
1036    * @param fs
1037    * @param rootdir
1038    * @return All the table directories under <code>rootdir</code>. Ignore non table hbase folders such as
1039    * .logs, .oldlogs, .corrupt, .META., and -ROOT- folders.
1040    * @throws IOException
1041    */
1042   public static List<Path> getTableDirs(final FileSystem fs, final Path rootdir)
1043   throws IOException {
1044     // presumes any directory under hbase.rootdir is a table
1045     FileStatus [] dirs = fs.listStatus(rootdir, new DirFilter(fs));
1046     List<Path> tabledirs = new ArrayList<Path>(dirs.length);
1047     for (FileStatus dir: dirs) {
1048       Path p = dir.getPath();
1049       String tableName = p.getName();
1050       if (!HConstants.HBASE_NON_USER_TABLE_DIRS.contains(tableName)) {
1051         tabledirs.add(p);
1052       }
1053     }
1054     return tabledirs;
1055   }
1056 
1057   public static Path getTablePath(Path rootdir, byte [] tableName) {
1058     return getTablePath(rootdir, Bytes.toString(tableName));
1059   }
1060 
1061   public static Path getTablePath(Path rootdir, final String tableName) {
1062     return new Path(rootdir, tableName);
1063   }
1064 
1065   /**
1066    * Filter for all dirs that don't start with '.'
1067    */
1068   public static class RegionDirFilter implements PathFilter {
1069     // This pattern will accept 0.90+ style hex region dirs and older numeric region dir names.
1070     final public static Pattern regionDirPattern = Pattern.compile("^[0-9a-f]*$");
1071     final FileSystem fs;
1072 
1073     public RegionDirFilter(FileSystem fs) {
1074       this.fs = fs;
1075     }
1076 
1077     @Override
1078     public boolean accept(Path rd) {
1079       if (!regionDirPattern.matcher(rd.getName()).matches()) {
1080         return false;
1081       }
1082 
1083       try {
1084         return fs.getFileStatus(rd).isDir();
1085       } catch (IOException ioe) {
1086         // Maybe the file was moved or the fs was disconnected.
1087         LOG.warn("Skipping file " + rd +" due to IOException", ioe);
1088         return false;
1089       }
1090     }
1091   }
1092 
1093   /**
1094    * Given a particular table dir, return all the regiondirs inside it, excluding files such as
1095    * .tableinfo
1096    * @param fs A file system for the Path
1097    * @param tableDir Path to a specific table directory <hbase.rootdir>/<tabledir>
1098    * @return List of paths to valid region directories in table dir.
1099    * @throws IOException
1100    */
1101   public static List<Path> getRegionDirs(final FileSystem fs, final Path tableDir) throws IOException {
1102     // assumes we are in a table dir.
1103     FileStatus[] rds = fs.listStatus(tableDir, new RegionDirFilter(fs));
1104     List<Path> regionDirs = new ArrayList<Path>(rds.length);
1105     for (FileStatus rdfs: rds) {
1106       Path rdPath = rdfs.getPath();
1107       regionDirs.add(rdPath);
1108     }
1109     return regionDirs;
1110   }
1111 
1112   /**
1113    * Filter for all dirs that are legal column family names.  This is generally used for colfam
1114    * dirs <hbase.rootdir>/<tabledir>/<regiondir>/<colfamdir>.
1115    */
1116   public static class FamilyDirFilter implements PathFilter {
1117     final FileSystem fs;
1118 
1119     public FamilyDirFilter(FileSystem fs) {
1120       this.fs = fs;
1121     }
1122 
1123     @Override
1124     public boolean accept(Path rd) {
1125       try {
1126         // throws IAE if invalid
1127         HColumnDescriptor.isLegalFamilyName(Bytes.toBytes(rd.getName()));
1128       } catch (IllegalArgumentException iae) {
1129         // path name is an invalid family name and thus is excluded.
1130         return false;
1131       }
1132 
1133       try {
1134         return fs.getFileStatus(rd).isDir();
1135       } catch (IOException ioe) {
1136         // Maybe the file was moved or the fs was disconnected.
1137         LOG.warn("Skipping file " + rd +" due to IOException", ioe);
1138         return false;
1139       }
1140     }
1141   }
1142 
1143   /**
1144    * Given a particular region dir, return all the familydirs inside it
1145    *
1146    * @param fs A file system for the Path
1147    * @param regionDir Path to a specific region directory
1148    * @return List of paths to valid family directories in region dir.
1149    * @throws IOException
1150    */
1151   public static List<Path> getFamilyDirs(final FileSystem fs, final Path regionDir) throws IOException {
1152     // assumes we are in a region dir.
1153     FileStatus[] fds = fs.listStatus(regionDir, new FamilyDirFilter(fs));
1154     List<Path> familyDirs = new ArrayList<Path>(fds.length);
1155     for (FileStatus fdfs: fds) {
1156       Path fdPath = fdfs.getPath();
1157       familyDirs.add(fdPath);
1158     }
1159     return familyDirs;
1160   }
1161 
1162   /**
1163    * Filter for HFiles that excludes reference files.
1164    */
1165   public static class HFileFilter implements PathFilter {
1166     // This pattern will accept 0.90+ style hex hfies files but reject reference files
1167     final public static Pattern hfilePattern = Pattern.compile("^([0-9a-f]+)$");
1168 
1169     final FileSystem fs;
1170 
1171     public HFileFilter(FileSystem fs) {
1172       this.fs = fs;
1173     }
1174 
1175     @Override
1176     public boolean accept(Path rd) {
1177       if (!hfilePattern.matcher(rd.getName()).matches()) {
1178         return false;
1179       }
1180 
1181       try {
1182         // only files
1183         return !fs.getFileStatus(rd).isDir();
1184       } catch (IOException ioe) {
1185         // Maybe the file was moved or the fs was disconnected.
1186         LOG.warn("Skipping file " + rd +" due to IOException", ioe);
1187         return false;
1188       }
1189     }
1190   }
1191 
1192   /**
1193    * @param conf
1194    * @return Returns the filesystem of the hbase rootdir.
1195    * @throws IOException
1196    */
1197   public static FileSystem getCurrentFileSystem(Configuration conf)
1198   throws IOException {
1199     return getRootDir(conf).getFileSystem(conf);
1200   }
1201 
1202   /**
1203    * Runs through the HBase rootdir and creates a reverse lookup map for
1204    * table StoreFile names to the full Path.
1205    * <br>
1206    * Example...<br>
1207    * Key = 3944417774205889744  <br>
1208    * Value = hdfs://localhost:51169/user/userid/-ROOT-/70236052/info/3944417774205889744
1209    *
1210    * @param fs  The file system to use.
1211    * @param hbaseRootDir  The root directory to scan.
1212    * @return Map keyed by StoreFile name with a value of the full Path.
1213    * @throws IOException When scanning the directory fails.
1214    */
1215   public static Map<String, Path> getTableStoreFilePathMap(
1216     final FileSystem fs, final Path hbaseRootDir)
1217   throws IOException {
1218     Map<String, Path> map = new HashMap<String, Path>();
1219     
1220     // if this method looks similar to 'getTableFragmentation' that is because 
1221     // it was borrowed from it.
1222     
1223     DirFilter df = new DirFilter(fs);
1224     // presumes any directory under hbase.rootdir is a table
1225     FileStatus [] tableDirs = fs.listStatus(hbaseRootDir, df);
1226     for (FileStatus tableDir : tableDirs) {
1227       // Skip the .log and other non-table directories.  All others should be tables.
1228       // Inside a table, there are compaction.dir directories to skip.  Otherwise, all else
1229       // should be regions. 
1230       Path d = tableDir.getPath();
1231       if (HConstants.HBASE_NON_TABLE_DIRS.contains(d.getName())) {
1232         continue;
1233       }
1234       FileStatus[] regionDirs = fs.listStatus(d, df);
1235       for (FileStatus regionDir : regionDirs) {
1236         Path dd = regionDir.getPath();
1237         if (dd.getName().equals(HConstants.HREGION_COMPACTIONDIR_NAME)) {
1238           continue;
1239         }
1240         // else its a region name, now look in region for families
1241         FileStatus[] familyDirs = fs.listStatus(dd, df);
1242         for (FileStatus familyDir : familyDirs) {
1243           Path family = familyDir.getPath();
1244           // now in family, iterate over the StoreFiles and
1245           // put in map
1246           FileStatus[] familyStatus = fs.listStatus(family);
1247           for (FileStatus sfStatus : familyStatus) {
1248             Path sf = sfStatus.getPath();
1249             map.put( sf.getName(), sf);
1250           }
1251 
1252         }
1253       }
1254     }
1255       return map;
1256   }
1257 
1258   /**
1259    * Calls fs.listStatus() and treats FileNotFoundException as non-fatal
1260    * This accommodates differences between hadoop versions
1261    *
1262    * @param fs file system
1263    * @param dir directory
1264    * @param filter path filter
1265    * @return null if tabledir doesn't exist, otherwise FileStatus array
1266    */
1267   public static FileStatus [] listStatus(final FileSystem fs,
1268       final Path dir, final PathFilter filter) throws IOException {
1269     FileStatus [] status = null;
1270     try {
1271       status = filter == null ? fs.listStatus(dir) : fs.listStatus(dir, filter);
1272     } catch (FileNotFoundException fnfe) {
1273       // if directory doesn't exist, return null
1274       LOG.debug(dir + " doesn't exist");
1275     }
1276     if (status == null || status.length < 1) return null;
1277     return status;
1278   }
1279 
1280   /**
1281    * Calls fs.listStatus() and treats FileNotFoundException as non-fatal
1282    * This would accommodates differences between hadoop versions
1283    *
1284    * @param fs file system
1285    * @param dir directory
1286    * @return null if tabledir doesn't exist, otherwise FileStatus array
1287    */
1288   public static FileStatus[] listStatus(final FileSystem fs, final Path dir) throws IOException {
1289     return listStatus(fs, dir, null);
1290   }
1291 
1292   /**
1293    * Calls fs.delete() and returns the value returned by the fs.delete()
1294    *
1295    * @param fs
1296    * @param path
1297    * @param recursive
1298    * @return
1299    * @throws IOException
1300    */
1301   public static boolean delete(final FileSystem fs, final Path path, final boolean recursive)
1302       throws IOException {
1303     return fs.delete(path, recursive);
1304   }
1305 
1306   /**
1307    * Throw an exception if an action is not permitted by a user on a file.
1308    * 
1309    * @param user
1310    *          the user
1311    * @param file
1312    *          the file
1313    * @param action
1314    *          the action
1315    */
1316   public static void checkAccess(User user, FileStatus file,
1317       FsAction action) throws AccessControlException {
1318     // See HBASE-7814. UserGroupInformation from hadoop 0.20.x may not support getShortName().
1319     String username = user.getShortName();
1320     if (username.equals(file.getOwner())) {
1321       if (file.getPermission().getUserAction().implies(action)) {
1322         return;
1323       }
1324     } else if (contains(user.getGroupNames(), file.getGroup())) {
1325       if (file.getPermission().getGroupAction().implies(action)) {
1326         return;
1327       }
1328     } else if (file.getPermission().getOtherAction().implies(action)) {
1329       return;
1330     }
1331     throw new AccessControlException("Permission denied:" + " action=" + action
1332         + " path=" + file.getPath() + " user=" + username);
1333   }
1334 
1335   private static boolean contains(String[] groups, String user) {
1336     for (String group : groups) {
1337       if (group.equals(user)) {
1338         return true;
1339       }
1340     }
1341     return false;
1342   }
1343 
1344   /**
1345    * Calls fs.exists(). Checks if the specified path exists
1346    *
1347    * @param fs
1348    * @param path
1349    * @return
1350    * @throws IOException
1351    */
1352   public static boolean isExists(final FileSystem fs, final Path path) throws IOException {
1353     return fs.exists(path);
1354   }
1355 
1356   /**
1357    * Log the current state of the filesystem from a certain root directory
1358    * @param fs filesystem to investigate
1359    * @param root root file/directory to start logging from
1360    * @param LOG log to output information
1361    * @throws IOException if an unexpected exception occurs
1362    */
1363   public static void logFileSystemState(final FileSystem fs, final Path root, Log LOG)
1364       throws IOException {
1365     LOG.debug("Current file system:");
1366     logFSTree(LOG, fs, root, "|-");
1367   }
1368 
1369   /**
1370    * Recursive helper to log the state of the FS
1371    * @see #logFileSystemState(FileSystem, Path, Log)
1372    */
1373   private static void logFSTree(Log LOG, final FileSystem fs, final Path root, String prefix)
1374       throws IOException {
1375     FileStatus[] files = FSUtils.listStatus(fs, root, null);
1376     if (files == null) return;
1377 
1378     for (FileStatus file : files) {
1379       if (file.isDir()) {
1380         LOG.debug(prefix + file.getPath().getName() + "/");
1381         logFSTree(LOG, fs, file.getPath(), prefix + "---");
1382       } else {
1383         LOG.debug(prefix + file.getPath().getName());
1384       }
1385     }
1386   }
1387 }