org.apache.hadoop.fs.viewfs.ViewFileSystem.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hadoop.fs.viewfs.ViewFileSystem.java

Source

/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.hadoop.fs.viewfs;

import static org.apache.hadoop.fs.viewfs.Constants.PERMISSION_555;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.BlockLocation;
import org.apache.hadoop.fs.BlockStoragePolicySpi;
import org.apache.hadoop.fs.ContentSummary;
import org.apache.hadoop.fs.CreateFlag;
import org.apache.hadoop.fs.FSDataInputStream;
import org.apache.hadoop.fs.FSDataOutputStream;
import org.apache.hadoop.fs.FileAlreadyExistsException;
import org.apache.hadoop.fs.FileChecksum;
import org.apache.hadoop.fs.FileStatus;
import org.apache.hadoop.fs.FileSystem;
import org.apache.hadoop.fs.FsConstants;
import org.apache.hadoop.fs.FsServerDefaults;
import org.apache.hadoop.fs.FsStatus;
import org.apache.hadoop.fs.LocatedFileStatus;
import org.apache.hadoop.fs.Path;
import org.apache.hadoop.fs.PathFilter;
import org.apache.hadoop.fs.QuotaUsage;
import org.apache.hadoop.fs.RemoteIterator;
import org.apache.hadoop.fs.XAttrSetFlag;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.fs.permission.AclUtil;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.fs.viewfs.InodeTree.INode;
import org.apache.hadoop.fs.viewfs.InodeTree.INodeLink;
import org.apache.hadoop.security.AccessControlException;
import org.apache.hadoop.security.UserGroupInformation;
import org.apache.hadoop.util.Progressable;
import org.apache.hadoop.util.Time;

/**
 * ViewFileSystem (extends the FileSystem interface) implements a client-side
 * mount table. Its spec and implementation is identical to {@link ViewFs}.
 */

@InterfaceAudience.Public
@InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */
public class ViewFileSystem extends FileSystem {

    private static final Path ROOT_PATH = new Path(Path.SEPARATOR);

    static AccessControlException readOnlyMountTable(final String operation, final String p) {
        return new AccessControlException(
                "InternalDir of ViewFileSystem is readonly; operation=" + operation + "Path=" + p);
    }

    static AccessControlException readOnlyMountTable(final String operation, final Path p) {
        return readOnlyMountTable(operation, p.toString());
    }

    /**
     * MountPoint representation built from the configuration.
     */
    public static class MountPoint {

        /**
         * The mounted on path location.
         */
        private final Path mountedOnPath;

        /**
         * Array of target FileSystem URIs.
         */
        private final URI[] targetFileSystemURIs;

        MountPoint(Path srcPath, URI[] targetFs) {
            mountedOnPath = srcPath;
            targetFileSystemURIs = targetFs;
        }

        public Path getMountedOnPath() {
            return mountedOnPath;
        }

        public URI[] getTargetFileSystemURIs() {
            return targetFileSystemURIs;
        }
    }

    final long creationTime; // of the the mount table
    final UserGroupInformation ugi; // the user/group of user who created mtable
    URI myUri;
    private Path workingDir;
    Configuration config;
    InodeTree<FileSystem> fsState; // the fs state; ie the mount table
    Path homeDir = null;
    // Default to rename within same mountpoint
    private RenameStrategy renameStrategy = RenameStrategy.SAME_MOUNTPOINT;

    /**
     * Make the path Absolute and get the path-part of a pathname.
     * Checks that URI matches this file system 
     * and that the path-part is a valid name.
     * 
     * @param p path
     * @return path-part of the Path p
     */
    String getUriPath(final Path p) {
        checkPath(p);
        return makeAbsolute(p).toUri().getPath();
    }

    private Path makeAbsolute(final Path f) {
        return f.isAbsolute() ? f : new Path(workingDir, f);
    }

    /**
     * This is the  constructor with the signature needed by
     * {@link FileSystem#createFileSystem(URI, Configuration)}
     * 
     * After this constructor is called initialize() is called.
     * @throws IOException 
     */
    public ViewFileSystem() throws IOException {
        ugi = UserGroupInformation.getCurrentUser();
        creationTime = Time.now();
    }

    /**
     * Return the protocol scheme for the FileSystem.
     *
     * @return <code>viewfs</code>
     */
    @Override
    public String getScheme() {
        return FsConstants.VIEWFS_SCHEME;
    }

    /**
     * Called after a new FileSystem instance is constructed.
     * @param theUri a uri whose authority section names the host, port, etc. for
     *        this FileSystem
     * @param conf the configuration
     */
    @Override
    public void initialize(final URI theUri, final Configuration conf) throws IOException {
        super.initialize(theUri, conf);
        setConf(conf);
        config = conf;
        // Now build  client side view (i.e. client side mount table) from config.
        final String authority = theUri.getAuthority();
        try {
            myUri = new URI(FsConstants.VIEWFS_SCHEME, authority, "/", null, null);
            fsState = new InodeTree<FileSystem>(conf, authority) {

                @Override
                protected FileSystem getTargetFileSystem(final URI uri) throws URISyntaxException, IOException {
                    return new ChRootedFileSystem(uri, config);
                }

                @Override
                protected FileSystem getTargetFileSystem(final INodeDir<FileSystem> dir) throws URISyntaxException {
                    return new InternalDirOfViewFs(dir, creationTime, ugi, myUri, config);
                }

                @Override
                protected FileSystem getTargetFileSystem(final String settings, final URI[] uris)
                        throws URISyntaxException, IOException {
                    return NflyFSystem.createFileSystem(uris, config, settings);
                }
            };
            workingDir = this.getHomeDirectory();
            renameStrategy = RenameStrategy.valueOf(
                    conf.get(Constants.CONFIG_VIEWFS_RENAME_STRATEGY, RenameStrategy.SAME_MOUNTPOINT.toString()));
        } catch (URISyntaxException e) {
            throw new IOException("URISyntax exception: " + theUri);
        }

    }

    /**
     * Convenience Constructor for apps to call directly
     * @param theUri which must be that of ViewFileSystem
     * @param conf
     * @throws IOException
     */
    ViewFileSystem(final URI theUri, final Configuration conf) throws IOException {
        this();
        initialize(theUri, conf);
    }

    /**
     * Convenience Constructor for apps to call directly
     * @param conf
     * @throws IOException
     */
    public ViewFileSystem(final Configuration conf) throws IOException {
        this(FsConstants.VIEWFS_URI, conf);
    }

    @Override
    public URI getUri() {
        return myUri;
    }

    @Override
    public Path resolvePath(final Path f) throws IOException {
        final InodeTree.ResolveResult<FileSystem> res;
        res = fsState.resolve(getUriPath(f), true);
        if (res.isInternalDir()) {
            return f;
        }
        return res.targetFileSystem.resolvePath(res.remainingPath);
    }

    @Override
    public Path getHomeDirectory() {
        if (homeDir == null) {
            String base = fsState.getHomeDirPrefixValue();
            if (base == null) {
                base = "/user";
            }
            homeDir = (base.equals("/") ? this.makeQualified(new Path(base + ugi.getShortUserName()))
                    : this.makeQualified(new Path(base + "/" + ugi.getShortUserName())));
        }
        return homeDir;
    }

    @Override
    public Path getWorkingDirectory() {
        return workingDir;
    }

    @Override
    public void setWorkingDirectory(final Path new_dir) {
        getUriPath(new_dir); // this validates the path
        workingDir = makeAbsolute(new_dir);
    }

    @Override
    public FSDataOutputStream append(final Path f, final int bufferSize, final Progressable progress)
            throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.append(res.remainingPath, bufferSize, progress);
    }

    @Override
    public FSDataOutputStream createNonRecursive(Path f, FsPermission permission, EnumSet<CreateFlag> flags,
            int bufferSize, short replication, long blockSize, Progressable progress) throws IOException {
        InodeTree.ResolveResult<FileSystem> res;
        try {
            res = fsState.resolve(getUriPath(f), false);
        } catch (FileNotFoundException e) {
            throw readOnlyMountTable("create", f);
        }
        assert (res.remainingPath != null);
        return res.targetFileSystem.createNonRecursive(res.remainingPath, permission, flags, bufferSize,
                replication, blockSize, progress);
    }

    @Override
    public FSDataOutputStream create(final Path f, final FsPermission permission, final boolean overwrite,
            final int bufferSize, final short replication, final long blockSize, final Progressable progress)
            throws IOException {
        InodeTree.ResolveResult<FileSystem> res;
        try {
            res = fsState.resolve(getUriPath(f), false);
        } catch (FileNotFoundException e) {
            throw readOnlyMountTable("create", f);
        }
        assert (res.remainingPath != null);
        return res.targetFileSystem.create(res.remainingPath, permission, overwrite, bufferSize, replication,
                blockSize, progress);
    }

    @Override
    public boolean delete(final Path f, final boolean recursive)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        // If internal dir or target is a mount link (ie remainingPath is Slash)
        if (res.isInternalDir() || res.remainingPath == InodeTree.SlashPath) {
            throw readOnlyMountTable("delete", f);
        }
        return res.targetFileSystem.delete(res.remainingPath, recursive);
    }

    @Override
    @SuppressWarnings("deprecation")
    public boolean delete(final Path f) throws AccessControlException, FileNotFoundException, IOException {
        return delete(f, true);
    }

    @Override
    public BlockLocation[] getFileBlockLocations(FileStatus fs, long start, long len) throws IOException {
        final InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(fs.getPath()), true);
        return res.targetFileSystem.getFileBlockLocations(new ViewFsFileStatus(fs, res.remainingPath), start, len);
    }

    @Override
    public FileChecksum getFileChecksum(final Path f)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.getFileChecksum(res.remainingPath);
    }

    @Override
    public FileChecksum getFileChecksum(final Path f, final long length)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.getFileChecksum(res.remainingPath, length);
    }

    private static FileStatus fixFileStatus(FileStatus orig, Path qualified) throws IOException {
        // FileStatus#getPath is a fully qualified path relative to the root of
        // target file system.
        // We need to change it to viewfs URI - relative to root of mount table.

        // The implementors of RawLocalFileSystem were trying to be very smart.
        // They implement FileStatus#getOwner lazily -- the object
        // returned is really a RawLocalFileSystem that expect the
        // FileStatus#getPath to be unchanged so that it can get owner when needed.
        // Hence we need to interpose a new ViewFileSystemFileStatus that
        // works around.
        if ("file".equals(orig.getPath().toUri().getScheme())) {
            orig = wrapLocalFileStatus(orig, qualified);
        }

        orig.setPath(qualified);
        return orig;
    }

    private static FileStatus wrapLocalFileStatus(FileStatus orig, Path qualified) {
        return orig instanceof LocatedFileStatus ? new ViewFsLocatedFileStatus((LocatedFileStatus) orig, qualified)
                : new ViewFsFileStatus(orig, qualified);
    }

    @Override
    public FileStatus getFileStatus(final Path f)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        FileStatus status = res.targetFileSystem.getFileStatus(res.remainingPath);
        return fixFileStatus(status, this.makeQualified(f));
    }

    @Override
    public void access(Path path, FsAction mode) throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.access(res.remainingPath, mode);
    }

    @Override
    public FileStatus[] listStatus(final Path f) throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);

        FileStatus[] statusLst = res.targetFileSystem.listStatus(res.remainingPath);
        if (!res.isInternalDir()) {
            // We need to change the name in the FileStatus as described in
            // {@link #getFileStatus }
            int i = 0;
            for (FileStatus status : statusLst) {
                statusLst[i++] = fixFileStatus(status, getChrootedPath(res, status, f));
            }
        }
        return statusLst;
    }

    @Override
    public RemoteIterator<LocatedFileStatus> listLocatedStatus(final Path f, final PathFilter filter)
            throws FileNotFoundException, IOException {
        final InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        final RemoteIterator<LocatedFileStatus> statusIter = res.targetFileSystem
                .listLocatedStatus(res.remainingPath);

        if (res.isInternalDir()) {
            return statusIter;
        }

        return new RemoteIterator<LocatedFileStatus>() {
            @Override
            public boolean hasNext() throws IOException {
                return statusIter.hasNext();
            }

            @Override
            public LocatedFileStatus next() throws IOException {
                final LocatedFileStatus status = statusIter.next();
                return (LocatedFileStatus) fixFileStatus(status, getChrootedPath(res, status, f));
            }
        };
    }

    private Path getChrootedPath(InodeTree.ResolveResult<FileSystem> res, FileStatus status, Path f)
            throws IOException {
        final String suffix;
        if (res.targetFileSystem instanceof ChRootedFileSystem) {
            suffix = ((ChRootedFileSystem) res.targetFileSystem).stripOutRoot(status.getPath());
        } else { // nfly
            suffix = ((NflyFSystem.NflyStatus) status).stripRoot();
        }
        return this.makeQualified(suffix.length() == 0 ? f : new Path(res.resolvedPath, suffix));
    }

    @Override
    public boolean mkdirs(final Path dir, final FsPermission permission) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(dir), false);
        return res.targetFileSystem.mkdirs(res.remainingPath, permission);
    }

    @Override
    public FSDataInputStream open(final Path f, final int bufferSize)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.open(res.remainingPath, bufferSize);
    }

    @Override
    public boolean rename(final Path src, final Path dst) throws IOException {
        // passing resolveLastComponet as false to catch renaming a mount point to 
        // itself. We need to catch this as an internal operation and fail.
        InodeTree.ResolveResult<FileSystem> resSrc = fsState.resolve(getUriPath(src), false);

        if (resSrc.isInternalDir()) {
            throw readOnlyMountTable("rename", src);
        }

        InodeTree.ResolveResult<FileSystem> resDst = fsState.resolve(getUriPath(dst), false);
        if (resDst.isInternalDir()) {
            throw readOnlyMountTable("rename", dst);
        }

        URI srcUri = resSrc.targetFileSystem.getUri();
        URI dstUri = resDst.targetFileSystem.getUri();

        verifyRenameStrategy(srcUri, dstUri, resSrc.targetFileSystem == resDst.targetFileSystem, renameStrategy);

        if (resSrc.targetFileSystem instanceof ChRootedFileSystem
                && resDst.targetFileSystem instanceof ChRootedFileSystem) {
            ChRootedFileSystem srcFS = (ChRootedFileSystem) resSrc.targetFileSystem;
            ChRootedFileSystem dstFS = (ChRootedFileSystem) resDst.targetFileSystem;
            return srcFS.getMyFs().rename(srcFS.fullPath(resSrc.remainingPath),
                    dstFS.fullPath(resDst.remainingPath));
        } else {
            return resSrc.targetFileSystem.rename(resSrc.remainingPath, resDst.remainingPath);
        }
    }

    static void verifyRenameStrategy(URI srcUri, URI dstUri, boolean isSrcDestSame,
            ViewFileSystem.RenameStrategy renameStrategy) throws IOException {
        switch (renameStrategy) {
        case SAME_FILESYSTEM_ACROSS_MOUNTPOINT:
            if (srcUri.getAuthority() != null) {
                if (!(srcUri.getScheme().equals(dstUri.getScheme())
                        && srcUri.getAuthority().equals(dstUri.getAuthority()))) {
                    throw new IOException("Renames across Mount points not supported");
                }
            }

            break;
        case SAME_TARGET_URI_ACROSS_MOUNTPOINT:
            // Alternate 2: Rename across mountpoints with same target.
            // i.e. Rename across alias mountpoints.
            //
            // Note we compare the URIs. the URIs include the link targets.
            // hence we allow renames across mount links as long as the mount links
            // point to the same target.
            if (!srcUri.equals(dstUri)) {
                throw new IOException("Renames across Mount points not supported");
            }

            break;
        case SAME_MOUNTPOINT:
            //
            // Alternate 3 : renames ONLY within the the same mount links.
            //
            if (!isSrcDestSame) {
                throw new IOException("Renames across Mount points not supported");
            }
            break;
        default:
            throw new IllegalArgumentException("Unexpected rename strategy");
        }
    }

    @Override
    public boolean truncate(final Path f, final long newLength) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.truncate(res.remainingPath, newLength);
    }

    @Override
    public void setOwner(final Path f, final String username, final String groupname)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        res.targetFileSystem.setOwner(res.remainingPath, username, groupname);
    }

    @Override
    public void setPermission(final Path f, final FsPermission permission)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        res.targetFileSystem.setPermission(res.remainingPath, permission);
    }

    @Override
    public boolean setReplication(final Path f, final short replication)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.setReplication(res.remainingPath, replication);
    }

    @Override
    public void setTimes(final Path f, final long mtime, final long atime)
            throws AccessControlException, FileNotFoundException, IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        res.targetFileSystem.setTimes(res.remainingPath, mtime, atime);
    }

    @Override
    public void modifyAclEntries(Path path, List<AclEntry> aclSpec) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.modifyAclEntries(res.remainingPath, aclSpec);
    }

    @Override
    public void removeAclEntries(Path path, List<AclEntry> aclSpec) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.removeAclEntries(res.remainingPath, aclSpec);
    }

    @Override
    public void removeDefaultAcl(Path path) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.removeDefaultAcl(res.remainingPath);
    }

    @Override
    public void removeAcl(Path path) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.removeAcl(res.remainingPath);
    }

    @Override
    public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.setAcl(res.remainingPath, aclSpec);
    }

    @Override
    public AclStatus getAclStatus(Path path) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        return res.targetFileSystem.getAclStatus(res.remainingPath);
    }

    @Override
    public void setXAttr(Path path, String name, byte[] value, EnumSet<XAttrSetFlag> flag) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.setXAttr(res.remainingPath, name, value, flag);
    }

    @Override
    public byte[] getXAttr(Path path, String name) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        return res.targetFileSystem.getXAttr(res.remainingPath, name);
    }

    @Override
    public Map<String, byte[]> getXAttrs(Path path) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        return res.targetFileSystem.getXAttrs(res.remainingPath);
    }

    @Override
    public Map<String, byte[]> getXAttrs(Path path, List<String> names) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        return res.targetFileSystem.getXAttrs(res.remainingPath, names);
    }

    @Override
    public List<String> listXAttrs(Path path) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        return res.targetFileSystem.listXAttrs(res.remainingPath);
    }

    @Override
    public void removeXAttr(Path path, String name) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.removeXAttr(res.remainingPath, name);
    }

    @Override
    public void setVerifyChecksum(final boolean verifyChecksum) {
        List<InodeTree.MountPoint<FileSystem>> mountPoints = fsState.getMountPoints();
        for (InodeTree.MountPoint<FileSystem> mount : mountPoints) {
            mount.target.targetFileSystem.setVerifyChecksum(verifyChecksum);
        }
    }

    @Override
    public long getDefaultBlockSize() {
        throw new NotInMountpointException("getDefaultBlockSize");
    }

    @Override
    public short getDefaultReplication() {
        throw new NotInMountpointException("getDefaultReplication");
    }

    @Override
    public FsServerDefaults getServerDefaults() throws IOException {
        throw new NotInMountpointException("getServerDefaults");
    }

    @Override
    public long getDefaultBlockSize(Path f) {
        try {
            InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
            return res.targetFileSystem.getDefaultBlockSize(res.remainingPath);
        } catch (FileNotFoundException e) {
            throw new NotInMountpointException(f, "getDefaultBlockSize");
        }
    }

    @Override
    public short getDefaultReplication(Path f) {
        try {
            InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
            return res.targetFileSystem.getDefaultReplication(res.remainingPath);
        } catch (FileNotFoundException e) {
            throw new NotInMountpointException(f, "getDefaultReplication");
        }
    }

    @Override
    public FsServerDefaults getServerDefaults(Path f) throws IOException {
        try {
            InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
            return res.targetFileSystem.getServerDefaults(res.remainingPath);
        } catch (FileNotFoundException e) {
            throw new NotInMountpointException(f, "getServerDefaults");
        }
    }

    @Override
    public ContentSummary getContentSummary(Path f) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.getContentSummary(res.remainingPath);
    }

    @Override
    public QuotaUsage getQuotaUsage(Path f) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(f), true);
        return res.targetFileSystem.getQuotaUsage(res.remainingPath);
    }

    @Override
    public void setWriteChecksum(final boolean writeChecksum) {
        List<InodeTree.MountPoint<FileSystem>> mountPoints = fsState.getMountPoints();
        for (InodeTree.MountPoint<FileSystem> mount : mountPoints) {
            mount.target.targetFileSystem.setWriteChecksum(writeChecksum);
        }
    }

    @Override
    public FileSystem[] getChildFileSystems() {
        List<InodeTree.MountPoint<FileSystem>> mountPoints = fsState.getMountPoints();
        Set<FileSystem> children = new HashSet<FileSystem>();
        for (InodeTree.MountPoint<FileSystem> mountPoint : mountPoints) {
            FileSystem targetFs = mountPoint.target.targetFileSystem;
            children.addAll(Arrays.asList(targetFs.getChildFileSystems()));
        }
        return children.toArray(new FileSystem[] {});
    }

    public MountPoint[] getMountPoints() {
        List<InodeTree.MountPoint<FileSystem>> mountPoints = fsState.getMountPoints();

        MountPoint[] result = new MountPoint[mountPoints.size()];
        for (int i = 0; i < mountPoints.size(); ++i) {
            result[i] = new MountPoint(new Path(mountPoints.get(i).src),
                    mountPoints.get(i).target.targetDirLinkList);
        }
        return result;
    }

    @Override
    public Path createSnapshot(Path path, String snapshotName) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        return res.targetFileSystem.createSnapshot(res.remainingPath, snapshotName);
    }

    @Override
    public void renameSnapshot(Path path, String snapshotOldName, String snapshotNewName) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.renameSnapshot(res.remainingPath, snapshotOldName, snapshotNewName);
    }

    @Override
    public void deleteSnapshot(Path path, String snapshotName) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
        res.targetFileSystem.deleteSnapshot(res.remainingPath, snapshotName);
    }

    @Override
    public void setStoragePolicy(Path src, String policyName) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(src), true);
        res.targetFileSystem.setStoragePolicy(res.remainingPath, policyName);
    }

    @Override
    public void unsetStoragePolicy(Path src) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(src), true);
        res.targetFileSystem.unsetStoragePolicy(res.remainingPath);
    }

    @Override
    public BlockStoragePolicySpi getStoragePolicy(Path src) throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(src), true);
        return res.targetFileSystem.getStoragePolicy(res.remainingPath);
    }

    @Override
    public Collection<? extends BlockStoragePolicySpi> getAllStoragePolicies() throws IOException {
        Collection<BlockStoragePolicySpi> allPolicies = new HashSet<>();
        for (FileSystem fs : getChildFileSystems()) {
            try {
                Collection<? extends BlockStoragePolicySpi> policies = fs.getAllStoragePolicies();
                allPolicies.addAll(policies);
            } catch (UnsupportedOperationException e) {
                // ignored
            }
        }
        return allPolicies;
    }

    /**
     * Get the trash root directory for current user when the path
     * specified is deleted.
     *
     * @param path the trash root of the path to be determined.
     * @return the trash root path.
     */
    @Override
    public Path getTrashRoot(Path path) {
        try {
            InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(path), true);
            return res.targetFileSystem.getTrashRoot(res.remainingPath);
        } catch (Exception e) {
            throw new NotInMountpointException(path, "getTrashRoot");
        }
    }

    /**
     * Get all the trash roots for current user or all users.
     *
     * @param allUsers return trash roots for all users if true.
     * @return all Trash root directories.
     */
    @Override
    public Collection<FileStatus> getTrashRoots(boolean allUsers) {
        List<FileStatus> trashRoots = new ArrayList<>();
        for (FileSystem fs : getChildFileSystems()) {
            trashRoots.addAll(fs.getTrashRoots(allUsers));
        }
        return trashRoots;
    }

    @Override
    public FsStatus getStatus() throws IOException {
        return getStatus(null);
    }

    @Override
    public FsStatus getStatus(Path p) throws IOException {
        if (p == null) {
            p = InodeTree.SlashPath;
        }
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(p), true);
        return res.targetFileSystem.getStatus(p);
    }

    /**
     * Return the total size of all files under "/", if {@link
     * Constants#CONFIG_VIEWFS_LINK_MERGE_SLASH} is supported and is a valid
     * mount point. Else, throw NotInMountpointException.
     *
     * @throws IOException
     */
    @Override
    public long getUsed() throws IOException {
        InodeTree.ResolveResult<FileSystem> res = fsState.resolve(getUriPath(InodeTree.SlashPath), true);
        if (res.isInternalDir()) {
            throw new NotInMountpointException(InodeTree.SlashPath, "getUsed");
        } else {
            return res.targetFileSystem.getUsed();
        }
    }

    @Override
    public Path getLinkTarget(Path path) throws IOException {
        InodeTree.ResolveResult<FileSystem> res;
        try {
            res = fsState.resolve(getUriPath(path), true);
        } catch (FileNotFoundException e) {
            throw new NotInMountpointException(path, "getLinkTarget");
        }
        return res.targetFileSystem.getLinkTarget(res.remainingPath);
    }

    /**
     * An instance of this class represents an internal dir of the viewFs
     * that is internal dir of the mount table.
     * It is a read only mount tables and create, mkdir or delete operations
     * are not allowed.
     * If called on create or mkdir then this target is the parent of the
     * directory in which one is trying to create or mkdir; hence
     * in this case the path name passed in is the last component. 
     * Otherwise this target is the end point of the path and hence
     * the path name passed in is null. 
     */
    static class InternalDirOfViewFs extends FileSystem {
        final InodeTree.INodeDir<FileSystem> theInternalDir;
        final long creationTime; // of the the mount table
        final UserGroupInformation ugi; // the user/group of user who created mtable
        final URI myUri;

        public InternalDirOfViewFs(final InodeTree.INodeDir<FileSystem> dir, final long cTime,
                final UserGroupInformation ugi, URI uri, Configuration config) throws URISyntaxException {
            myUri = uri;
            try {
                initialize(myUri, config);
            } catch (IOException e) {
                throw new RuntimeException("Cannot occur");
            }
            theInternalDir = dir;
            creationTime = cTime;
            this.ugi = ugi;
        }

        static private void checkPathIsSlash(final Path f) throws IOException {
            if (f != InodeTree.SlashPath) {
                throw new IOException("Internal implementation error: expected file name to be /");
            }
        }

        @Override
        public URI getUri() {
            return myUri;
        }

        @Override
        public Path getWorkingDirectory() {
            throw new RuntimeException("Internal impl error: getWorkingDir should not have been called");
        }

        @Override
        public void setWorkingDirectory(final Path new_dir) {
            throw new RuntimeException("Internal impl error: getWorkingDir should not have been called");
        }

        @Override
        public FSDataOutputStream append(final Path f, final int bufferSize, final Progressable progress)
                throws IOException {
            throw readOnlyMountTable("append", f);
        }

        @Override
        public FSDataOutputStream create(final Path f, final FsPermission permission, final boolean overwrite,
                final int bufferSize, final short replication, final long blockSize, final Progressable progress)
                throws AccessControlException {
            throw readOnlyMountTable("create", f);
        }

        @Override
        public boolean delete(final Path f, final boolean recursive) throws AccessControlException, IOException {
            checkPathIsSlash(f);
            throw readOnlyMountTable("delete", f);
        }

        @Override
        @SuppressWarnings("deprecation")
        public boolean delete(final Path f) throws AccessControlException, IOException {
            return delete(f, true);
        }

        @Override
        public BlockLocation[] getFileBlockLocations(final FileStatus fs, final long start, final long len)
                throws FileNotFoundException, IOException {
            checkPathIsSlash(fs.getPath());
            throw new FileNotFoundException("Path points to dir not a file");
        }

        @Override
        public FileChecksum getFileChecksum(final Path f) throws FileNotFoundException, IOException {
            checkPathIsSlash(f);
            throw new FileNotFoundException("Path points to dir not a file");
        }

        @Override
        public FileStatus getFileStatus(Path f) throws IOException {
            checkPathIsSlash(f);
            return new FileStatus(0, true, 0, 0, creationTime, creationTime, PERMISSION_555, ugi.getShortUserName(),
                    ugi.getPrimaryGroupName(),

                    new Path(theInternalDir.fullPath).makeQualified(myUri, ROOT_PATH));
        }

        @Override
        public FileStatus[] listStatus(Path f) throws AccessControlException, FileNotFoundException, IOException {
            checkPathIsSlash(f);
            FileStatus[] result = new FileStatus[theInternalDir.getChildren().size()];
            int i = 0;
            for (Entry<String, INode<FileSystem>> iEntry : theInternalDir.getChildren().entrySet()) {
                INode<FileSystem> inode = iEntry.getValue();
                if (inode.isLink()) {
                    INodeLink<FileSystem> link = (INodeLink<FileSystem>) inode;

                    result[i++] = new FileStatus(0, false, 0, 0, creationTime, creationTime, PERMISSION_555,
                            ugi.getShortUserName(), ugi.getPrimaryGroupName(), link.getTargetLink(),
                            new Path(inode.fullPath).makeQualified(myUri, null));
                } else {
                    result[i++] = new FileStatus(0, true, 0, 0, creationTime, creationTime, PERMISSION_555,
                            ugi.getShortUserName(), ugi.getGroupNames()[0],
                            new Path(inode.fullPath).makeQualified(myUri, null));
                }
            }
            return result;
        }

        @Override
        public boolean mkdirs(Path dir, FsPermission permission)
                throws AccessControlException, FileAlreadyExistsException {
            if (theInternalDir.isRoot() && dir == null) {
                throw new FileAlreadyExistsException("/ already exits");
            }
            // Note dir starts with /
            if (theInternalDir.getChildren().containsKey(dir.toString().substring(1))) {
                return true; // this is the stupid semantics of FileSystem
            }
            throw readOnlyMountTable("mkdirs", dir);
        }

        @Override
        public FSDataInputStream open(Path f, int bufferSize)
                throws AccessControlException, FileNotFoundException, IOException {
            checkPathIsSlash(f);
            throw new FileNotFoundException("Path points to dir not a file");
        }

        @Override
        public boolean rename(Path src, Path dst) throws AccessControlException, IOException {
            checkPathIsSlash(src);
            checkPathIsSlash(dst);
            throw readOnlyMountTable("rename", src);
        }

        @Override
        public boolean truncate(Path f, long newLength) throws IOException {
            throw readOnlyMountTable("truncate", f);
        }

        @Override
        public void setOwner(Path f, String username, String groupname) throws AccessControlException, IOException {
            checkPathIsSlash(f);
            throw readOnlyMountTable("setOwner", f);
        }

        @Override
        public void setPermission(Path f, FsPermission permission) throws AccessControlException, IOException {
            checkPathIsSlash(f);
            throw readOnlyMountTable("setPermission", f);
        }

        @Override
        public boolean setReplication(Path f, short replication) throws AccessControlException, IOException {
            checkPathIsSlash(f);
            throw readOnlyMountTable("setReplication", f);
        }

        @Override
        public void setTimes(Path f, long mtime, long atime) throws AccessControlException, IOException {
            checkPathIsSlash(f);
            throw readOnlyMountTable("setTimes", f);
        }

        @Override
        public void setVerifyChecksum(boolean verifyChecksum) {
            // Noop for viewfs
        }

        @Override
        public FsServerDefaults getServerDefaults(Path f) throws IOException {
            throw new NotInMountpointException(f, "getServerDefaults");
        }

        @Override
        public long getDefaultBlockSize(Path f) {
            throw new NotInMountpointException(f, "getDefaultBlockSize");
        }

        @Override
        public short getDefaultReplication(Path f) {
            throw new NotInMountpointException(f, "getDefaultReplication");
        }

        @Override
        public void modifyAclEntries(Path path, List<AclEntry> aclSpec) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("modifyAclEntries", path);
        }

        @Override
        public void removeAclEntries(Path path, List<AclEntry> aclSpec) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("removeAclEntries", path);
        }

        @Override
        public void removeDefaultAcl(Path path) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("removeDefaultAcl", path);
        }

        @Override
        public void removeAcl(Path path) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("removeAcl", path);
        }

        @Override
        public void setAcl(Path path, List<AclEntry> aclSpec) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("setAcl", path);
        }

        @Override
        public AclStatus getAclStatus(Path path) throws IOException {
            checkPathIsSlash(path);
            return new AclStatus.Builder().owner(ugi.getShortUserName()).group(ugi.getPrimaryGroupName())
                    .addEntries(AclUtil.getMinimalAcl(PERMISSION_555)).stickyBit(false).build();
        }

        @Override
        public void setXAttr(Path path, String name, byte[] value, EnumSet<XAttrSetFlag> flag) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("setXAttr", path);
        }

        @Override
        public byte[] getXAttr(Path path, String name) throws IOException {
            throw new NotInMountpointException(path, "getXAttr");
        }

        @Override
        public Map<String, byte[]> getXAttrs(Path path) throws IOException {
            throw new NotInMountpointException(path, "getXAttrs");
        }

        @Override
        public Map<String, byte[]> getXAttrs(Path path, List<String> names) throws IOException {
            throw new NotInMountpointException(path, "getXAttrs");
        }

        @Override
        public List<String> listXAttrs(Path path) throws IOException {
            throw new NotInMountpointException(path, "listXAttrs");
        }

        @Override
        public void removeXAttr(Path path, String name) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("removeXAttr", path);
        }

        @Override
        public Path createSnapshot(Path path, String snapshotName) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("createSnapshot", path);
        }

        @Override
        public void renameSnapshot(Path path, String snapshotOldName, String snapshotNewName) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("renameSnapshot", path);
        }

        @Override
        public void deleteSnapshot(Path path, String snapshotName) throws IOException {
            checkPathIsSlash(path);
            throw readOnlyMountTable("deleteSnapshot", path);
        }

        @Override
        public QuotaUsage getQuotaUsage(Path f) throws IOException {
            throw new NotInMountpointException(f, "getQuotaUsage");
        }

        @Override
        public void setStoragePolicy(Path src, String policyName) throws IOException {
            checkPathIsSlash(src);
            throw readOnlyMountTable("setStoragePolicy", src);
        }

        @Override
        public void unsetStoragePolicy(Path src) throws IOException {
            checkPathIsSlash(src);
            throw readOnlyMountTable("unsetStoragePolicy", src);
        }

        @Override
        public BlockStoragePolicySpi getStoragePolicy(Path src) throws IOException {
            throw new NotInMountpointException(src, "getStoragePolicy");
        }

        @Override
        public Collection<? extends BlockStoragePolicySpi> getAllStoragePolicies() throws IOException {
            Collection<BlockStoragePolicySpi> allPolicies = new HashSet<>();
            for (FileSystem fs : getChildFileSystems()) {
                try {
                    Collection<? extends BlockStoragePolicySpi> policies = fs.getAllStoragePolicies();
                    allPolicies.addAll(policies);
                } catch (UnsupportedOperationException e) {
                    // ignored
                }
            }
            return allPolicies;
        }
    }

    enum RenameStrategy {
        SAME_MOUNTPOINT, SAME_TARGET_URI_ACROSS_MOUNTPOINT, SAME_FILESYSTEM_ACROSS_MOUNTPOINT
    }
}