blob: 8604c7d8bfcdccad5a09147eb1b207a1b99eeb55 [file] [log] [blame]
/**
* 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;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.classification.InterfaceAudience.LimitedPrivate.Project;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.fs.Options.CreateOpts;
import org.apache.hadoop.fs.Options.Rename;
import org.apache.hadoop.fs.permission.FsPermission;
import org.apache.hadoop.io.IOUtils;
import org.apache.hadoop.util.Progressable;
/**
* The FileContext class provides an interface to the application writer for
* using the Hadoop filesystem.
* It provides a set of methods for the usual operation: create, open,
* list, etc
*
* *** Path Names ***
*
* The Hadoop filesystem supports a URI name space and URI names.
* It offers a a forest of filesystems that can be referenced using fully
* qualified URIs.
*
* Two common Hadoop filesystems implementations are
* the local filesystem: file:///path
* the hdfs filesystem hdfs://nnAddress:nnPort/path
*
* While URI names are very flexible, it requires knowing the name or address
* of the server. For convenience one often wants to access the default system
* in your environment without knowing its name/address. This has an
* additional benefit that it allows one to change one's default fs
* (say your admin moves you from cluster1 to cluster2).
*
* Too facilitate this Hadoop supports a notion of a default filesystem.
* The user can set his default filesystem, although this is
* typically set up for you in your environment in your default config.
* A default filesystem implies a default scheme and authority; slash-relative
* names (such as /for/bar) are resolved relative to that default FS.
* Similarly a user can also have working-directory-relative names (i.e. names
* not starting with a slash). While the working directory is generally in the
* same default FS, the wd can be in a different FS; in particular, changing
* the default filesystem DOES NOT change the working directory,
*
* Hence Hadoop path names can be one of:
* fully qualified URI: scheme://authority/path
* slash relative names: /path - relative to the default filesystem
* wd-relative names: path - relative to the working dir
*
* Relative paths with scheme (scheme:foo/bar) are illegal
*
* ****The Role of the FileContext and configuration defaults****
* The FileContext provides file namespace context for resolving file names;
* it also contains the umask for permissions, In that sense it is like the
* per-process file-related state in Unix system.
* These, in general, are obtained from the default configuration file
* in your environment, (@see {@link Configuration}
*
* No other configuration parameters are obtained from the default config as
* far as the file context layer is concerned. All filesystem instances
* (i.e. deployments of filesystems) have default properties; we call these
* server side (SS) defaults. Operation like create allow one to select many
* properties: either pass them in as explicit parameters or
* one can choose to used the SS properties.
*
* The filesystem related SS defaults are
* - the home directory (default is "/user/<userName>")
* - the initial wd (only for local fs)
* - replication factor
* - block size
* - buffer size
* - bytesPerChecksum (if used).
*
*
* *** Usage Model for the FileContext class ***
*
* Example 1: use the default config read from the $HADOOP_CONFIG/core.xml.
* Unspecified values come from core-defaults.xml in the release jar.
*
* myFiles = getFileContext(); // uses the default config
* myFiles.create(path, ...);
* myFiles.setWorkingDir(path)
* myFiles.open (path, ...);
*
* Example 2: Use a specific config, ignoring $HADOOP_CONFIG
* configX = someConfigSomeOnePassedToYou.
* myFContext = getFileContext(configX); //configX not changed but passeddown
* myFContext.create(path, ...);
* myFContext.setWorkingDir(path)
*
* Other ways of creating new FileContexts:
* getLocalFSFileContext(...) // local filesystem is the default FS
* getLocalFileContext(URI, ...) // where specified URI is default FS.
*
*/
@InterfaceAudience.Public
@InterfaceStability.Evolving /*Evolving for a release,to be changed to Stable */
public final class FileContext {
public static final Log LOG = LogFactory.getLog(FileContext.class);
/**
* List of files that should be deleted on JVM shutdown
*/
final static Map<FileContext, Set<Path>> deleteOnExit =
new IdentityHashMap<FileContext, Set<Path>>();
/** JVM shutdown hook thread */
final static FileContextFinalizer finalizer =
new FileContextFinalizer();
/**
* The FileContext is defined by.
* 1) defaultFS (slash)
* 2) wd
* 3) umask
*
*/
private final FileSystem defaultFS; // the default FS for this FileContext.
private Path workingDir; // Fully qualified
private FsPermission umask;
private final Configuration conf; // passed to the filesystem below
// When we move to new AbstractFileSystem
// then it is not really needed except for
// undocumented config vars;
private FileContext(final FileSystem defFs, final FsPermission theUmask,
final Configuration aConf) {
defaultFS = defFs;
umask = FsPermission.getUMask(aConf);
conf = aConf;
/*
* Init the wd.
* WorkingDir is implemented at the FileContext layer
* NOT at the FileSystem layer.
* If the DefaultFS, such as localFilesystem has a notion of
* builtin WD, we use that as the initial WD.
* Otherwise the WD is initialized to the home directory.
*/
workingDir = defaultFS.getInitialWorkingDirectory();
if (workingDir == null) {
workingDir = defaultFS.getHomeDirectory();
}
util = new Util(); // for the inner class
}
/*
* Remove relative part - return "absolute":
* If input is relative path ("foo/bar") add wd: ie "/<workingDir>/foo/bar"
* A fully qualified uri ("hdfs://nn:p/foo/bar") or a slash-relative path
* ("/foo/bar") are returned unchanged.
*
* Applications that use FileContext should use #makeQualified() since
* they really want a fully qualified URI.
* Hence this method os not called makeAbsolute() and
* has been deliberately declared private.
*/
private Path fixRelativePart(Path f) {
if (f.isUriPathAbsolute()) {
return f;
} else {
return new Path(workingDir, f);
}
}
/**
* Delete all the paths that were marked as delete-on-exit.
*/
static void processDeleteOnExit() {
synchronized (deleteOnExit) {
Set<Entry<FileContext, Set<Path>>> set = deleteOnExit.entrySet();
for (Entry<FileContext, Set<Path>> entry : set) {
FileContext fc = entry.getKey();
Set<Path> paths = entry.getValue();
for (Path path : paths) {
try {
fc.delete(path, true);
}
catch (IOException e) {
LOG.warn("Ignoring failure to deleteOnExit for path " + path);
}
}
}
deleteOnExit.clear();
}
}
/**
* Pathnames with scheme and relative path are illegal.
* @param path to be checked
* @throws IllegalArgumentException if of type scheme:foo/bar
*/
private static void checkNotSchemeWithRelative(final Path path) {
if (path.toUri().isAbsolute() && !path.isUriPathAbsolute()) {
throw new IllegalArgumentException(
"Unsupported name: has scheme but relative path-part");
}
}
/**
* Get the filesystem of supplied path.
* @param absOrFqPath - absolute or fully qualified path
* @return the filesystem of the path
* @throws IOException
*/
private FileSystem getFSofPath(final Path absOrFqPath) throws IOException {
checkNotSchemeWithRelative(absOrFqPath);
if (!absOrFqPath.isAbsolute() && absOrFqPath.toUri().getScheme() == null) {
throw new IllegalArgumentException(
"FileContext Bug: path is relative");
}
// TBD cleanup this impl once we create a new FileSystem to replace current
// one - see HADOOP-6223.
try {
// Is it the default FS for this FileContext?
defaultFS.checkPath(absOrFqPath);
return defaultFS;
} catch (Exception e) { // it is different FileSystem
return FileSystem.get(absOrFqPath.toUri(), conf);
}
}
/**
* Protected Static Factory methods for getting a FileContexts
* that take a FileSystem as input. To be used for testing.
* Protected since new FileSystem will be protected.
* Note new file contexts are created for each call.
*/
/**
* Create a FileContext with specified FS as default
* using the specified config.
*
* @param defFS
* @param aConf
* @return new FileContext with specifed FS as default.
* @throws IOException if the filesystem with specified cannot be created
*/
protected static FileContext getFileContext(final FileSystem defFS,
final Configuration aConf) throws IOException {
return new FileContext(defFS, FsPermission.getUMask(aConf), aConf);
}
/**
* Create a FileContext for specified FileSystem using the default config.
*
* @param defaultFS
* @return a FileSystem for the specified URI
* @throws IOException if the filesysem with specified cannot be created
*/
protected static FileContext getFileContext(final FileSystem defaultFS)
throws IOException {
return getFileContext(defaultFS, new Configuration());
}
public static final URI LOCAL_FS_URI = URI.create("file:///");
public static final FsPermission DEFAULT_PERM = FsPermission.getDefault();
/**
* Static Factory methods for getting a FileContext.
* Note new file contexts are created for each call.
* The only singleton is the local FS context using the default config.
*
* Methods that use the default config: the default config read from the
* $HADOOP_CONFIG/core.xml,
* Unspecified key-values for config are defaulted from core-defaults.xml
* in the release jar.
*
* The keys relevant to the FileContext layer are extracted at time of
* construction. Changes to the config after the call are ignore
* by the FileContext layer.
* The conf is passed to lower layers like FileSystem and HDFS which
* pick up their own config variables.
*/
/**
* Create a FileContext using the default config read from the
* $HADOOP_CONFIG/core.xml,
* Unspecified key-values for config are defaulted from core-defaults.xml
* in the release jar.
*
* @throws IOException if default FileSystem in the config cannot be created
*/
public static FileContext getFileContext() throws IOException {
return getFileContext(new Configuration());
}
private static FileContext localFsSingleton = null;
/**
*
* @return a FileContext for the local filesystem using the default config.
* @throws IOException
*/
public static FileContext getLocalFSFileContext() throws IOException {
if (localFsSingleton == null) {
localFsSingleton = getFileContext(LOCAL_FS_URI);
}
return localFsSingleton;
}
/**
* Create a FileContext for specified URI using the default config.
*
* @param defaultFsUri
* @return a FileSystem for the specified URI
* @throws IOException if the filesysem with specified cannot be created
*/
public static FileContext getFileContext(final URI defaultFsUri)
throws IOException {
return getFileContext(defaultFsUri, new Configuration());
}
/**
* Create a FileContext for specified default URI using the specified config.
*
* @param defaultFsUri
* @param aConf
* @return new FileContext for specified uri
* @throws IOException if the filesysem with specified cannot be created
*/
public static FileContext getFileContext(final URI defaultFsUri,
final Configuration aConf) throws IOException {
return getFileContext(FileSystem.get(defaultFsUri, aConf), aConf);
}
/**
* Create a FileContext using the passed config.
*
* @param aConf
* @return new FileContext
* @throws IOException if default FileSystem in the config cannot be created
*/
public static FileContext getFileContext(final Configuration aConf)
throws IOException {
return getFileContext(URI.create(FsConfig.getDefaultFsURI(aConf)), aConf);
}
/**
* @param aConf - from which the FileContext is configured
* @return a FileContext for the local filesystem using the specified config.
* @throws IOException
*/
public static FileContext getLocalFSFileContext(final Configuration aConf)
throws IOException {
return getFileContext(LOCAL_FS_URI, aConf);
}
/* This method is needed for tests. */
@InterfaceAudience.Private
@InterfaceStability.Unstable /* return type will change to AFS once
HADOOP-6223 is completed */
protected FileSystem getDefaultFileSystem() {
return defaultFS;
}
/**
* Set the working directory for wd-relative names (such a "foo/bar").
* @param newWDir
* @throws IOException
*
* newWdir can be one of
* - relative path: "foo/bar";
* - absolute without scheme: "/foo/bar"
* - fully qualified with scheme: "xx://auth/foo/bar"
* Illegal WDs:
* - relative with scheme: "xx:foo/bar"
*/
public void setWorkingDirectory(final Path newWDir) throws IOException {
checkNotSchemeWithRelative(newWDir);
// wd is stored as fully qualified path.
final Path newWorkingDir = new Path(workingDir, newWDir);
FileStatus status = getFileStatus(newWorkingDir);
if (!status.isDir()) {
throw new FileNotFoundException(" Cannot setWD to a file");
}
workingDir = newWorkingDir;
}
/**
* Gets the working directory for wd-relative names (such a "foo/bar").
*/
public Path getWorkingDirectory() {
return workingDir;
}
/**
*
* @return the umask of this FileContext
*/
public FsPermission getUMask() {
return umask;
}
/**
* Set umask to the supplied parameter.
* @param newUmask the new umask
*/
public void setUMask(final FsPermission newUmask) {
umask = newUmask;
}
/**
* Make the path fully qualified if it is isn't.
* A Fully-qualified path has scheme and authority specified and an absolute
* path.
* Use the default filesystem and working dir in this FileContext to qualify.
* @param path
* @return qualified path
*/
public Path makeQualified(final Path path) {
return path.makeQualified(defaultFS.getUri(), getWorkingDirectory());
}
/**
* Create or overwrite file on indicated path and returns an output stream
* for writing into the file.
* @param f the file name to open
* @param createFlag gives the semantics of create: overwrite, append etc.
* @param opts - varargs of CreateOpt:
* Progress - to report progress on the operation - default null
* Permission - umask is applied against permisssion:
* default FsPermissions:getDefault()
* @see #setPermission(Path, FsPermission)
* CreateParent - create missing parent path
* default is to not create parents
*
* The defaults for the following are SS defaults of the
* file server implementing the tart path.
* Not all parameters make sense for all kinds of filesystem
* - eg. localFS ignores Blocksize, replication, checksum
* BufferSize - buffersize used in FSDataOutputStream
* Blocksize - block size for file blocks
* ReplicationFactor - replication for blocks
* BytesPerChecksum - bytes per checksum
*
*
* @throws IOException
*/
@SuppressWarnings("deprecation") // call to primitiveCreate
public FSDataOutputStream create(final Path f,
final EnumSet<CreateFlag> createFlag,
CreateOpts... opts)
throws IOException {
Path absF = fixRelativePart(f);
FileSystem fsOfAbsF = getFSofPath(absF);
// If one of the options is a permission, extract it & apply umask
// If not, add a default Perms and apply umask;
// FileSystem#create
FsPermission permission = null;
if (opts != null) {
for (int i = 0; i < opts.length; ++i) {
if (opts[i] instanceof CreateOpts.Perms) {
if (permission != null)
throw new IllegalArgumentException("multiple permissions varargs");
permission = ((CreateOpts.Perms) opts[i]).getValue();
opts[i] = CreateOpts.perms(permission.applyUMask(umask));
}
}
}
CreateOpts[] theOpts = opts;
if (permission == null) { // no permission was set
CreateOpts[] newOpts = new CreateOpts[opts.length + 1];
System.arraycopy(opts, 0, newOpts, 0, opts.length);
newOpts[opts.length] =
CreateOpts.perms(FsPermission.getDefault().applyUMask(umask));
theOpts = newOpts;
}
return fsOfAbsF.primitiveCreate(absF, createFlag, theOpts);
}
/**
* Make the given file and all non-existent parents into
* directories.
*
* @param dir - the dir to make
* @param permission - permissions is set permission&~umask
* @param createParent - if true then missing parent dirs are created
* if false then parent must exist
* @throws IOException when operation fails not authorized or
* if parent does not exist and createParent is false.
*/
@SuppressWarnings("deprecation") // call to primitiveMkdir
public void mkdir(final Path dir, final FsPermission permission,
final boolean createParent)
throws IOException {
Path absDir = fixRelativePart(dir);
FsPermission absFerms = (permission == null ?
FsPermission.getDefault() : permission).applyUMask(umask);
getFSofPath(absDir).primitiveMkdir(absDir, absFerms, createParent);
}
/**
* Delete a file.
* @param f the path to delete.
* @param recursive if path is a directory and set to
* true, the directory is deleted else throws an exception. In
* case of a file the recursive can be set to either true or false.
* @return true if delete is successful else false.
* @throws IOException
*/
public boolean delete(final Path f, final boolean recursive)
throws IOException {
Path absF = fixRelativePart(f);
return getFSofPath(absF).delete(absF, recursive);
}
/**
* Opens an FSDataInputStream at the indicated Path using
* default buffersize.
* @param f the file name to open
*/
public FSDataInputStream open(final Path f) throws IOException {
final Path absF = fixRelativePart(f);
return getFSofPath(absF).open(absF);
}
/**
* Opens an FSDataInputStream at the indicated Path.
* @param f the file name to open
* @param bufferSize the size of the buffer to be used.
*/
public FSDataInputStream open(final Path f, int bufferSize)
throws IOException {
final Path absF = fixRelativePart(f);
return getFSofPath(absF).open(absF, bufferSize);
}
/**
* Set replication for an existing file.
*
* @param f file name
* @param replication new replication
* @throws IOException
* @return true if successful;
* false if file does not exist or is a directory
*/
public boolean setReplication(final Path f, final short replication)
throws IOException {
final Path absF = fixRelativePart(f);
return getFSofPath(absF).setReplication(absF, replication);
}
/**
* Renames Path src to Path dst
* <ul>
* <li
* <li>Fails if src is a file and dst is a directory.
* <li>Fails if src is a directory and dst is a file.
* <li>Fails if the parent of dst does not exist or is a file.
* </ul>
* <p>
* If OVERWRITE option is not passed as an argument, rename fails
* if the dst already exists.
* <p>
* If OVERWRITE option is passed as an argument, rename overwrites
* the dst if it is a file or an empty directory. Rename fails if dst is
* a non-empty directory.
* <p>
* Note that atomicity of rename is dependent on the file system
* implementation. Please refer to the file system documentation for
* details
* <p>
*
* @param src path to be renamed
* @param dst new path after rename
* @throws IOException on failure
*/
@SuppressWarnings("deprecation")
public void rename(final Path src, final Path dst, final Rename... options)
throws IOException {
final Path absSrc = fixRelativePart(src);
final Path absDst = fixRelativePart(dst);
FileSystem srcFS = getFSofPath(absSrc);
FileSystem dstFS = getFSofPath(absDst);
if(!srcFS.getUri().equals(dstFS.getUri())) {
throw new IOException("Renames across FileSystems not supported");
}
srcFS.rename(absSrc, absDst, options);
}
/**
* Set permission of a path.
* @param f
* @param permission - the new absolute permission (umask is not applied)
*/
public void setPermission(final Path f, final FsPermission permission)
throws IOException {
final Path absF = fixRelativePart(f);
getFSofPath(absF).setPermission(absF, permission);
}
/**
* Set owner of a path (i.e. a file or a directory).
* The parameters username and groupname cannot both be null.
* @param f The path
* @param username If it is null, the original username remains unchanged.
* @param groupname If it is null, the original groupname remains unchanged.
*/
public void setOwner(final Path f, final String username,
final String groupname) throws IOException {
if ((username == null) && (groupname == null)) {
throw new IllegalArgumentException(
"usernme and groupname cannot both be null");
}
final Path absF = fixRelativePart(f);
getFSofPath(absF).setOwner(absF, username, groupname);
}
/**
* Set access time of a file.
* @param f The path
* @param mtime Set the modification time of this file.
* The number of milliseconds since epoch (Jan 1, 1970).
* A value of -1 means that this call should not set modification time.
* @param atime Set the access time of this file.
* The number of milliseconds since Jan 1, 1970.
* A value of -1 means that this call should not set access time.
*/
public void setTimes(final Path f, final long mtime, final long atime)
throws IOException {
final Path absF = fixRelativePart(f);
getFSofPath(absF).setTimes(absF, mtime, atime);
}
/**
* Get the checksum of a file.
*
* @param f The file path
* @return The file checksum. The default return value is null,
* which indicates that no checksum algorithm is implemented
* in the corresponding FileSystem.
*/
public FileChecksum getFileChecksum(final Path f) throws IOException {
final Path absF = fixRelativePart(f);
return getFSofPath(absF).getFileChecksum(absF);
}
/**
* Set the verify checksum flag for the filesystem denoted by the path.
* This is only applicable if the
* corresponding FileSystem supports checksum. By default doesn't do anything.
* @param verifyChecksum
* @param f - set the verifyChecksum for the Filesystem containing this path
* @throws IOException
*/
public void setVerifyChecksum(final boolean verifyChecksum, final Path f)
throws IOException {
final Path absF = fixRelativePart(f);
getFSofPath(absF).setVerifyChecksum(verifyChecksum);
//TBD need to be changed when we add symlinks.
}
/**
* Return a file status object that represents the path.
* @param f The path we want information from
* @return a FileStatus object
* @throws FileNotFoundException when the path does not exist;
* IOException see specific implementation
*/
public FileStatus getFileStatus(final Path f) throws IOException {
final Path absF = fixRelativePart(f);
return getFSofPath(absF).getFileStatus(absF);
}
/**
* Return blockLocation of the given file for the given offset and len.
* For a nonexistent file or regions, null will be returned.
*
* This call is most helpful with DFS, where it returns
* hostnames of machines that contain the given file.
*
* @param p - get blocklocations of this file
* @param start position (byte offset)
* @param len (in bytes)
* @return block locations for given file at specified offset of len
* @throws IOException
*/
@InterfaceAudience.LimitedPrivate({Project.HDFS, Project.MAPREDUCE})
@InterfaceStability.Evolving
public BlockLocation[] getFileBlockLocations(final Path p,
final long start, final long len) throws IOException {
return getFSofPath(p).getFileBlockLocations(p, start, len);
}
/**
* Returns a status object describing the use and capacity of the
* filesystem denoted by the Parh argument p.
* If the filesystem has multiple partitions, the
* use and capacity of the partition pointed to by the specified
* path is reflected.
*
* @param f Path for which status should be obtained. null means the
* root partition of the default filesystem.
* @return a FsStatus object
* @throws IOException
* see specific implementation
*/
public FsStatus getFsStatus(final Path f) throws IOException {
if (f == null) {
return defaultFS.getStatus(null);
}
final Path absF = fixRelativePart(f);
return getFSofPath(absF).getStatus(absF);
}
/**
* Does the file exist?
* Note: Avoid using this method if you already have FileStatus in hand.
* Instead reuse the FileStatus
* @param f the file or dir to be checked
*/
public boolean exists(final Path f) throws IOException {
try {
return getFileStatus(f) != null;
} catch (FileNotFoundException e) {
return false;
}
}
/**
* Is a directory?
* Note: Avoid using this method if you already have FileStatus in hand.
* Instead reuse the FileStatus
* returned by getFileStatus() or listStatus() methods.
*
* @param f Path to evaluate
* @return True iff the named path is a directory.
* @throws IOException
*/
public boolean isDirectory(final Path f) throws IOException {
try {
final Path absF = fixRelativePart(f);
return getFileStatus(absF).isDir();
} catch (FileNotFoundException e) {
return false; // f does not exist
}
}
/** True iff the named path is a regular file.
* Note: Avoid using this method if you already have FileStatus in hand
* Instead reuse the FileStatus
* returned by getFileStatus() or listStatus() methods.
*/
public boolean isFile(final Path f) throws IOException {
try {
final Path absF = fixRelativePart(f);
return !getFileStatus(absF).isDir();
} catch (FileNotFoundException e) {
return false; // f does not exist
}
}
/**
* List the statuses of the files/directories in the given path if the path is
* a directory.
*
* @param f is the path
* @return the statuses of the files/directories in the given path
* @throws IOException
*/
public FileStatus[] listStatus(final Path f) throws IOException {
final Path absF = fixRelativePart(f);
return getFSofPath(absF).listStatus(absF);
}
/**
* Mark a path to be deleted on JVM shutdown.
*
* @param f the existing path to delete.
* @return true if deleteOnExit is successful, otherwise false.
* @throws IOException
*/
public boolean deleteOnExit(Path f) throws IOException {
if (!exists(f)) {
return false;
}
synchronized (deleteOnExit) {
if (deleteOnExit.isEmpty() && !finalizer.isAlive()) {
Runtime.getRuntime().addShutdownHook(finalizer);
}
Set<Path> set = deleteOnExit.get(this);
if (set == null) {
set = new TreeSet<Path>();
deleteOnExit.put(this, set);
}
set.add(f);
}
return true;
}
private final Util util;
public Util util() {
return util;
}
/**
* Utility/library methods built over the basic FileContext methods.
* Since this are library functions, the oprtation are not atomic
* and some of them may partially complete if other threads are making
* changes to the same part of the name space.
*/
public class Util {
/**
* Return a list of file status objects that corresponds to supplied paths
* excluding those non-existent paths.
*
* @param paths are the list of paths we want information from
* @return a list of FileStatus objects
* @throws IOException
*/
private FileStatus[] getFileStatus(Path[] paths) throws IOException {
if (paths == null) {
return null;
}
ArrayList<FileStatus> results = new ArrayList<FileStatus>(paths.length);
for (int i = 0; i < paths.length; i++) {
try {
results.add(FileContext.this.getFileStatus(paths[i]));
} catch (FileNotFoundException fnfe) {
// ignoring
}
}
return results.toArray(new FileStatus[results.size()]);
}
/**
* Filter files/directories in the given list of paths using default
* path filter.
*
* @param files is the list of paths
* @return a list of statuses for the files under the given paths after
* applying the filter default Path filter
* @exception IOException
*/
public FileStatus[] listStatus(Path[] files)
throws IOException {
return listStatus(files, DEFAULT_FILTER);
}
/**
* Filter files/directories in the given path using the user-supplied path
* filter.
*
* @param f is the path name
* @param filter is the user-supplied path filter
* @return an array of FileStatus objects for the files under the given path
* after applying the filter
* @throws IOException
* if encounter any problem while fetching the status
*/
public FileStatus[] listStatus(Path f, PathFilter filter)
throws IOException {
ArrayList<FileStatus> results = new ArrayList<FileStatus>();
listStatus(results, f, filter);
return results.toArray(new FileStatus[results.size()]);
}
/**
* Filter files/directories in the given list of paths using user-supplied
* path filter.
*
* @param files is a list of paths
* @param filter is the filter
* @return a list of statuses for the files under the given paths after
* applying the filter
* @exception IOException
*/
public FileStatus[] listStatus(Path[] files, PathFilter filter)
throws IOException {
ArrayList<FileStatus> results = new ArrayList<FileStatus>();
for (int i = 0; i < files.length; i++) {
listStatus(results, files[i], filter);
}
return results.toArray(new FileStatus[results.size()]);
}
/*
* Filter files/directories in the given path using the user-supplied path
* filter. Results are added to the given array <code>results</code>.
*/
private void listStatus(ArrayList<FileStatus> results, Path f,
PathFilter filter) throws IOException {
FileStatus[] listing = FileContext.this.listStatus(f);
if (listing != null) {
for (int i = 0; i < listing.length; i++) {
if (filter.accept(listing[i].getPath())) {
results.add(listing[i]);
}
}
}
}
/**
* <p>Return all the files that match filePattern and are not checksum
* files. Results are sorted by their names.
*
* <p>
* A filename pattern is composed of <i>regular</i> characters and
* <i>special pattern matching</i> characters, which are:
*
* <dl>
* <dd>
* <dl>
* <p>
* <dt> <tt> ? </tt>
* <dd> Matches any single character.
*
* <p>
* <dt> <tt> * </tt>
* <dd> Matches zero or more characters.
*
* <p>
* <dt> <tt> [<i>abc</i>] </tt>
* <dd> Matches a single character from character set
* <tt>{<i>a,b,c</i>}</tt>.
*
* <p>
* <dt> <tt> [<i>a</i>-<i>b</i>] </tt>
* <dd> Matches a single character from the character range
* <tt>{<i>a...b</i>}</tt>. Note: character <tt><i>a</i></tt> must be
* lexicographically less than or equal to character <tt><i>b</i></tt>.
*
* <p>
* <dt> <tt> [^<i>a</i>] </tt>
* <dd> Matches a single char that is not from character set or range
* <tt>{<i>a</i>}</tt>. Note that the <tt>^</tt> character must occur
* immediately to the right of the opening bracket.
*
* <p>
* <dt> <tt> \<i>c</i> </tt>
* <dd> Removes (escapes) any special meaning of character <i>c</i>.
*
* <p>
* <dt> <tt> {ab,cd} </tt>
* <dd> Matches a string from the string set <tt>{<i>ab, cd</i>} </tt>
*
* <p>
* <dt> <tt> {ab,c{de,fh}} </tt>
* <dd> Matches a string from string set <tt>{<i>ab, cde, cfh</i>}</tt>
*
* </dl>
* </dd>
* </dl>
*
* @param pathPattern a regular expression specifying a pth pattern
* @return an array of paths that match the path pattern
* @throws IOException
*/
public FileStatus[] globStatus(Path pathPattern) throws IOException {
return globStatus(pathPattern, DEFAULT_FILTER);
}
/**
* Return an array of FileStatus objects whose path names match pathPattern
* and is accepted by the user-supplied path filter. Results are sorted by
* their path names.
* Return null if pathPattern has no glob and the path does not exist.
* Return an empty array if pathPattern has a glob and no path matches it.
*
* @param pathPattern
* a regular expression specifying the path pattern
* @param filter
* a user-supplied path filter
* @return an array of FileStatus objects
* @throws IOException if any I/O error occurs when fetching file status
*/
public FileStatus[] globStatus(final Path pathPattern, final PathFilter filter)
throws IOException {
String filename = pathPattern.toUri().getPath();
List<String> filePatterns = GlobExpander.expand(filename);
if (filePatterns.size() == 1) {
Path p = fixRelativePart(pathPattern);
FileSystem fs = getFSofPath(p);
URI uri = fs.getUri();
return globStatusInternal(uri, p, filter);
} else {
List<FileStatus> results = new ArrayList<FileStatus>();
for (String filePattern : filePatterns) {
Path p = new Path(filePattern);
p = fixRelativePart(p);
FileSystem fs = getFSofPath(p);
URI uri = fs.getUri();
FileStatus[] files = globStatusInternal(uri, p, filter);
for (FileStatus file : files) {
results.add(file);
}
}
return results.toArray(new FileStatus[results.size()]);
}
}
private FileStatus[] globStatusInternal(
final URI uri, final Path inPathPattern, final PathFilter filter)
throws IOException {
Path[] parents = new Path[1];
int level = 0;
// comes in as full path, but just in case
final Path pathPattern = fixRelativePart(inPathPattern);
String filename = pathPattern.toUri().getPath();
// path has only zero component
if ("".equals(filename) || Path.SEPARATOR.equals(filename)) {
Path p = pathPattern.makeQualified(uri, null);
return getFileStatus(new Path[]{p});
}
// path has at least one component
String[] components = filename.split(Path.SEPARATOR);
// get the first component
if (pathPattern.isAbsolute()) {
parents[0] = new Path(Path.SEPARATOR);
level = 1;
} else {
parents[0] = new Path(Path.CUR_DIR);
}
// glob the paths that match the parent path, ie. [0, components.length-1]
boolean[] hasGlob = new boolean[]{false};
Path[] relParentPaths = globPathsLevel(parents, components, level, hasGlob);
FileStatus[] results;
if (relParentPaths == null || relParentPaths.length == 0) {
results = null;
} else {
// fix the pathes to be abs
Path[] parentPaths = new Path [relParentPaths.length];
for(int i=0; i<relParentPaths.length; i++) {
parentPaths[i] = relParentPaths[i].makeQualified(uri, null);
}
// Now work on the last component of the path
GlobFilter fp =
new GlobFilter(components[components.length - 1], filter);
if (fp.hasPattern()) { // last component has a pattern
// list parent directories and then glob the results
results = listStatus(parentPaths, fp);
hasGlob[0] = true;
} else { // last component does not have a pattern
// get all the path names
ArrayList<Path> filteredPaths =
new ArrayList<Path>(parentPaths.length);
for (int i = 0; i < parentPaths.length; i++) {
parentPaths[i] = new Path(parentPaths[i],
components[components.length - 1]);
if (fp.accept(parentPaths[i])) {
filteredPaths.add(parentPaths[i]);
}
}
// get all their statuses
results = getFileStatus(
filteredPaths.toArray(new Path[filteredPaths.size()]));
}
}
// Decide if the pathPattern contains a glob or not
if (results == null) {
if (hasGlob[0]) {
results = new FileStatus[0];
}
} else {
if (results.length == 0) {
if (!hasGlob[0]) {
results = null;
}
} else {
Arrays.sort(results);
}
}
return results;
}
/*
* For a path of N components, return a list of paths that match the
* components [<code>level</code>, <code>N-1</code>].
*/
private Path[] globPathsLevel(Path[] parents, String[] filePattern,
int level, boolean[] hasGlob) throws IOException {
if (level == filePattern.length - 1) {
return parents;
}
if (parents == null || parents.length == 0) {
return null;
}
GlobFilter fp = new GlobFilter(filePattern[level]);
if (fp.hasPattern()) {
parents = FileUtil.stat2Paths(listStatus(parents, fp));
hasGlob[0] = true;
} else {
for (int i = 0; i < parents.length; i++) {
parents[i] = new Path(parents[i], filePattern[level]);
}
}
return globPathsLevel(parents, filePattern, level + 1, hasGlob);
}
/**
* Copy file from src to dest.
* @param src
* @param dst
* @return true if copy is successful
* @throws IOException
*/
public boolean copy(final Path src, final Path dst) throws IOException {
return copy(src, dst, false, false);
}
/**
* Copy from src to dst, optionally deleting src and overwriting dst.
* @param src
* @param dst
* @param deleteSource - delete src if true
* @param overwrite overwrite dst if true; throw IOException if dst exists
* and overwrite is false.
* @return true if copy is successful
* @throws IOException
*/
public boolean copy(final Path src, final Path dst,
boolean deleteSource, boolean overwrite)
throws IOException {
checkNotSchemeWithRelative(src);
checkNotSchemeWithRelative(dst);
Path qSrc = makeQualified(src);
Path qDst = makeQualified(dst);
checkDest(qSrc.getName(), qDst, false);
if (isDirectory(qSrc)) {
checkDependencies(qSrc, qDst);
mkdir(qDst, FsPermission.getDefault(), true);
FileStatus[] contents = FileContext.this.listStatus(qSrc);
for (FileStatus content : contents) {
copy(content.getPath(), new Path(qDst, content.getPath()),
deleteSource, overwrite);
}
} else {
InputStream in=null;
OutputStream out = null;
try {
in = open(qSrc);
out = create(qDst, EnumSet.of(CreateFlag.OVERWRITE));
IOUtils.copyBytes(in, out, conf, true);
} catch (IOException e) {
IOUtils.closeStream(out);
IOUtils.closeStream(in);
throw e;
}
}
if (deleteSource) {
return delete(qSrc, true);
} else {
return true;
}
}
}
private static final PathFilter DEFAULT_FILTER = new PathFilter() {
public boolean accept(final Path file) {
return true;
}
};
/* A class that could decide if a string matches the glob or not */
private static class GlobFilter implements PathFilter {
private PathFilter userFilter = DEFAULT_FILTER;
private Pattern regex;
private boolean hasPattern = false;
/** Default pattern character: Escape any special meaning. */
private static final char PAT_ESCAPE = '\\';
/** Default pattern character: Any single character. */
private static final char PAT_ANY = '.';
/** Default pattern character: Character set close. */
private static final char PAT_SET_CLOSE = ']';
GlobFilter() {
}
GlobFilter(final String filePattern) throws IOException {
setRegex(filePattern);
}
GlobFilter(final String filePattern, final PathFilter filter)
throws IOException {
userFilter = filter;
setRegex(filePattern);
}
private boolean isJavaRegexSpecialChar(char pChar) {
return pChar == '.' || pChar == '$' || pChar == '(' || pChar == ')' ||
pChar == '|' || pChar == '+';
}
void setRegex(String filePattern) throws IOException {
int len;
int setOpen;
int curlyOpen;
boolean setRange;
StringBuilder fileRegex = new StringBuilder();
// Validate the pattern
len = filePattern.length();
if (len == 0) {
return;
}
setOpen = 0;
setRange = false;
curlyOpen = 0;
for (int i = 0; i < len; i++) {
char pCh;
// Examine a single pattern character
pCh = filePattern.charAt(i);
if (pCh == PAT_ESCAPE) {
fileRegex.append(pCh);
i++;
if (i >= len) {
error("An escaped character does not present", filePattern, i);
}
pCh = filePattern.charAt(i);
} else if (isJavaRegexSpecialChar(pCh)) {
fileRegex.append(PAT_ESCAPE);
} else if (pCh == '*') {
fileRegex.append(PAT_ANY);
hasPattern = true;
} else if (pCh == '?') {
pCh = PAT_ANY;
hasPattern = true;
} else if (pCh == '{') {
fileRegex.append('(');
pCh = '(';
curlyOpen++;
hasPattern = true;
} else if (pCh == ',' && curlyOpen > 0) {
fileRegex.append(")|");
pCh = '(';
} else if (pCh == '}' && curlyOpen > 0) {
// End of a group
curlyOpen--;
fileRegex.append(")");
pCh = ')';
} else if (pCh == '[' && setOpen == 0) {
setOpen++;
hasPattern = true;
} else if (pCh == '^' && setOpen > 0) {
} else if (pCh == '-' && setOpen > 0) {
// Character set range
setRange = true;
} else if (pCh == PAT_SET_CLOSE && setRange) {
// Incomplete character set range
error("Incomplete character set range", filePattern, i);
} else if (pCh == PAT_SET_CLOSE && setOpen > 0) {
// End of a character set
if (setOpen < 2) {
error("Unexpected end of set", filePattern, i);
}
setOpen = 0;
} else if (setOpen > 0) {
// Normal character, or the end of a character set range
setOpen++;
setRange = false;
}
fileRegex.append(pCh);
}
// Check for a well-formed pattern
if (setOpen > 0 || setRange || curlyOpen > 0) {
// Incomplete character set or character range
error("Expecting set closure character or end of range, or }",
filePattern, len);
}
regex = Pattern.compile(fileRegex.toString());
}
boolean hasPattern() {
return hasPattern;
}
public boolean accept(final Path path) {
return regex.matcher(path.getName()).matches() && userFilter.accept(path);
}
private void error(final String s, final String pattern, final int pos)
throws IOException {
throw new IOException("Illegal file pattern: "
+s+ " for glob "+ pattern + " at " + pos);
}
}
//
// Check destionation. Throw IOException if destination already exists
// and overwrite is not true
//
private void checkDest(String srcName, Path dst, boolean overwrite)
throws IOException {
if (exists(dst)) {
if (isDirectory(dst)) {
// TBD not very clear
if (null == srcName) {
throw new IOException("Target " + dst + " is a directory");
}
checkDest(null, new Path(dst, srcName), overwrite);
} else if (!overwrite) {
throw new IOException("Target " + dst + " already exists");
}
}
}
//
// If the destination is a subdirectory of the source, then
// generate exception
//
private static void checkDependencies(Path qualSrc, Path qualDst)
throws IOException {
if (isSameFS(qualSrc, qualDst)) {
String srcq = qualSrc.toString() + Path.SEPARATOR;
String dstq = qualDst.toString() + Path.SEPARATOR;
if (dstq.startsWith(srcq)) {
if (srcq.length() == dstq.length()) {
throw new IOException("Cannot copy " + qualSrc + " to itself.");
} else {
throw new IOException("Cannot copy " + qualSrc +
" to its subdirectory " + qualDst);
}
}
}
}
/**
* Are qualSrc and qualDst of the same file system?
* @param qualPath1 - fully qualified path
* @param qualPath2 - fully qualified path
* @return
*/
private static boolean isSameFS(Path qualPath1, Path qualPath2) {
URI srcUri = qualPath1.toUri();
URI dstUri = qualPath2.toUri();
return (srcUri.getAuthority().equals(dstUri.getAuthority()) && srcUri
.getAuthority().equals(dstUri.getAuthority()));
}
/**
* Deletes all the paths in deleteOnExit on JVM shutdown
*/
static class FileContextFinalizer extends Thread {
public synchronized void run() {
processDeleteOnExit();
}
}
}