implements
private DirectoryEntries entries;
private int i = 0;
- DirListingIterator(Path path) {
+ DirListingIterator(Path path) throws IOException {
this.path = path;
+ this.entries = listStatusBatch(path, null);
}
@Override
public boolean hasNext() throws IOException {
- if (entries == null) {
- fetchMore();
- }
return i < entries.getEntries().length ||
entries.hasMore();
}
private void fetchMore() throws IOException {
- byte[] token = null;
- if (entries != null) {
- token = entries.getToken();
- }
+ byte[] token = entries.getToken();
entries = listStatusBatch(path, token);
i = 0;
}
@@ -2135,7 +2299,9 @@ private void fetchMore() throws IOException {
@Override
@SuppressWarnings("unchecked")
public T next() throws IOException {
- Preconditions.checkState(hasNext(), "No more items in iterator");
+ if (!hasNext()) {
+ throw new NoSuchElementException("No more items in iterator");
+ }
if (i == entries.getEntries().length) {
fetchMore();
}
@@ -2235,10 +2401,19 @@ public LocatedFileStatus next() throws IOException {
/** Return the current user's home directory in this FileSystem.
* The default implementation returns {@code "/user/$USER/"}.
+ * @return the path.
*/
public Path getHomeDirectory() {
+ String username;
+ try {
+ username = UserGroupInformation.getCurrentUser().getShortUserName();
+ } catch(IOException ex) {
+ LOGGER.warn("Unable to get user name. Fall back to system property " +
+ "user.name", ex);
+ username = System.getProperty("user.name");
+ }
return this.makeQualified(
- new Path(USER_HOME_PREFIX + "/" + System.getProperty("user.name")));
+ new Path(USER_HOME_PREFIX + "/" + username));
}
@@ -2289,6 +2464,7 @@ public boolean mkdirs(Path f) throws IOException {
* @param f path to create
* @param permission to apply to f
* @throws IOException IO failure
+ * @return if mkdir success true, not false.
*/
public abstract boolean mkdirs(Path f, FsPermission permission
) throws IOException;
@@ -2336,6 +2512,7 @@ public void moveFromLocalFile(Path src, Path dst)
* @param delSrc whether to delete the src
* @param src path
* @param dst path
+ * @throws IOException IO failure.
*/
public void copyFromLocalFile(boolean delSrc, Path src, Path dst)
throws IOException {
@@ -2450,6 +2627,7 @@ public void copyToLocalFile(boolean delSrc, Path src, Path dst,
* @param fsOutputFile path of output file
* @param tmpLocalFile path of local tmp file
* @throws IOException IO failure
+ * @return the path.
*/
public Path startLocalOutput(Path fsOutputFile, Path tmpLocalFile)
throws IOException {
@@ -2483,14 +2661,21 @@ public void completeLocalOutput(Path fsOutputFile, Path tmpLocalFile)
*/
@Override
public void close() throws IOException {
+ debugLogFileSystemClose("close", "Key: " + key + "; URI: " + getUri()
+ + "; Object Identity Hash: "
+ + Integer.toHexString(System.identityHashCode(this)));
// delete all files that were marked as delete-on-exit.
- processDeleteOnExit();
- CACHE.remove(this.key, this);
+ try {
+ processDeleteOnExit();
+ } finally {
+ CACHE.remove(this.key, this);
+ }
}
/**
* Return the total size of all files in the filesystem.
* @throws IOException IO failure
+ * @return the number of path used.
*/
public long getUsed() throws IOException {
Path path = new Path("/");
@@ -2499,7 +2684,9 @@ public long getUsed() throws IOException {
/**
* Return the total size of all files from a specified path.
+ * @param path the path.
* @throws IOException IO failure
+ * @return the number of path content summary.
*/
public long getUsed(Path path) throws IOException {
return getContentSummary(path).getLength();
@@ -2522,6 +2709,7 @@ public long getBlockSize(Path f) throws IOException {
* Return the number of bytes that large input files should be optimally
* be split into to minimize I/O time.
* @deprecated use {@link #getDefaultBlockSize(Path)} instead
+ * @return default block size.
*/
@Deprecated
public long getDefaultBlockSize() {
@@ -2568,6 +2756,20 @@ public short getDefaultReplication(Path path) {
*/
public abstract FileStatus getFileStatus(Path f) throws IOException;
+ /**
+ * Synchronize client metadata state.
+ *
+ * In some FileSystem implementations such as HDFS metadata
+ * synchronization is essential to guarantee consistency of read requests
+ * particularly in HA setting.
+ * @throws IOException If an I/O error occurred.
+ * @throws UnsupportedOperationException if the operation is unsupported.
+ */
+ public void msync() throws IOException, UnsupportedOperationException {
+ throw new UnsupportedOperationException(getClass().getCanonicalName() +
+ " does not support method msync");
+ }
+
/**
* Checks if the user can access a path. The mode specifies which access
* checks to perform. If the requested permissions are granted, then the
@@ -2637,6 +2839,8 @@ static void checkAccessPermissions(FileStatus stat, FsAction mode)
/**
* See {@link FileContext#fixRelativePart}.
+ * @param p the path.
+ * @return relative part.
*/
protected Path fixRelativePart(Path p) {
if (p.isUriPathAbsolute()) {
@@ -2648,6 +2852,18 @@ protected Path fixRelativePart(Path p) {
/**
* See {@link FileContext#createSymlink(Path, Path, boolean)}.
+ *
+ * @param target target path.
+ * @param link link.
+ * @param createParent create parent.
+ * @throws AccessControlException if access is denied.
+ * @throws FileAlreadyExistsException when the path does not exist.
+ * @throws FileNotFoundException when the path does not exist.
+ * @throws ParentNotDirectoryException if the parent path of dest is not
+ * a directory.
+ * @throws UnsupportedFileSystemException if there was no known implementation
+ * for the scheme.
+ * @throws IOException raised on errors performing I/O.
*/
public void createSymlink(final Path target, final Path link,
final boolean createParent) throws AccessControlException,
@@ -2661,8 +2877,14 @@ public void createSymlink(final Path target, final Path link,
/**
* See {@link FileContext#getFileLinkStatus(Path)}.
- * @throws FileNotFoundException when the path does not exist
- * @throws IOException see specific implementation
+ *
+ * @param f the path.
+ * @throws AccessControlException if access is denied.
+ * @throws FileNotFoundException when the path does not exist.
+ * @throws IOException raised on errors performing I/O.
+ * @throws UnsupportedFileSystemException if there was no known implementation
+ * for the scheme.
+ * @return file status
*/
public FileStatus getFileLinkStatus(final Path f)
throws AccessControlException, FileNotFoundException,
@@ -2673,6 +2895,7 @@ public FileStatus getFileLinkStatus(final Path f)
/**
* See {@link AbstractFileSystem#supportsSymlinks()}.
+ * @return if support symlinkls true, not false.
*/
public boolean supportsSymlinks() {
return false;
@@ -2680,8 +2903,11 @@ public boolean supportsSymlinks() {
/**
* See {@link FileContext#getLinkTarget(Path)}.
+ * @param f the path.
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
+ * @throws IOException IO failure.
+ * @return the path.
*/
public Path getLinkTarget(Path f) throws IOException {
// Supporting filesystems should override this method
@@ -2691,8 +2917,11 @@ public Path getLinkTarget(Path f) throws IOException {
/**
* See {@link AbstractFileSystem#getLinkTarget(Path)}.
+ * @param f the path.
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
+ * @throws IOException IO failure.
+ * @return the path.
*/
protected Path resolveLink(Path f) throws IOException {
// Supporting filesystems should override this method
@@ -2872,7 +3101,7 @@ public void deleteSnapshot(Path path, String snapshotName)
* changes. (Modifications are merged into the current ACL.)
*
* @param path Path to modify
- * @param aclSpec List describing modifications
+ * @param aclSpec List<AclEntry> describing modifications
* @throws IOException if an ACL could not be modified
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
@@ -3065,7 +3294,7 @@ public Map getXAttrs(Path path, List names)
* Refer to the HDFS extended attributes user documentation for details.
*
* @param path Path to get extended attributes
- * @return List of the XAttr names of the file or directory
+ * @return List{@literal } of the XAttr names of the file or directory
* @throws IOException IO failure
* @throws UnsupportedOperationException if the operation is unsupported
* (default outcome).
@@ -3093,6 +3322,16 @@ public void removeXAttr(Path path, String name) throws IOException {
+ " doesn't support removeXAttr");
}
+ /**
+ * Set the source path to satisfy storage policy.
+ * @param path The source path referring to either a directory or a file.
+ * @throws IOException If an I/O error occurred.
+ */
+ public void satisfyStoragePolicy(final Path path) throws IOException {
+ throw new UnsupportedOperationException(
+ getClass().getSimpleName() + " doesn't support setStoragePolicy");
+ }
+
/**
* Set the storage policy for a given file or directory.
*
@@ -3199,6 +3438,25 @@ public Collection getTrashRoots(boolean allUsers) {
return ret;
}
+ /**
+ * The base FileSystem implementation generally has no knowledge
+ * of the capabilities of actual implementations.
+ * Unless it has a way to explicitly determine the capabilities,
+ * this method returns false.
+ * {@inheritDoc}
+ */
+ public boolean hasPathCapability(final Path path, final String capability)
+ throws IOException {
+ switch (validatePathCapabilityArgs(makeQualified(path), capability)) {
+ case CommonPathCapabilities.FS_SYMLINKS:
+ // delegate to the existing supportsSymlinks() call.
+ return supportsSymlinks() && areSymlinksEnabled();
+ default:
+ // the feature is not implemented.
+ return false;
+ }
+ }
+
// making it volatile to be able to do a double checked locking
private volatile static boolean FILE_SYSTEMS_LOADED = false;
@@ -3303,25 +3561,64 @@ public static Class extends FileSystem> getFileSystemClass(String scheme,
private static FileSystem createFileSystem(URI uri, Configuration conf)
throws IOException {
Tracer tracer = FsTracer.get(conf);
- try(TraceScope scope = tracer.newScope("FileSystem#createFileSystem")) {
+ try(TraceScope scope = tracer.newScope("FileSystem#createFileSystem");
+ DurationInfo ignored =
+ new DurationInfo(LOGGER, false, "Creating FS %s", uri)) {
scope.addKVAnnotation("scheme", uri.getScheme());
- Class> clazz = getFileSystemClass(uri.getScheme(), conf);
- FileSystem fs = (FileSystem)ReflectionUtils.newInstance(clazz, conf);
- fs.initialize(uri, conf);
+ Class extends FileSystem> clazz =
+ getFileSystemClass(uri.getScheme(), conf);
+ FileSystem fs = ReflectionUtils.newInstance(clazz, conf);
+ try {
+ fs.initialize(uri, conf);
+ } catch (IOException | RuntimeException e) {
+ // exception raised during initialization.
+ // log summary at warn and full stack at debug
+ LOGGER.warn("Failed to initialize fileystem {}: {}",
+ uri, e.toString());
+ LOGGER.debug("Failed to initialize fileystem", e);
+ // then (robustly) close the FS, so as to invoke any
+ // cleanup code.
+ IOUtils.cleanupWithLogger(LOGGER, fs);
+ throw e;
+ }
return fs;
}
}
/** Caching FileSystem objects. */
- static class Cache {
+ static final class Cache {
private final ClientFinalizer clientFinalizer = new ClientFinalizer();
private final Map map = new HashMap<>();
private final Set toAutoClose = new HashSet<>();
+ /** Semaphore used to serialize creation of new FS instances. */
+ private final Semaphore creatorPermits;
+
+ /**
+ * Counter of the number of discarded filesystem instances
+ * in this cache. Primarily for testing, but it could possibly
+ * be made visible as some kind of metric.
+ */
+ private final AtomicLong discardedInstances = new AtomicLong(0);
+
/** A variable that makes all objects in the cache unique. */
private static AtomicLong unique = new AtomicLong(1);
+ /**
+ * Instantiate. The configuration is used to read the
+ * count of permits issued for concurrent creation
+ * of filesystem instances.
+ * @param conf configuration
+ */
+ Cache(final Configuration conf) {
+ int permits = conf.getInt(FS_CREATION_PARALLEL_COUNT,
+ FS_CREATION_PARALLEL_COUNT_DEFAULT);
+ checkArgument(permits > 0, "Invalid value of %s: %s",
+ FS_CREATION_PARALLEL_COUNT, permits);
+ creatorPermits = new Semaphore(permits);
+ }
+
FileSystem get(URI uri, Configuration conf) throws IOException{
Key key = new Key(uri, conf);
return getInternal(uri, conf, key);
@@ -3344,7 +3641,7 @@ FileSystem getUnique(URI uri, Configuration conf) throws IOException{
* @param conf configuration
* @param key key to store/retrieve this FileSystem in the cache
* @return a cached or newly instantiated FileSystem.
- * @throws IOException
+ * @throws IOException If an I/O error occurred.
*/
private FileSystem getInternal(URI uri, Configuration conf, Key key)
throws IOException{
@@ -3355,28 +3652,82 @@ private FileSystem getInternal(URI uri, Configuration conf, Key key)
if (fs != null) {
return fs;
}
-
- fs = createFileSystem(uri, conf);
- synchronized (this) { // refetch the lock again
- FileSystem oldfs = map.get(key);
- if (oldfs != null) { // a file system is created while lock is releasing
- fs.close(); // close the new file system
- return oldfs; // return the old file system
- }
-
- // now insert the new file system into the map
- if (map.isEmpty()
- && !ShutdownHookManager.get().isShutdownInProgress()) {
- ShutdownHookManager.get().addShutdownHook(clientFinalizer, SHUTDOWN_HOOK_PRIORITY);
+ // fs not yet created, acquire lock
+ // to construct an instance.
+ try (DurationInfo d = new DurationInfo(LOGGER, false,
+ "Acquiring creator semaphore for %s", uri)) {
+ creatorPermits.acquireUninterruptibly();
+ }
+ FileSystem fsToClose = null;
+ try {
+ // See if FS was instantiated by another thread while waiting
+ // for the permit.
+ synchronized (this) {
+ fs = map.get(key);
}
- fs.key = key;
- map.put(key, fs);
- if (conf.getBoolean(
- FS_AUTOMATIC_CLOSE_KEY, FS_AUTOMATIC_CLOSE_DEFAULT)) {
- toAutoClose.add(key);
+ if (fs != null) {
+ LOGGER.debug("Filesystem {} created while awaiting semaphore", uri);
+ return fs;
}
- return fs;
+ // create the filesystem
+ fs = createFileSystem(uri, conf);
+ final long timeout = conf.getTimeDuration(SERVICE_SHUTDOWN_TIMEOUT,
+ SERVICE_SHUTDOWN_TIMEOUT_DEFAULT,
+ ShutdownHookManager.TIME_UNIT_DEFAULT);
+ // any FS to close outside of the synchronized section
+ synchronized (this) { // lock on the Cache object
+
+ // see if there is now an entry for the FS, which happens
+ // if another thread's creation overlapped with this one.
+ FileSystem oldfs = map.get(key);
+ if (oldfs != null) {
+ // a file system was created in a separate thread.
+ // save the FS reference to close outside all locks,
+ // and switch to returning the oldFS
+ fsToClose = fs;
+ fs = oldfs;
+ } else {
+ // register the clientFinalizer if needed and shutdown isn't
+ // already active
+ if (map.isEmpty()
+ && !ShutdownHookManager.get().isShutdownInProgress()) {
+ ShutdownHookManager.get().addShutdownHook(clientFinalizer,
+ SHUTDOWN_HOOK_PRIORITY, timeout,
+ ShutdownHookManager.TIME_UNIT_DEFAULT);
+ }
+ // insert the new file system into the map
+ fs.key = key;
+ map.put(key, fs);
+ if (conf.getBoolean(
+ FS_AUTOMATIC_CLOSE_KEY, FS_AUTOMATIC_CLOSE_DEFAULT)) {
+ toAutoClose.add(key);
+ }
+ }
+ } // end of synchronized block
+ } finally {
+ // release the creator permit.
+ creatorPermits.release();
}
+ if (fsToClose != null) {
+ LOGGER.debug("Duplicate FS created for {}; discarding {}",
+ uri, fs);
+ discardedInstances.incrementAndGet();
+ // close the new file system
+ // note this will briefly remove and reinstate "fsToClose" from
+ // the map. It is done in a synchronized block so will not be
+ // visible to others.
+ IOUtils.cleanupWithLogger(LOGGER, fsToClose);
+ }
+ return fs;
+ }
+
+ /**
+ * Get the count of discarded instances.
+ * @return the new instance.
+ */
+ @VisibleForTesting
+ long getDiscardedInstances() {
+ return discardedInstances.get();
}
synchronized void remove(Key key, FileSystem fs) {
@@ -3781,6 +4132,7 @@ public void run() {
/**
* Get or create the thread-local data associated with the current thread.
+ * @return statistics data.
*/
public StatisticsData getThreadStatistics() {
StatisticsData data = threadData.get();
@@ -4139,6 +4491,7 @@ public static synchronized Map getStatistics() {
/**
* Return the FileSystem classes that have Statistics.
* @deprecated use {@link #getGlobalStorageStatistics()}
+ * @return statistics lists.
*/
@Deprecated
public static synchronized List getAllStatistics() {
@@ -4147,6 +4500,7 @@ public static synchronized List getAllStatistics() {
/**
* Get the statistics for a particular file system.
+ * @param scheme scheme.
* @param cls the class to lookup
* @return a statistics object
* @deprecated use {@link #getGlobalStorageStatistics()}
@@ -4181,6 +4535,7 @@ public static synchronized void clearStatistics() {
/**
* Print all statistics for all file systems to {@code System.out}
+ * @throws IOException If an I/O error occurred.
*/
public static synchronized
void printStatistics() throws IOException {
@@ -4221,24 +4576,47 @@ public StorageStatistics getStorageStatistics() {
/**
* Get the global storage statistics.
+ * @return global storage statistics.
*/
public static GlobalStorageStatistics getGlobalStorageStatistics() {
return GlobalStorageStatistics.INSTANCE;
}
+ /**
+ * Create instance of the standard FSDataOutputStreamBuilder for the
+ * given filesystem and path.
+ * @param fileSystem owner
+ * @param path path to create
+ * @return a builder.
+ */
+ @InterfaceStability.Unstable
+ protected static FSDataOutputStreamBuilder createDataOutputStreamBuilder(
+ @Nonnull final FileSystem fileSystem,
+ @Nonnull final Path path) {
+ return new FileSystemDataOutputStreamBuilder(fileSystem, path);
+ }
+
+ /**
+ * Standard implementation of the FSDataOutputStreamBuilder; invokes
+ * create/createNonRecursive or Append depending upon the options.
+ */
private static final class FileSystemDataOutputStreamBuilder extends
FSDataOutputStreamBuilder {
/**
* Constructor.
+ * @param fileSystem owner
+ * @param p path to create
*/
- protected FileSystemDataOutputStreamBuilder(FileSystem fileSystem, Path p) {
+ private FileSystemDataOutputStreamBuilder(FileSystem fileSystem, Path p) {
super(fileSystem, p);
}
@Override
public FSDataOutputStream build() throws IOException {
+ rejectUnknownMandatoryKeys(Collections.emptySet(),
+ " for " + getPath());
if (getFlags().contains(CreateFlag.CREATE) ||
getFlags().contains(CreateFlag.OVERWRITE)) {
if (isRecursive()) {
@@ -4253,11 +4631,12 @@ public FSDataOutputStream build() throws IOException {
} else if (getFlags().contains(CreateFlag.APPEND)) {
return getFS().append(getPath(), getBufferSize(), getProgress());
}
- throw new IOException("Must specify either create, overwrite or append");
+ throw new PathIOException(getPath().toString(),
+ "Must specify either create, overwrite or append");
}
@Override
- protected FileSystemDataOutputStreamBuilder getThisBuilder() {
+ public FileSystemDataOutputStreamBuilder getThisBuilder() {
return this;
}
}
@@ -4273,7 +4652,7 @@ protected FileSystemDataOutputStreamBuilder getThisBuilder() {
* builder interface becomes stable.
*/
public FSDataOutputStreamBuilder createFile(Path path) {
- return new FileSystemDataOutputStreamBuilder(this, path)
+ return createDataOutputStreamBuilder(this, path)
.create().overwrite(true);
}
@@ -4283,6 +4662,236 @@ public FSDataOutputStreamBuilder createFile(Path path) {
* @return a {@link FSDataOutputStreamBuilder} to build file append request.
*/
public FSDataOutputStreamBuilder appendFile(Path path) {
- return new FileSystemDataOutputStreamBuilder(this, path).append();
+ return createDataOutputStreamBuilder(this, path).append();
+ }
+
+ /**
+ * Open a file for reading through a builder API.
+ * Ultimately calls {@link #open(Path, int)} unless a subclass
+ * executes the open command differently.
+ *
+ * The semantics of this call are therefore the same as that of
+ * {@link #open(Path, int)} with one special point: it is in
+ * {@code FSDataInputStreamBuilder.build()} in which the open operation
+ * takes place -it is there where all preconditions to the operation
+ * are checked.
+ * @param path file path
+ * @return a FSDataInputStreamBuilder object to build the input stream
+ * @throws IOException if some early checks cause IO failures.
+ * @throws UnsupportedOperationException if support is checked early.
+ */
+ @InterfaceStability.Unstable
+ public FutureDataInputStreamBuilder openFile(Path path)
+ throws IOException, UnsupportedOperationException {
+ return createDataInputStreamBuilder(this, path).getThisBuilder();
+ }
+
+ /**
+ * Open a file for reading through a builder API.
+ * Ultimately calls {@link #open(PathHandle, int)} unless a subclass
+ * executes the open command differently.
+ *
+ * If PathHandles are unsupported, this may fail in the
+ * {@code FSDataInputStreamBuilder.build()} command,
+ * rather than in this {@code openFile()} operation.
+ * @param pathHandle path handle.
+ * @return a FSDataInputStreamBuilder object to build the input stream
+ * @throws IOException if some early checks cause IO failures.
+ * @throws UnsupportedOperationException if support is checked early.
+ */
+ @InterfaceStability.Unstable
+ public FutureDataInputStreamBuilder openFile(PathHandle pathHandle)
+ throws IOException, UnsupportedOperationException {
+ return createDataInputStreamBuilder(this, pathHandle)
+ .getThisBuilder();
+ }
+
+ /**
+ * Execute the actual open file operation.
+ *
+ * This is invoked from {@code FSDataInputStreamBuilder.build()}
+ * and from {@link DelegateToFileSystem} and is where
+ * the action of opening the file should begin.
+ *
+ * The base implementation performs a blocking
+ * call to {@link #open(Path, int)} in this call;
+ * the actual outcome is in the returned {@code CompletableFuture}.
+ * This avoids having to create some thread pool, while still
+ * setting up the expectation that the {@code get()} call
+ * is needed to evaluate the result.
+ * @param path path to the file
+ * @param parameters open file parameters from the builder.
+ * @return a future which will evaluate to the opened file.
+ * @throws IOException failure to resolve the link.
+ * @throws IllegalArgumentException unknown mandatory key
+ */
+ protected CompletableFuture openFileWithOptions(
+ final Path path,
+ final OpenFileParameters parameters) throws IOException {
+ AbstractFSBuilderImpl.rejectUnknownMandatoryKeys(
+ parameters.getMandatoryKeys(),
+ Options.OpenFileOptions.FS_OPTION_OPENFILE_STANDARD_OPTIONS,
+ "for " + path);
+ return LambdaUtils.eval(
+ new CompletableFuture<>(), () ->
+ open(path, parameters.getBufferSize()));
+ }
+
+ /**
+ * Execute the actual open file operation.
+ * The base implementation performs a blocking
+ * call to {@link #open(Path, int)} in this call;
+ * the actual outcome is in the returned {@code CompletableFuture}.
+ * This avoids having to create some thread pool, while still
+ * setting up the expectation that the {@code get()} call
+ * is needed to evaluate the result.
+ * @param pathHandle path to the file
+ * @param parameters open file parameters from the builder.
+ * @return a future which will evaluate to the opened file.
+ * @throws IOException failure to resolve the link.
+ * @throws IllegalArgumentException unknown mandatory key
+ * @throws UnsupportedOperationException PathHandles are not supported.
+ * This may be deferred until the future is evaluated.
+ */
+ protected CompletableFuture openFileWithOptions(
+ final PathHandle pathHandle,
+ final OpenFileParameters parameters) throws IOException {
+ AbstractFSBuilderImpl.rejectUnknownMandatoryKeys(
+ parameters.getMandatoryKeys(),
+ Options.OpenFileOptions.FS_OPTION_OPENFILE_STANDARD_OPTIONS, "");
+ CompletableFuture result = new CompletableFuture<>();
+ try {
+ result.complete(open(pathHandle, parameters.getBufferSize()));
+ } catch (UnsupportedOperationException tx) {
+ // fail fast here
+ throw tx;
+ } catch (Throwable tx) {
+ // fail lazily here to ensure callers expect all File IO operations to
+ // surface later
+ result.completeExceptionally(tx);
+ }
+ return result;
+ }
+
+ /**
+ * Helper method that throws an {@link UnsupportedOperationException} for the
+ * current {@link FileSystem} method being called.
+ */
+ private void methodNotSupported() {
+ // The order of the stacktrace elements is (from top to bottom):
+ // - java.lang.Thread.getStackTrace
+ // - org.apache.hadoop.fs.FileSystem.methodNotSupported
+ // -
+ // therefore, to find out the current method name, we use the element at
+ // index 2.
+ String name = Thread.currentThread().getStackTrace()[2].getMethodName();
+ throw new UnsupportedOperationException(getClass().getCanonicalName() +
+ " does not support method " + name);
+ }
+
+ /**
+ * Create instance of the standard {@link FSDataInputStreamBuilder} for the
+ * given filesystem and path.
+ * @param fileSystem owner
+ * @param path path to read
+ * @return a builder.
+ */
+ @InterfaceAudience.LimitedPrivate("Filesystems")
+ @InterfaceStability.Unstable
+ protected static FSDataInputStreamBuilder createDataInputStreamBuilder(
+ @Nonnull final FileSystem fileSystem,
+ @Nonnull final Path path) {
+ return new FSDataInputStreamBuilder(fileSystem, path);
+ }
+
+ /**
+ * Create instance of the standard {@link FSDataInputStreamBuilder} for the
+ * given filesystem and path handle.
+ * @param fileSystem owner
+ * @param pathHandle path handle of file to open.
+ * @return a builder.
+ */
+ @InterfaceAudience.LimitedPrivate("Filesystems")
+ @InterfaceStability.Unstable
+ protected static FSDataInputStreamBuilder createDataInputStreamBuilder(
+ @Nonnull final FileSystem fileSystem,
+ @Nonnull final PathHandle pathHandle) {
+ return new FSDataInputStreamBuilder(fileSystem, pathHandle);
+ }
+
+ /**
+ * Builder returned for {@code #openFile(Path)}
+ * and {@code #openFile(PathHandle)}.
+ */
+ private static class FSDataInputStreamBuilder
+ extends FutureDataInputStreamBuilderImpl
+ implements FutureDataInputStreamBuilder {
+
+ /**
+ * Path Constructor.
+ * @param fileSystem owner
+ * @param path path to open.
+ */
+ protected FSDataInputStreamBuilder(
+ @Nonnull final FileSystem fileSystem,
+ @Nonnull final Path path) {
+ super(fileSystem, path);
+ }
+
+ /**
+ * Construct from a path handle.
+ * @param fileSystem owner
+ * @param pathHandle path handle of file to open.
+ */
+ protected FSDataInputStreamBuilder(
+ @Nonnull final FileSystem fileSystem,
+ @Nonnull final PathHandle pathHandle) {
+ super(fileSystem, pathHandle);
+ }
+
+ /**
+ * Perform the open operation.
+ * Returns a future which, when get() or a chained completion
+ * operation is invoked, will supply the input stream of the file
+ * referenced by the path/path handle.
+ * @return a future to the input stream.
+ * @throws IOException early failure to open
+ * @throws UnsupportedOperationException if the specific operation
+ * is not supported.
+ * @throws IllegalArgumentException if the parameters are not valid.
+ */
+ @Override
+ public CompletableFuture build() throws IOException {
+ Optional optionalPath = getOptionalPath();
+ OpenFileParameters parameters = new OpenFileParameters()
+ .withMandatoryKeys(getMandatoryKeys())
+ .withOptionalKeys(getOptionalKeys())
+ .withOptions(getOptions())
+ .withStatus(super.getStatus())
+ .withBufferSize(
+ getOptions().getInt(FS_OPTION_OPENFILE_BUFFER_SIZE, getBufferSize()));
+ if(optionalPath.isPresent()) {
+ return getFS().openFileWithOptions(optionalPath.get(),
+ parameters);
+ } else {
+ return getFS().openFileWithOptions(getPathHandle(),
+ parameters);
+ }
+ }
+
+ }
+
+ /**
+ * Create a multipart uploader.
+ * @param basePath file path under which all files are uploaded
+ * @return a MultipartUploaderBuilder object to build the uploader
+ * @throws IOException if some early checks cause IO failures.
+ * @throws UnsupportedOperationException if support is checked early.
+ */
+ @InterfaceStability.Unstable
+ public MultipartUploaderBuilder createMultipartUploader(Path basePath)
+ throws IOException {
+ methodNotSupported();
+ return null;
}
}
From 2927ca64cd1fa63b7bd8ac061945034ff9ba5424 Mon Sep 17 00:00:00 2001
From: Star Poon
Date: Wed, 31 Aug 2022 15:08:06 +0900
Subject: [PATCH 2/4] Inline LineReader from Hadoop 3.3.5
---
.../org/apache/hadoop/util/LineReader.java | 736 ++++++++----------
1 file changed, 344 insertions(+), 392 deletions(-)
diff --git a/src/main/java/org/apache/hadoop/util/LineReader.java b/src/main/java/org/apache/hadoop/util/LineReader.java
index 29f25f6..08bd810 100644
--- a/src/main/java/org/apache/hadoop/util/LineReader.java
+++ b/src/main/java/org/apache/hadoop/util/LineReader.java
@@ -6,9 +6,9 @@
* 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
- *
+ *
+ * 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.
@@ -18,15 +18,19 @@
package org.apache.hadoop.util;
-import io.trino.hadoop.TextLineLengthLimitExceededException;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.statistics.IOStatistics;
+import org.apache.hadoop.fs.statistics.IOStatisticsSource;
+import org.apache.hadoop.fs.statistics.IOStatisticsSupport;
import org.apache.hadoop.io.Text;
-import java.io.Closeable;
-import java.io.IOException;
-import java.io.InputStream;
+import static org.apache.hadoop.fs.CommonConfigurationKeysPublic.IO_FILE_BUFFER_SIZE_KEY;
/**
* A class that provides a line reader from an input stream.
@@ -41,414 +45,362 @@
*/
@InterfaceAudience.LimitedPrivate({"MapReduce"})
@InterfaceStability.Unstable
-public class LineReader
- implements Closeable
-{
- // Limitation for array size is VM specific. Current HotSpot VM limitation
- // for array size is Integer.MAX_VALUE - 5 (2^31 - 1 - 5).
- // Integer.MAX_VALUE - 8 should be safe enough.
- private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
- private static final int DEFAULT_BUFFER_SIZE = 64 * 1024;
- private static final byte CR = '\r';
- private static final byte LF = '\n';
- // The line delimiter
- private final byte[] recordDelimiterBytes;
- private int bufferSize = DEFAULT_BUFFER_SIZE;
- private InputStream in;
- private byte[] buffer;
- // the number of bytes of real data in the buffer
- private int bufferLength = 0;
- // the current position in the buffer
- private int bufferPosn = 0;
+public class LineReader implements Closeable, IOStatisticsSource {
+ private static final int DEFAULT_BUFFER_SIZE = 64 * 1024;
+ private int bufferSize = DEFAULT_BUFFER_SIZE;
+ private InputStream in;
+ private byte[] buffer;
+ // the number of bytes of real data in the buffer
+ private int bufferLength = 0;
+ // the current position in the buffer
+ private int bufferPosn = 0;
- /**
- * Create a line reader that reads from the given stream using the
- * default buffer-size (64k).
- * @param in The input stream
- * @throws IOException
- */
- public LineReader(InputStream in)
- {
- this(in, DEFAULT_BUFFER_SIZE);
- }
+ private static final byte CR = '\r';
+ private static final byte LF = '\n';
- /**
- * Create a line reader that reads from the given stream using the
- * given buffer-size.
- * @param in The input stream
- * @param bufferSize Size of the read buffer
- * @throws IOException
- */
- public LineReader(InputStream in, int bufferSize)
- {
- this.in = in;
- this.bufferSize = bufferSize;
- this.buffer = new byte[this.bufferSize];
- this.recordDelimiterBytes = null;
- }
+ // The line delimiter
+ private final byte[] recordDelimiterBytes;
- /**
- * Create a line reader that reads from the given stream using the
- * io.file.buffer.size specified in the given
- * Configuration.
- * @param in input stream
- * @param conf configuration
- * @throws IOException
- */
- public LineReader(InputStream in, Configuration conf)
- throws IOException
- {
- this(in, conf.getInt("io.file.buffer.size", DEFAULT_BUFFER_SIZE));
- }
+ /**
+ * Create a line reader that reads from the given stream using the
+ * default buffer-size (64k).
+ * @param in The input stream
+ */
+ public LineReader(InputStream in) {
+ this(in, DEFAULT_BUFFER_SIZE);
+ }
- /**
- * Create a line reader that reads from the given stream using the
- * default buffer-size, and using a custom delimiter of array of
- * bytes.
- * @param in The input stream
- * @param recordDelimiterBytes The delimiter
- */
- public LineReader(InputStream in, byte[] recordDelimiterBytes)
- {
- this.in = in;
- this.bufferSize = DEFAULT_BUFFER_SIZE;
- this.buffer = new byte[this.bufferSize];
- this.recordDelimiterBytes = recordDelimiterBytes;
- }
+ /**
+ * Create a line reader that reads from the given stream using the
+ * given buffer-size.
+ * @param in The input stream
+ * @param bufferSize Size of the read buffer
+ */
+ public LineReader(InputStream in, int bufferSize) {
+ this.in = in;
+ this.bufferSize = bufferSize;
+ this.buffer = new byte[this.bufferSize];
+ this.recordDelimiterBytes = null;
+ }
- /**
- * Create a line reader that reads from the given stream using the
- * given buffer-size, and using a custom delimiter of array of
- * bytes.
- * @param in The input stream
- * @param bufferSize Size of the read buffer
- * @param recordDelimiterBytes The delimiter
- * @throws IOException
- */
- public LineReader(InputStream in, int bufferSize,
- byte[] recordDelimiterBytes)
- {
- this.in = in;
- this.bufferSize = bufferSize;
- this.buffer = new byte[this.bufferSize];
- this.recordDelimiterBytes = recordDelimiterBytes;
- }
+ /**
+ * Create a line reader that reads from the given stream using the
+ * io.file.buffer.size specified in the given
+ * Configuration.
+ * @param in input stream
+ * @param conf configuration
+ * @throws IOException raised on errors performing I/O.
+ */
+ public LineReader(InputStream in, Configuration conf) throws IOException {
+ this(in, conf.getInt(IO_FILE_BUFFER_SIZE_KEY, DEFAULT_BUFFER_SIZE));
+ }
- /**
- * Create a line reader that reads from the given stream using the
- * io.file.buffer.size specified in the given
- * Configuration, and using a custom delimiter of array of
- * bytes.
- * @param in input stream
- * @param conf configuration
- * @param recordDelimiterBytes The delimiter
- * @throws IOException
- */
- public LineReader(InputStream in, Configuration conf,
- byte[] recordDelimiterBytes)
- throws IOException
- {
- this.in = in;
- this.bufferSize = conf.getInt("io.file.buffer.size", DEFAULT_BUFFER_SIZE);
- this.buffer = new byte[this.bufferSize];
- this.recordDelimiterBytes = recordDelimiterBytes;
- }
+ /**
+ * Create a line reader that reads from the given stream using the
+ * default buffer-size, and using a custom delimiter of array of
+ * bytes.
+ * @param in The input stream
+ * @param recordDelimiterBytes The delimiter
+ */
+ public LineReader(InputStream in, byte[] recordDelimiterBytes) {
+ this.in = in;
+ this.bufferSize = DEFAULT_BUFFER_SIZE;
+ this.buffer = new byte[this.bufferSize];
+ this.recordDelimiterBytes = recordDelimiterBytes;
+ }
- /**
- * Close the underlying stream.
- * @throws IOException
- */
- public void close()
- throws IOException
- {
- in.close();
- }
+ /**
+ * Create a line reader that reads from the given stream using the
+ * given buffer-size, and using a custom delimiter of array of
+ * bytes.
+ * @param in The input stream
+ * @param bufferSize Size of the read buffer
+ * @param recordDelimiterBytes The delimiter
+ */
+ public LineReader(InputStream in, int bufferSize,
+ byte[] recordDelimiterBytes) {
+ this.in = in;
+ this.bufferSize = bufferSize;
+ this.buffer = new byte[this.bufferSize];
+ this.recordDelimiterBytes = recordDelimiterBytes;
+ }
- /**
- * Read one line from the InputStream into the given Text.
- *
- * @param str the object to store the given line (without newline)
- * @param maxLineLength the maximum number of bytes to store into str;
- * the rest of the line is silently discarded.
- * @param maxBytesToConsume the maximum number of bytes to consume
- * in this call. This is only a hint, because if the line cross
- * this threshold, we allow it to happen. It can overshoot
- * potentially by as much as one buffer length.
- *
- * @return the number of bytes read including the (longest) newline
- * found.
- *
- * @throws IOException if the underlying stream throws
- */
- public int readLine(Text str, int maxLineLength,
- int maxBytesToConsume)
- throws IOException
- {
- maxLineLength = Math.min(maxLineLength, MAX_ARRAY_SIZE);
- maxBytesToConsume = Math.min(maxBytesToConsume, MAX_ARRAY_SIZE);
- if (this.recordDelimiterBytes != null) {
- return readCustomLine(str, maxLineLength, maxBytesToConsume);
- }
- else {
- return readDefaultLine(str, maxLineLength, maxBytesToConsume);
- }
- }
+ /**
+ * Create a line reader that reads from the given stream using the
+ * io.file.buffer.size specified in the given
+ * Configuration, and using a custom delimiter of array of
+ * bytes.
+ * @param in input stream
+ * @param conf configuration
+ * @param recordDelimiterBytes The delimiter
+ * @throws IOException raised on errors performing I/O.
+ */
+ public LineReader(InputStream in, Configuration conf,
+ byte[] recordDelimiterBytes) throws IOException {
+ this.in = in;
+ this.bufferSize = conf.getInt(IO_FILE_BUFFER_SIZE_KEY, DEFAULT_BUFFER_SIZE);
+ this.buffer = new byte[this.bufferSize];
+ this.recordDelimiterBytes = recordDelimiterBytes;
+ }
+
+
+ /**
+ * Close the underlying stream.
+ * @throws IOException raised on errors performing I/O.
+ */
+ public void close() throws IOException {
+ in.close();
+ }
- protected int fillBuffer(InputStream in, byte[] buffer, boolean inDelimiter)
- throws IOException
- {
- return in.read(buffer);
+ /**
+ * Return any IOStatistics provided by the source.
+ * @return IO stats from the input stream.
+ */
+ @Override
+ public IOStatistics getIOStatistics() {
+ return IOStatisticsSupport.retrieveIOStatistics(in);
+ }
+
+ /**
+ * Read one line from the InputStream into the given Text.
+ *
+ * @param str the object to store the given line (without newline)
+ * @param maxLineLength the maximum number of bytes to store into str;
+ * the rest of the line is silently discarded.
+ * @param maxBytesToConsume the maximum number of bytes to consume
+ * in this call. This is only a hint, because if the line cross
+ * this threshold, we allow it to happen. It can overshoot
+ * potentially by as much as one buffer length.
+ *
+ * @return the number of bytes read including the (longest) newline
+ * found.
+ *
+ * @throws IOException if the underlying stream throws
+ */
+ public int readLine(Text str, int maxLineLength,
+ int maxBytesToConsume) throws IOException {
+ if (this.recordDelimiterBytes != null) {
+ return readCustomLine(str, maxLineLength, maxBytesToConsume);
+ } else {
+ return readDefaultLine(str, maxLineLength, maxBytesToConsume);
}
+ }
+
+ protected int fillBuffer(InputStream in, byte[] buffer, boolean inDelimiter)
+ throws IOException {
+ return in.read(buffer);
+ }
- /**
- * Read a line terminated by one of CR, LF, or CRLF.
+ /**
+ * Read a line terminated by one of CR, LF, or CRLF.
+ */
+ private int readDefaultLine(Text str, int maxLineLength, int maxBytesToConsume)
+ throws IOException {
+ /* We're reading data from in, but the head of the stream may be
+ * already buffered in buffer, so we have several cases:
+ * 1. No newline characters are in the buffer, so we need to copy
+ * everything and read another buffer from the stream.
+ * 2. An unambiguously terminated line is in buffer, so we just
+ * copy to str.
+ * 3. Ambiguously terminated line is in buffer, i.e. buffer ends
+ * in CR. In this case we copy everything up to CR to str, but
+ * we also need to see what follows CR: if it's LF, then we
+ * need consume LF as well, so next call to readLine will read
+ * from after that.
+ * We use a flag prevCharCR to signal if previous character was CR
+ * and, if it happens to be at the end of the buffer, delay
+ * consuming it until we have a chance to look at the char that
+ * follows.
*/
- private int readDefaultLine(Text str, int maxLineLength, int maxBytesToConsume)
- throws IOException
- {
- /* We're reading data from in, but the head of the stream may be
- * already buffered in buffer, so we have several cases:
- * 1. No newline characters are in the buffer, so we need to copy
- * everything and read another buffer from the stream.
- * 2. An unambiguously terminated line is in buffer, so we just
- * copy to str.
- * 3. Ambiguously terminated line is in buffer, i.e. buffer ends
- * in CR. In this case we copy everything up to CR to str, but
- * we also need to see what follows CR: if it's LF, then we
- * need consume LF as well, so next call to readLine will read
- * from after that.
- * We use a flag prevCharCR to signal if previous character was CR
- * and, if it happens to be at the end of the buffer, delay
- * consuming it until we have a chance to look at the char that
- * follows.
- */
- str.clear();
- int txtLength = 0; //tracks str.getLength(), as an optimization
- int newlineLength = 0; //length of terminating newline
- boolean prevCharCR = false; //true of prev char was CR
- long bytesConsumed = 0;
- do {
- int startPosn = bufferPosn; //starting from where we left off the last time
- if (bufferPosn >= bufferLength) {
- startPosn = bufferPosn = 0;
- if (prevCharCR) {
- ++bytesConsumed; //account for CR from previous read
- }
- bufferLength = fillBuffer(in, buffer, prevCharCR);
- if (bufferLength <= 0) {
- break; // EOF
- }
- }
- for (; bufferPosn < bufferLength; ++bufferPosn) { //search for newline
- if (buffer[bufferPosn] == LF) {
- newlineLength = (prevCharCR) ? 2 : 1;
- ++bufferPosn; // at next invocation proceed from following byte
- break;
- }
- if (prevCharCR) { //CR + notLF, we are at notLF
- newlineLength = 1;
- break;
- }
- prevCharCR = (buffer[bufferPosn] == CR);
- }
- int readLength = bufferPosn - startPosn;
- if (prevCharCR && newlineLength == 0) {
- --readLength; //CR at the end of the buffer
- }
- bytesConsumed += readLength;
- int appendLength = readLength - newlineLength;
- if (appendLength > maxLineLength - txtLength) {
- appendLength = maxLineLength - txtLength;
- if (appendLength > 0) {
- // We want to fail the read when the line length is over the limit.
- throw new TextLineLengthLimitExceededException("Too many bytes before newline: " + maxLineLength);
- }
- }
- if (appendLength > 0) {
- int newTxtLength = txtLength + appendLength;
- if (str.getBytes().length < newTxtLength && Math.max(newTxtLength, txtLength << 1) > MAX_ARRAY_SIZE) {
- // If str need to be resized but the target capacity is over VM limit, it will trigger OOM.
- // In such case we will throw an IOException so the caller can deal with it.
- throw new TextLineLengthLimitExceededException("Too many bytes before newline: " + newTxtLength);
- }
- str.append(buffer, startPosn, appendLength);
- txtLength = newTxtLength;
- }
+ str.clear();
+ int txtLength = 0; //tracks str.getLength(), as an optimization
+ int newlineLength = 0; //length of terminating newline
+ boolean prevCharCR = false; //true of prev char was CR
+ long bytesConsumed = 0;
+ do {
+ int startPosn = bufferPosn; //starting from where we left off the last time
+ if (bufferPosn >= bufferLength) {
+ startPosn = bufferPosn = 0;
+ if (prevCharCR) {
+ ++bytesConsumed; //account for CR from previous read
}
- while (newlineLength == 0 && bytesConsumed < maxBytesToConsume);
-
- if (newlineLength == 0 && bytesConsumed >= maxBytesToConsume) {
- // It is possible that bytesConsumed is over the maxBytesToConsume but we
- // didn't append anything to str.bytes. If we have consumed over maxBytesToConsume
- // bytes but still haven't seen a line terminator, we will fail the read.
- throw new TextLineLengthLimitExceededException("Too many bytes before newline: " + bytesConsumed);
+ bufferLength = fillBuffer(in, buffer, prevCharCR);
+ if (bufferLength <= 0) {
+ break; // EOF
+ }
+ }
+ for (; bufferPosn < bufferLength; ++bufferPosn) { //search for newline
+ if (buffer[bufferPosn] == LF) {
+ newlineLength = (prevCharCR) ? 2 : 1;
+ ++bufferPosn; // at next invocation proceed from following byte
+ break;
+ }
+ if (prevCharCR) { //CR + notLF, we are at notLF
+ newlineLength = 1;
+ break;
}
- return (int) bytesConsumed;
+ prevCharCR = (buffer[bufferPosn] == CR);
+ }
+ int readLength = bufferPosn - startPosn;
+ if (prevCharCR && newlineLength == 0) {
+ --readLength; //CR at the end of the buffer
+ }
+ bytesConsumed += readLength;
+ int appendLength = readLength - newlineLength;
+ if (appendLength > maxLineLength - txtLength) {
+ appendLength = maxLineLength - txtLength;
+ }
+ if (appendLength > 0) {
+ str.append(buffer, startPosn, appendLength);
+ txtLength += appendLength;
+ }
+ } while (newlineLength == 0 && bytesConsumed < maxBytesToConsume);
+
+ if (bytesConsumed > Integer.MAX_VALUE) {
+ throw new IOException("Too many bytes before newline: " + bytesConsumed);
}
+ return (int)bytesConsumed;
+ }
- /**
- * Read a line terminated by a custom delimiter.
- */
- private int readCustomLine(Text str, int maxLineLength, int maxBytesToConsume)
- throws IOException
- {
- /* We're reading data from inputStream, but the head of the stream may be
- * already captured in the previous buffer, so we have several cases:
- *
- * 1. The buffer tail does not contain any character sequence which
- * matches with the head of delimiter. We count it as a
- * ambiguous byte count = 0
- *
- * 2. The buffer tail contains a X number of characters,
- * that forms a sequence, which matches with the
- * head of delimiter. We count ambiguous byte count = X
- *
- * // *** eg: A segment of input file is as follows
- *
- * " record 1792: I found this bug very interesting and
- * I have completely read about it. record 1793: This bug
- * can be solved easily record 1794: This ."
- *
- * delimiter = "record";
- *
- * supposing:- String at the end of buffer =
- * "I found this bug very interesting and I have completely re"
- * There for next buffer = "ad about it. record 179 ...."
- *
- * The matching characters in the input
- * buffer tail and delimiter head = "re"
- * Therefore, ambiguous byte count = 2 **** //
- *
- * 2.1 If the following bytes are the remaining characters of
- * the delimiter, then we have to capture only up to the starting
- * position of delimiter. That means, we need not include the
- * ambiguous characters in str.
- *
- * 2.2 If the following bytes are not the remaining characters of
- * the delimiter ( as mentioned in the example ),
- * then we have to include the ambiguous characters in str.
- */
- str.clear();
- int txtLength = 0; // tracks str.getLength(), as an optimization
- long bytesConsumed = 0;
- int delPosn = 0;
- int ambiguousByteCount = 0; // To capture the ambiguous characters count
- do {
- int startPosn = bufferPosn; // Start from previous end position
- if (bufferPosn >= bufferLength) {
- startPosn = bufferPosn = 0;
- bufferLength = fillBuffer(in, buffer, ambiguousByteCount > 0);
- if (bufferLength <= 0) {
- if (ambiguousByteCount > 0) {
- str.append(recordDelimiterBytes, 0, ambiguousByteCount);
- bytesConsumed += ambiguousByteCount;
- }
- break; // EOF
- }
- }
- for (; bufferPosn < bufferLength; ++bufferPosn) {
- if (buffer[bufferPosn] == recordDelimiterBytes[delPosn]) {
- delPosn++;
- if (delPosn >= recordDelimiterBytes.length) {
- bufferPosn++;
- break;
- }
- }
- else if (delPosn != 0) {
- bufferPosn -= delPosn;
- if (bufferPosn < -1) {
- bufferPosn = -1;
- }
- delPosn = 0;
- }
- }
- int readLength = bufferPosn - startPosn;
- bytesConsumed += readLength;
- int appendLength = readLength - delPosn;
- if (appendLength > maxLineLength - txtLength) {
- appendLength = maxLineLength - txtLength;
- if (appendLength > 0) {
- // We want to fail the read when the line length is over the limit.
- throw new TextLineLengthLimitExceededException("Too many bytes before delimiter: " + maxLineLength);
- }
- }
+ /**
+ * Read a line terminated by a custom delimiter.
+ */
+ private int readCustomLine(Text str, int maxLineLength, int maxBytesToConsume)
+ throws IOException {
+ /* We're reading data from inputStream, but the head of the stream may be
+ * already captured in the previous buffer, so we have several cases:
+ *
+ * 1. The buffer tail does not contain any character sequence which
+ * matches with the head of delimiter. We count it as a
+ * ambiguous byte count = 0
+ *
+ * 2. The buffer tail contains a X number of characters,
+ * that forms a sequence, which matches with the
+ * head of delimiter. We count ambiguous byte count = X
+ *
+ * // *** eg: A segment of input file is as follows
+ *
+ * " record 1792: I found this bug very interesting and
+ * I have completely read about it. record 1793: This bug
+ * can be solved easily record 1794: This ."
+ *
+ * delimiter = "record";
+ *
+ * supposing:- String at the end of buffer =
+ * "I found this bug very interesting and I have completely re"
+ * There for next buffer = "ad about it. record 179 ...."
+ *
+ * The matching characters in the input
+ * buffer tail and delimiter head = "re"
+ * Therefore, ambiguous byte count = 2 **** //
+ *
+ * 2.1 If the following bytes are the remaining characters of
+ * the delimiter, then we have to capture only up to the starting
+ * position of delimiter. That means, we need not include the
+ * ambiguous characters in str.
+ *
+ * 2.2 If the following bytes are not the remaining characters of
+ * the delimiter ( as mentioned in the example ),
+ * then we have to include the ambiguous characters in str.
+ */
+ str.clear();
+ int txtLength = 0; // tracks str.getLength(), as an optimization
+ long bytesConsumed = 0;
+ int delPosn = 0;
+ int ambiguousByteCount=0; // To capture the ambiguous characters count
+ do {
+ int startPosn = bufferPosn; // Start from previous end position
+ if (bufferPosn >= bufferLength) {
+ startPosn = bufferPosn = 0;
+ bufferLength = fillBuffer(in, buffer, ambiguousByteCount > 0);
+ if (bufferLength <= 0) {
+ if (ambiguousByteCount > 0) {
+ str.append(recordDelimiterBytes, 0, ambiguousByteCount);
bytesConsumed += ambiguousByteCount;
- if (appendLength >= 0 && ambiguousByteCount > 0) {
- //appending the ambiguous characters (refer case 2.2)
- str.append(recordDelimiterBytes, 0, ambiguousByteCount);
- ambiguousByteCount = 0;
- // since it is now certain that the split did not split a delimiter we
- // should not read the next record: clear the flag otherwise duplicate
- // records could be generated
- unsetNeedAdditionalRecordAfterSplit();
- }
- if (appendLength > 0) {
- int newTxtLength = txtLength + appendLength;
- if (str.getBytes().length < newTxtLength && Math.max(newTxtLength, txtLength << 1) > MAX_ARRAY_SIZE) {
- // If str need to be resized but the target capacity is over VM limit, it will trigger OOM.
- // In such case we will throw an IOException so the caller can deal with it.
- throw new TextLineLengthLimitExceededException("Too many bytes before delimiter: " + newTxtLength);
- }
- str.append(buffer, startPosn, appendLength);
- txtLength = newTxtLength;
- }
- if (bufferPosn >= bufferLength) {
- if (delPosn > 0 && delPosn < recordDelimiterBytes.length) {
- ambiguousByteCount = delPosn;
- bytesConsumed -= ambiguousByteCount; //to be consumed in next
- }
- }
+ }
+ break; // EOF
+ }
+ }
+ for (; bufferPosn < bufferLength; ++bufferPosn) {
+ if (buffer[bufferPosn] == recordDelimiterBytes[delPosn]) {
+ delPosn++;
+ if (delPosn >= recordDelimiterBytes.length) {
+ bufferPosn++;
+ break;
+ }
+ } else if (delPosn != 0) {
+ bufferPosn -= delPosn;
+ if(bufferPosn < -1) {
+ bufferPosn = -1;
+ }
+ delPosn = 0;
}
- while (delPosn < recordDelimiterBytes.length
- && bytesConsumed < maxBytesToConsume);
- if (delPosn < recordDelimiterBytes.length
- && bytesConsumed >= maxBytesToConsume) {
- // It is possible that bytesConsumed is over the maxBytesToConsume but we
- // didn't append anything to str.bytes. If we have consumed over maxBytesToConsume
- // bytes but still haven't seen a line terminator, we will fail the read.
- throw new TextLineLengthLimitExceededException("Too many bytes before delimiter: " + bytesConsumed);
+ }
+ int readLength = bufferPosn - startPosn;
+ bytesConsumed += readLength;
+ int appendLength = readLength - delPosn;
+ if (appendLength > maxLineLength - txtLength) {
+ appendLength = maxLineLength - txtLength;
+ }
+ bytesConsumed += ambiguousByteCount;
+ if (appendLength >= 0 && ambiguousByteCount > 0) {
+ //appending the ambiguous characters (refer case 2.2)
+ str.append(recordDelimiterBytes, 0, ambiguousByteCount);
+ ambiguousByteCount = 0;
+ // since it is now certain that the split did not split a delimiter we
+ // should not read the next record: clear the flag otherwise duplicate
+ // records could be generated
+ unsetNeedAdditionalRecordAfterSplit();
+ }
+ if (appendLength > 0) {
+ str.append(buffer, startPosn, appendLength);
+ txtLength += appendLength;
+ }
+ if (bufferPosn >= bufferLength) {
+ if (delPosn > 0 && delPosn < recordDelimiterBytes.length) {
+ ambiguousByteCount = delPosn;
+ bytesConsumed -= ambiguousByteCount; //to be consumed in next
}
- return (int) bytesConsumed;
+ }
+ } while (delPosn < recordDelimiterBytes.length
+ && bytesConsumed < maxBytesToConsume);
+ if (bytesConsumed > Integer.MAX_VALUE) {
+ throw new IOException("Too many bytes before delimiter: " + bytesConsumed);
}
+ return (int) bytesConsumed;
+ }
- /**
- * Read from the InputStream into the given Text.
- * @param str the object to store the given line
- * @param maxLineLength the maximum number of bytes to store into str.
- * @return the number of bytes read including the newline
- * @throws IOException if the underlying stream throws
- */
- public int readLine(Text str, int maxLineLength)
- throws IOException
- {
- return readLine(str, maxLineLength, Integer.MAX_VALUE);
- }
+ /**
+ * Read from the InputStream into the given Text.
+ * @param str the object to store the given line
+ * @param maxLineLength the maximum number of bytes to store into str.
+ * @return the number of bytes read including the newline
+ * @throws IOException if the underlying stream throws
+ */
+ public int readLine(Text str, int maxLineLength) throws IOException {
+ return readLine(str, maxLineLength, Integer.MAX_VALUE);
+ }
- /**
- * Read from the InputStream into the given Text.
- * @param str the object to store the given line
- * @return the number of bytes read including the newline
- * @throws IOException if the underlying stream throws
- */
- public int readLine(Text str)
- throws IOException
- {
- return readLine(str, Integer.MAX_VALUE, Integer.MAX_VALUE);
- }
+ /**
+ * Read from the InputStream into the given Text.
+ * @param str the object to store the given line
+ * @return the number of bytes read including the newline
+ * @throws IOException if the underlying stream throws
+ */
+ public int readLine(Text str) throws IOException {
+ return readLine(str, Integer.MAX_VALUE, Integer.MAX_VALUE);
+ }
- protected int getBufferPosn()
- {
- return bufferPosn;
- }
+ protected int getBufferPosn() {
+ return bufferPosn;
+ }
- protected int getBufferSize()
- {
- return bufferSize;
- }
+ protected int getBufferSize() {
+ return bufferSize;
+ }
- protected void unsetNeedAdditionalRecordAfterSplit()
- {
- // needed for custom multi byte line delimiters only
- // see MAPREDUCE-6549 for details
- }
+ protected void unsetNeedAdditionalRecordAfterSplit() {
+ // needed for custom multi byte line delimiters only
+ // see MAPREDUCE-6549 for details
+ }
}
From bcaedde45d0e2b96f1baa1b350b1ff5add0d58f2 Mon Sep 17 00:00:00 2001
From: Star Poon
Date: Wed, 31 Aug 2022 12:35:42 +0900
Subject: [PATCH 3/4] Update to Hadoop 3.3.5
---
pom.xml | 173 +--
.../java/io/trino/hadoop/HadoopNative.java | 1 -
.../io/trino/hadoop/SocksSocketFactory.java | 2 +-
.../java/org/apache/hadoop/fs/FileSystem.java | 17 +-
.../hadoop/fs/ForwardingFileSystemCache.java | 1 +
.../fs/azurebfs/AzureBlobFileSystemStore.java | 1039 -----------------
.../hadoop/security/LdapGroupsMapping.java | 749 ------------
.../authentication/util/KerberosUtil.java | 462 --------
.../org/apache/hadoop/util/LineReader.java | 46 +-
.../org/wildfly/openssl/OpenSSLProvider.java | 16 +-
.../nativelib/Linux-aarch64/libhadoop.so | Bin 902088 -> 742544 bytes
.../nativelib/Linux-aarch64/libsnappy.so | Bin 173424 -> 0 bytes
.../nativelib/Linux-amd64/libhadoop.so | Bin 719358 -> 803040 bytes
.../nativelib/Linux-amd64/libsnappy.so | Bin 112065 -> 0 bytes
.../nativelib/Linux-ppc64le/libsnappy.so | Bin 177416 -> 0 bytes
.../Mac_OS_X-aarch64/libsnappy.dylib | Bin 94336 -> 0 bytes
.../nativelib/Mac_OS_X-x86_64/libsnappy.dylib | Bin 34648 -> 0 bytes
.../io/trino/hadoop/TestHadoopNative.java | 1 -
18 files changed, 162 insertions(+), 2345 deletions(-)
delete mode 100644 src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
delete mode 100644 src/main/java/org/apache/hadoop/security/LdapGroupsMapping.java
delete mode 100644 src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java
delete mode 100755 src/main/resources/nativelib/Linux-aarch64/libsnappy.so
delete mode 100755 src/main/resources/nativelib/Linux-amd64/libsnappy.so
delete mode 100755 src/main/resources/nativelib/Linux-ppc64le/libsnappy.so
delete mode 100644 src/main/resources/nativelib/Mac_OS_X-aarch64/libsnappy.dylib
delete mode 100644 src/main/resources/nativelib/Mac_OS_X-x86_64/libsnappy.dylib
diff --git a/pom.xml b/pom.xml
index e70f8bc..9655ca0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -10,7 +10,7 @@
io.trino.hadoop
hadoop-apache
- 3.2.0-19-SNAPSHOT
+ 3.3.5-1-SNAPSHOT
hadoop-apache
Shaded version of Apache Hadoop for Trino
@@ -41,8 +41,8 @@
UTF-8
1.8
io.trino.hadoop.\$internal
- 1.7.25
- 3.2.0
+ 1.7.36
+ 3.3.5
@@ -87,10 +87,6 @@
org.apache.directory.server
apacheds-kerberos-codec
-
- org.apache.commons
- commons-compress
-
org.apache.commons
commons-math
@@ -135,6 +131,10 @@
com.sun.jersey
jersey-servlet
+
+ com.github.pjfanning
+ jersey-json
+
javax.servlet
javax.servlet-api
@@ -163,10 +163,6 @@
com.jcraft
jsch
-
- jdk.tools
- jdk.tools
-
dnsjava
dnsjava
@@ -183,14 +179,6 @@
com.thoughtworks.paranamer
paranamer
-
- org.xerial.snappy
- snappy-java
-
-
- com.google.code.findbugs
- jsr305
-
log4j
log4j
@@ -199,6 +187,14 @@
org.slf4j
slf4j-log4j12
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ ch.qos.reload4j
+ reload4j
+
@@ -260,6 +256,14 @@
io.netty
netty
+
+ org.slf4j
+ slf4j-reload4j
+
+
+ ch.qos.reload4j
+ reload4j
+
@@ -281,10 +285,6 @@
com.google.inject.extensions
guice-servlet
-
- com.fasterxml.jackson.core
- jackson-annotations
-
io.netty
netty
@@ -293,6 +293,10 @@
org.slf4j
slf4j-log4j12
+
+ org.slf4j
+ slf4j-reload4j
+
@@ -301,18 +305,10 @@
hadoop-azure
${dep.hadoop.version}
-
- org.apache.httpcomponents
- httpclient
-
org.wildfly.openssl
wildfly-openssl
-
- com.google.guava
- guava
-
@@ -320,6 +316,30 @@
org.apache.hadoop
hadoop-azure-datalake
${dep.hadoop.version}
+
+
+ org.wildfly.openssl
+ wildfly-openssl
+
+
+
+
+
+ com.google.guava
+ guava
+ 27.0.1-jre
+
+
+
+ org.xerial.snappy
+ snappy-java
+ 1.1.10.0
+
+
+
+ org.lz4
+ lz4-java
+ 1.8.0
@@ -455,14 +475,20 @@
true
${project.build.directory}/pom.xml
true
+
+
+ org.xerial.snappy:snappy-java
+ org.lz4:lz4-java
+ com.squareup.okhttp3:okhttp
+ com.squareup.okio:okio
+ org.jetbrains.kotlin:kotlin-stdlib
+ org.jetbrains.kotlin:kotlin-stdlib-common
+
+
-
- org.iq80.leveldb
- ${shadeBase}.org.iq80.leveldb
-
org.apache.http
${shadeBase}.org.apache.http
@@ -495,10 +521,6 @@
org.apache.kerby
${shadeBase}.org.apache.kerby
-
- org.apache.htrace
- ${shadeBase}.htrace
-
org.wildfly.openssl
${shadeBase}.org.wildfly.openssl
@@ -507,6 +529,19 @@
com.google.common
${shadeBase}.com.google.common
+
+
+ com.google.thirdparty
+ ${shadeBase}.com.google.thirdparty
+
+
+ com.google.j2objc
+ ${shadeBase}.com.google.j2objc
+
+
+ com.google.errorprone
+ ${shadeBase}.com.google.j2objc
+
com.google.protobuf
${shadeBase}.com.google.protobuf
@@ -535,6 +570,10 @@
org.codehaus.stax2
${shadeBase}.org.codehaus.stax2
+
+ org.apache.hadoop.thirdparty
+ ${shadeBase}.org.apache.hadoop.thirdparty
+
org.apache.log4j
${shadeBase}.org.apache.log4j
@@ -543,13 +582,23 @@
org.slf4j
${shadeBase}.org.slf4j
+
+
+ javax.annotation
+ ${shadeBase}.javax.annotation
+
+
- com.squareup.okhttp
- ${shadeBase}.com.squareup.okhttp
+ javax.activation
+ ${shadeBase}.javax.activation
- okio
- ${shadeBase}.okio
+ org.checkerframework
+ ${shadeBase}.org.checkerframework
+
+
+ org.codehaus.mojo
+ ${shadeBase}.org.codehaus.mojo
@@ -557,9 +606,11 @@
*:*
META-INF/maven/**
+ META-INF/versions/**
META-INF/*.SF
META-INF/*.DSA
META-INF/*.RSA
+ module-info.java
@@ -569,9 +620,15 @@
- io.netty:netty-all
+ io.netty:*
META-INF/io.netty.versions.properties
+ META-INF/native-image/**
+ META-INF/native/**
+ META-INF/services/**
+ com/sun/nio/sctp/**
+ *.c
+ *.h
@@ -586,6 +643,12 @@
META-INF/services/**
+
+ jakarta.activation:jakarta.activation-api
+
+ pom.xml
+
+
org.apache.commons:commons-configuration2
@@ -619,12 +682,6 @@
keytab.txt
-
- org.apache.htrace:htrace-core4
-
- META-INF/services/com.fasterxml.jackson.*
-
-
com.microsoft.azure:azure-data-lake-store-sdk
@@ -640,22 +697,6 @@
org/apache/hadoop/util/LineReader.class
org/apache/hadoop/crypto/key/kms/KMSClientProvider.class
org/apache/hadoop/crypto/key/kms/KMSClientProvider$*.class
- org/apache/hadoop/security/LdapGroupsMapping.class
- org/apache/hadoop/security/LdapGroupsMapping$*.class
-
-
-
- org.apache.hadoop:hadoop-auth
-
- org/apache/hadoop/security/authentication/util/KerberosUtil.class
- org/apache/hadoop/security/authentication/util/KerberosUtil$*.class
-
-
-
- org.apache.hadoop:hadoop-azure
-
- org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.class
- org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore$*.class
diff --git a/src/main/java/io/trino/hadoop/HadoopNative.java b/src/main/java/io/trino/hadoop/HadoopNative.java
index b7deeba..0f99570 100644
--- a/src/main/java/io/trino/hadoop/HadoopNative.java
+++ b/src/main/java/io/trino/hadoop/HadoopNative.java
@@ -45,7 +45,6 @@ public static synchronized void requireHadoopNative()
}
try {
loadLibrary("hadoop");
- loadLibrary("snappy");
loadLibrary("zstd");
setStatic(NativeCodeLoader.class.getDeclaredField("nativeCodeLoaded"), true);
diff --git a/src/main/java/io/trino/hadoop/SocksSocketFactory.java b/src/main/java/io/trino/hadoop/SocksSocketFactory.java
index 71c2624..32ef932 100644
--- a/src/main/java/io/trino/hadoop/SocksSocketFactory.java
+++ b/src/main/java/io/trino/hadoop/SocksSocketFactory.java
@@ -112,7 +112,7 @@ public void connect(SocketAddress endpoint, int timeout)
throws IOException
{
try {
- SocketAddress address = new InetSocketAddress(InetAddress.getByName(proxy.getHostText()), proxy.getPort());
+ SocketAddress address = new InetSocketAddress(InetAddress.getByName(proxy.getHost()), proxy.getPort());
socket.connect(address, timeout);
}
catch (IOException e) {
diff --git a/src/main/java/org/apache/hadoop/fs/FileSystem.java b/src/main/java/org/apache/hadoop/fs/FileSystem.java
index 57eb8f1..6c3d625 100644
--- a/src/main/java/org/apache/hadoop/fs/FileSystem.java
+++ b/src/main/java/org/apache/hadoop/fs/FileSystem.java
@@ -202,8 +202,8 @@ public abstract class FileSystem extends Configured
public static final String TRASH_PREFIX = ".Trash";
public static final String USER_HOME_PREFIX = "/user";
- /** FileSystem cache. */
- static final Cache CACHE = new Cache(new Configuration());
+ /** FileSystem cache. May be replaced by {@link FileSystemManager}. */
+ static volatile Cache CACHE = new Cache(new Configuration());
/** The key this instance is stored under in the cache. */
private Cache.Key key;
@@ -507,7 +507,7 @@ private static String fixName(String name) {
*/
public static LocalFileSystem getLocal(Configuration conf)
throws IOException {
- return (LocalFileSystem)get(LocalFileSystem.NAME, conf);
+ return ensureLocalFileSystem(get(LocalFileSystem.NAME, conf));
}
/**
@@ -630,7 +630,14 @@ public static FileSystem newInstance(Configuration conf) throws IOException {
*/
public static LocalFileSystem newInstanceLocal(Configuration conf)
throws IOException {
- return (LocalFileSystem)newInstance(LocalFileSystem.NAME, conf);
+ return ensureLocalFileSystem(newInstance(LocalFileSystem.NAME, conf));
+ }
+
+ private static LocalFileSystem ensureLocalFileSystem(FileSystem fileSystem) {
+ if (fileSystem instanceof LocalFileSystem) {
+ return (LocalFileSystem) fileSystem;
+ }
+ return new LocalFileSystemWrapper(fileSystem);
}
/**
@@ -3586,7 +3593,7 @@ private static FileSystem createFileSystem(URI uri, Configuration conf)
}
/** Caching FileSystem objects. */
- static final class Cache {
+ static class Cache {
private final ClientFinalizer clientFinalizer = new ClientFinalizer();
private final Map map = new HashMap<>();
diff --git a/src/main/java/org/apache/hadoop/fs/ForwardingFileSystemCache.java b/src/main/java/org/apache/hadoop/fs/ForwardingFileSystemCache.java
index 0cb5b50..6df1e57 100644
--- a/src/main/java/org/apache/hadoop/fs/ForwardingFileSystemCache.java
+++ b/src/main/java/org/apache/hadoop/fs/ForwardingFileSystemCache.java
@@ -28,6 +28,7 @@ final class ForwardingFileSystemCache
public ForwardingFileSystemCache(FileSystemCache cache)
{
+ super(new Configuration(false));
this.cache = requireNonNull(cache, "cache is null");
}
diff --git a/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java b/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
deleted file mode 100644
index cac427b..0000000
--- a/src/main/java/org/apache/hadoop/fs/azurebfs/AzureBlobFileSystemStore.java
+++ /dev/null
@@ -1,1039 +0,0 @@
-/**
- * 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.azurebfs;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.ByteBuffer;
-import java.nio.CharBuffer;
-import java.nio.charset.CharacterCodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.CharsetDecoder;
-import java.nio.charset.CharsetEncoder;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-
-import org.apache.hadoop.classification.InterfaceAudience;
-import org.apache.hadoop.classification.InterfaceStability;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.FileStatus;
-import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.Path;
-import org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants;
-import org.apache.hadoop.fs.azurebfs.constants.FileSystemUriSchemes;
-import org.apache.hadoop.fs.azurebfs.constants.HttpHeaderConfigurations;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.AbfsRestOperationException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.AzureBlobFileSystemException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.FileSystemOperationUnhandledException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidAbfsRestOperationException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidFileSystemPropertyException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidUriAuthorityException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidUriException;
-import org.apache.hadoop.fs.azurebfs.contracts.exceptions.TimeoutException;
-import org.apache.hadoop.fs.azurebfs.contracts.services.AzureServiceErrorCode;
-import org.apache.hadoop.fs.azurebfs.contracts.services.ListResultEntrySchema;
-import org.apache.hadoop.fs.azurebfs.contracts.services.ListResultSchema;
-import org.apache.hadoop.fs.azurebfs.oauth2.AccessTokenProvider;
-import org.apache.hadoop.fs.azurebfs.services.AbfsAclHelper;
-import org.apache.hadoop.fs.azurebfs.services.AbfsClient;
-import org.apache.hadoop.fs.azurebfs.services.AbfsHttpOperation;
-import org.apache.hadoop.fs.azurebfs.services.AbfsInputStream;
-import org.apache.hadoop.fs.azurebfs.services.AbfsOutputStream;
-import org.apache.hadoop.fs.azurebfs.services.AbfsPermission;
-import org.apache.hadoop.fs.azurebfs.services.AbfsRestOperation;
-import org.apache.hadoop.fs.azurebfs.services.AuthType;
-import org.apache.hadoop.fs.azurebfs.services.ExponentialRetryPolicy;
-import org.apache.hadoop.fs.azurebfs.services.SharedKeyCredentials;
-import org.apache.hadoop.fs.azurebfs.utils.Base64;
-import org.apache.hadoop.fs.azurebfs.utils.UriUtils;
-import org.apache.hadoop.fs.permission.AclEntry;
-import org.apache.hadoop.fs.permission.AclStatus;
-import org.apache.hadoop.fs.permission.FsAction;
-import org.apache.hadoop.fs.permission.FsPermission;
-import org.apache.hadoop.security.UserGroupInformation;
-import org.apache.http.client.utils.URIBuilder;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.AZURE_ABFS_ENDPOINT;
-import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME;
-import static org.apache.hadoop.util.Time.now;
-
-/**
- * Provides the bridging logic between Hadoop's abstract filesystem and Azure Storage.
- */
-@InterfaceAudience.Public
-@InterfaceStability.Evolving
-public class AzureBlobFileSystemStore {
- private static final Logger LOG = LoggerFactory.getLogger(AzureBlobFileSystemStore.class);
-
- private AbfsClient client;
- private URI uri;
- private final UserGroupInformation userGroupInformation;
- private final String userName;
- private final String primaryUserGroup;
- // See https://issues.apache.org/jira/browse/HADOOP-16479 for DATE_TIME_PATTERN modification
- // TODO Drop the change (and the file) after upgrading to hadoop 3.3.0
- private static final String DATE_TIME_PATTERN = "E, dd MMM yyyy HH:mm:ss z";
- private static final String XMS_PROPERTIES_ENCODING = "ISO-8859-1";
- private static final int LIST_MAX_RESULTS = 5000;
- private static final int DELETE_DIRECTORY_TIMEOUT_MILISECONDS = 180000;
- private static final int RENAME_TIMEOUT_MILISECONDS = 180000;
-
- private final AbfsConfiguration abfsConfiguration;
- private final Set azureAtomicRenameDirSet;
- private boolean isNamespaceEnabledSet;
- private boolean isNamespaceEnabled;
-
- public AzureBlobFileSystemStore(URI uri, boolean isSecureScheme, Configuration configuration, UserGroupInformation userGroupInformation)
- throws AzureBlobFileSystemException, IOException {
- this.uri = uri;
-
- String[] authorityParts = authorityParts(uri);
- final String fileSystemName = authorityParts[0];
- final String accountName = authorityParts[1];
-
- try {
- this.abfsConfiguration = new AbfsConfiguration(configuration, accountName);
- } catch (IllegalAccessException exception) {
- throw new FileSystemOperationUnhandledException(exception);
- }
-
- this.userGroupInformation = userGroupInformation;
- this.userName = userGroupInformation.getShortUserName();
-
- if (!abfsConfiguration.getSkipUserGroupMetadataDuringInitialization()) {
- primaryUserGroup = userGroupInformation.getPrimaryGroupName();
- } else {
- //Provide a default group name
- primaryUserGroup = userName;
- }
-
- this.azureAtomicRenameDirSet = new HashSet<>(Arrays.asList(
- abfsConfiguration.getAzureAtomicRenameDirs().split(AbfsHttpConstants.COMMA)));
-
- boolean usingOauth = (AuthType.OAuth == abfsConfiguration.getEnum(
- FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME, AuthType.SharedKey));
-
- boolean useHttps = (usingOauth || abfsConfiguration.isHttpsAlwaysUsed()) ? true : isSecureScheme;
- initializeClient(uri, fileSystemName, accountName, useHttps);
- }
-
- private String[] authorityParts(URI uri) throws InvalidUriAuthorityException, InvalidUriException {
- final String authority = uri.getRawAuthority();
- if (null == authority) {
- throw new InvalidUriAuthorityException(uri.toString());
- }
-
- if (!authority.contains(AbfsHttpConstants.AZURE_DISTRIBUTED_FILE_SYSTEM_AUTHORITY_DELIMITER)) {
- throw new InvalidUriAuthorityException(uri.toString());
- }
-
- final String[] authorityParts = authority.split(AbfsHttpConstants.AZURE_DISTRIBUTED_FILE_SYSTEM_AUTHORITY_DELIMITER, 2);
-
- if (authorityParts.length < 2 || authorityParts[0] != null
- && authorityParts[0].isEmpty()) {
- final String errMsg = String
- .format("'%s' has a malformed authority, expected container name. "
- + "Authority takes the form "
- + FileSystemUriSchemes.ABFS_SCHEME + "://[@]",
- uri.toString());
- throw new InvalidUriException(errMsg);
- }
- return authorityParts;
- }
-
- public boolean getIsNamespaceEnabled() throws AzureBlobFileSystemException {
- if (!isNamespaceEnabledSet) {
- LOG.debug("getFilesystemProperties for filesystem: {}",
- client.getFileSystem());
-
- final AbfsRestOperation op = client.getFilesystemProperties();
- isNamespaceEnabled = Boolean.parseBoolean(
- op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_NAMESPACE_ENABLED));
- isNamespaceEnabledSet = true;
- }
-
- return isNamespaceEnabled;
- }
-
- @VisibleForTesting
- URIBuilder getURIBuilder(final String hostName, boolean isSecure) {
- String scheme = isSecure ? FileSystemUriSchemes.HTTPS_SCHEME : FileSystemUriSchemes.HTTP_SCHEME;
-
- final URIBuilder uriBuilder = new URIBuilder();
- uriBuilder.setScheme(scheme);
-
- // For testing purposes, an IP address and port may be provided to override
- // the host specified in the FileSystem URI. Also note that the format of
- // the Azure Storage Service URI changes from
- // http[s]://[account][domain-suffix]/[filesystem] to
- // http[s]://[ip]:[port]/[account]/[filesystem].
- String endPoint = abfsConfiguration.get(AZURE_ABFS_ENDPOINT);
- if (endPoint == null || !endPoint.contains(AbfsHttpConstants.COLON)) {
- uriBuilder.setHost(hostName);
- return uriBuilder;
- }
-
- // Split ip and port
- String[] data = endPoint.split(AbfsHttpConstants.COLON);
- if (data.length != 2) {
- throw new RuntimeException(String.format("ABFS endpoint is not set correctly : %s, "
- + "Do not specify scheme when using {IP}:{PORT}", endPoint));
- }
- uriBuilder.setHost(data[0].trim());
- uriBuilder.setPort(Integer.parseInt(data[1].trim()));
- uriBuilder.setPath("/" + UriUtils.extractAccountNameFromHostName(hostName));
-
- return uriBuilder;
- }
-
- public AbfsConfiguration getAbfsConfiguration() {
- return this.abfsConfiguration;
- }
-
- public Hashtable getFilesystemProperties() throws AzureBlobFileSystemException {
- LOG.debug("getFilesystemProperties for filesystem: {}",
- client.getFileSystem());
-
- final Hashtable parsedXmsProperties;
-
- final AbfsRestOperation op = client.getFilesystemProperties();
- final String xMsProperties = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_PROPERTIES);
-
- parsedXmsProperties = parseCommaSeparatedXmsProperties(xMsProperties);
-
- return parsedXmsProperties;
- }
-
- public void setFilesystemProperties(final Hashtable properties)
- throws AzureBlobFileSystemException {
- if (properties == null || properties.isEmpty()) {
- return;
- }
-
- LOG.debug("setFilesystemProperties for filesystem: {} with properties: {}",
- client.getFileSystem(),
- properties);
-
- final String commaSeparatedProperties;
- try {
- commaSeparatedProperties = convertXmsPropertiesToCommaSeparatedString(properties);
- } catch (CharacterCodingException ex) {
- throw new InvalidAbfsRestOperationException(ex);
- }
-
- client.setFilesystemProperties(commaSeparatedProperties);
- }
-
- public Hashtable getPathProperties(final Path path) throws AzureBlobFileSystemException {
- LOG.debug("getPathProperties for filesystem: {} path: {}",
- client.getFileSystem(),
- path);
-
- final Hashtable parsedXmsProperties;
- final AbfsRestOperation op = client.getPathProperties(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path));
-
- final String xMsProperties = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_PROPERTIES);
-
- parsedXmsProperties = parseCommaSeparatedXmsProperties(xMsProperties);
-
- return parsedXmsProperties;
- }
-
- public void setPathProperties(final Path path, final Hashtable properties) throws AzureBlobFileSystemException {
- LOG.debug("setFilesystemProperties for filesystem: {} path: {} with properties: {}",
- client.getFileSystem(),
- path,
- properties);
-
- final String commaSeparatedProperties;
- try {
- commaSeparatedProperties = convertXmsPropertiesToCommaSeparatedString(properties);
- } catch (CharacterCodingException ex) {
- throw new InvalidAbfsRestOperationException(ex);
- }
- client.setPathProperties(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path), commaSeparatedProperties);
- }
-
- public void createFilesystem() throws AzureBlobFileSystemException {
- LOG.debug("createFilesystem for filesystem: {}",
- client.getFileSystem());
-
- client.createFilesystem();
- }
-
- public void deleteFilesystem() throws AzureBlobFileSystemException {
- LOG.debug("deleteFilesystem for filesystem: {}",
- client.getFileSystem());
-
- client.deleteFilesystem();
- }
-
- public OutputStream createFile(final Path path, final boolean overwrite, final FsPermission permission,
- final FsPermission umask) throws AzureBlobFileSystemException {
- boolean isNamespaceEnabled = getIsNamespaceEnabled();
- LOG.debug("createFile filesystem: {} path: {} overwrite: {} permission: {} umask: {} isNamespaceEnabled: {}",
- client.getFileSystem(),
- path,
- overwrite,
- permission.toString(),
- umask.toString(),
- isNamespaceEnabled);
-
- client.createPath(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path), true, overwrite,
- isNamespaceEnabled ? getOctalNotation(permission) : null,
- isNamespaceEnabled ? getOctalNotation(umask) : null);
-
- return new AbfsOutputStream(
- client,
- AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path),
- 0,
- abfsConfiguration.getWriteBufferSize(),
- abfsConfiguration.isFlushEnabled());
- }
-
- public void createDirectory(final Path path, final FsPermission permission, final FsPermission umask)
- throws AzureBlobFileSystemException {
- boolean isNamespaceEnabled = getIsNamespaceEnabled();
- LOG.debug("createDirectory filesystem: {} path: {} permission: {} umask: {} isNamespaceEnabled: {}",
- client.getFileSystem(),
- path,
- permission,
- umask,
- isNamespaceEnabled);
-
- client.createPath(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path), false, true,
- isNamespaceEnabled ? getOctalNotation(permission) : null,
- isNamespaceEnabled ? getOctalNotation(umask) : null);
- }
-
- public AbfsInputStream openFileForRead(final Path path, final FileSystem.Statistics statistics)
- throws AzureBlobFileSystemException {
- LOG.debug("openFileForRead filesystem: {} path: {}",
- client.getFileSystem(),
- path);
-
- final AbfsRestOperation op = client.getPathProperties(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path));
-
- final String resourceType = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_RESOURCE_TYPE);
- final long contentLength = Long.parseLong(op.getResult().getResponseHeader(HttpHeaderConfigurations.CONTENT_LENGTH));
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
-
- if (parseIsDirectory(resourceType)) {
- throw new AbfsRestOperationException(
- AzureServiceErrorCode.PATH_NOT_FOUND.getStatusCode(),
- AzureServiceErrorCode.PATH_NOT_FOUND.getErrorCode(),
- "openFileForRead must be used with files and not directories",
- null);
- }
-
- // Add statistics for InputStream
- return new AbfsInputStream(client, statistics,
- AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path), contentLength,
- abfsConfiguration.getReadBufferSize(), abfsConfiguration.getReadAheadQueueDepth(), eTag);
- }
-
- public OutputStream openFileForWrite(final Path path, final boolean overwrite) throws
- AzureBlobFileSystemException {
- LOG.debug("openFileForWrite filesystem: {} path: {} overwrite: {}",
- client.getFileSystem(),
- path,
- overwrite);
-
- final AbfsRestOperation op = client.getPathProperties(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path));
-
- final String resourceType = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_RESOURCE_TYPE);
- final Long contentLength = Long.valueOf(op.getResult().getResponseHeader(HttpHeaderConfigurations.CONTENT_LENGTH));
-
- if (parseIsDirectory(resourceType)) {
- throw new AbfsRestOperationException(
- AzureServiceErrorCode.PATH_NOT_FOUND.getStatusCode(),
- AzureServiceErrorCode.PATH_NOT_FOUND.getErrorCode(),
- "openFileForRead must be used with files and not directories",
- null);
- }
-
- final long offset = overwrite ? 0 : contentLength;
-
- return new AbfsOutputStream(
- client,
- AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path),
- offset,
- abfsConfiguration.getWriteBufferSize(),
- abfsConfiguration.isFlushEnabled());
- }
-
- public void rename(final Path source, final Path destination) throws
- AzureBlobFileSystemException {
-
- if (isAtomicRenameKey(source.getName())) {
- LOG.warn("The atomic rename feature is not supported by the ABFS scheme; however rename,"
- +" create and delete operations are atomic if Namespace is enabled for your Azure Storage account.");
- }
-
- LOG.debug("renameAsync filesystem: {} source: {} destination: {}",
- client.getFileSystem(),
- source,
- destination);
-
- String continuation = null;
- long deadline = now() + RENAME_TIMEOUT_MILISECONDS;
-
- do {
- if (now() > deadline) {
- LOG.debug("Rename {} to {} timed out.",
- source,
- destination);
-
- throw new TimeoutException("Rename timed out.");
- }
-
- AbfsRestOperation op = client.renamePath(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(source),
- AbfsHttpConstants.FORWARD_SLASH + getRelativePath(destination), continuation);
- continuation = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_CONTINUATION);
-
- } while (continuation != null && !continuation.isEmpty());
- }
-
- public void delete(final Path path, final boolean recursive)
- throws AzureBlobFileSystemException {
- LOG.debug("delete filesystem: {} path: {} recursive: {}",
- client.getFileSystem(),
- path,
- String.valueOf(recursive));
-
- String continuation = null;
- long deadline = now() + DELETE_DIRECTORY_TIMEOUT_MILISECONDS;
-
- do {
- if (now() > deadline) {
- LOG.debug("Delete directory {} timed out.", path);
-
- throw new TimeoutException("Delete directory timed out.");
- }
-
- AbfsRestOperation op = client.deletePath(
- AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path), recursive, continuation);
- continuation = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_CONTINUATION);
-
- } while (continuation != null && !continuation.isEmpty());
- }
-
- public FileStatus getFileStatus(final Path path) throws IOException {
- boolean isNamespaceEnabled = getIsNamespaceEnabled();
- LOG.debug("getFileStatus filesystem: {} path: {} isNamespaceEnabled: {}",
- client.getFileSystem(),
- path,
- isNamespaceEnabled);
-
- if (path.isRoot()) {
- final AbfsRestOperation op = isNamespaceEnabled
- ? client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + AbfsHttpConstants.ROOT_PATH)
- : client.getFilesystemProperties();
-
- final long blockSize = abfsConfiguration.getAzureBlockSize();
- final String owner = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_OWNER);
- final String group = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_GROUP);
- final String permissions = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_PERMISSIONS);
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
- final String lastModified = op.getResult().getResponseHeader(HttpHeaderConfigurations.LAST_MODIFIED);
- final boolean hasAcl = AbfsPermission.isExtendedAcl(permissions);
-
- return new VersionedFileStatus(
- owner == null ? userName : owner,
- group == null ? primaryUserGroup : group,
- permissions == null ? new AbfsPermission(FsAction.ALL, FsAction.ALL, FsAction.ALL)
- : AbfsPermission.valueOf(permissions),
- hasAcl,
- 0,
- true,
- 1,
- blockSize,
- parseLastModifiedTime(lastModified),
- path,
- eTag);
- } else {
- AbfsRestOperation op = client.getPathProperties(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path));
-
- final long blockSize = abfsConfiguration.getAzureBlockSize();
- final AbfsHttpOperation result = op.getResult();
- final String eTag = result.getResponseHeader(HttpHeaderConfigurations.ETAG);
- final String lastModified = result.getResponseHeader(HttpHeaderConfigurations.LAST_MODIFIED);
- final String contentLength = result.getResponseHeader(HttpHeaderConfigurations.CONTENT_LENGTH);
- final String resourceType = result.getResponseHeader(HttpHeaderConfigurations.X_MS_RESOURCE_TYPE);
- final String owner = result.getResponseHeader(HttpHeaderConfigurations.X_MS_OWNER);
- final String group = result.getResponseHeader(HttpHeaderConfigurations.X_MS_GROUP);
- final String permissions = result.getResponseHeader((HttpHeaderConfigurations.X_MS_PERMISSIONS));
- final boolean hasAcl = AbfsPermission.isExtendedAcl(permissions);
-
- return new VersionedFileStatus(
- owner == null ? userName : owner,
- group == null ? primaryUserGroup : group,
- permissions == null ? new AbfsPermission(FsAction.ALL, FsAction.ALL, FsAction.ALL)
- : AbfsPermission.valueOf(permissions),
- hasAcl,
- parseContentLength(contentLength),
- parseIsDirectory(resourceType),
- 1,
- blockSize,
- parseLastModifiedTime(lastModified),
- path,
- eTag);
- }
- }
-
- public FileStatus[] listStatus(final Path path) throws IOException {
- LOG.debug("listStatus filesystem: {} path: {}",
- client.getFileSystem(),
- path);
-
- String relativePath = path.isRoot() ? AbfsHttpConstants.EMPTY_STRING : getRelativePath(path);
- String continuation = null;
- ArrayList fileStatuses = new ArrayList<>();
-
- do {
- AbfsRestOperation op = client.listPath(relativePath, false, LIST_MAX_RESULTS, continuation);
- continuation = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_CONTINUATION);
- ListResultSchema retrievedSchema = op.getResult().getListResultSchema();
- if (retrievedSchema == null) {
- throw new AbfsRestOperationException(
- AzureServiceErrorCode.PATH_NOT_FOUND.getStatusCode(),
- AzureServiceErrorCode.PATH_NOT_FOUND.getErrorCode(),
- "listStatusAsync path not found",
- null, op.getResult());
- }
-
- long blockSize = abfsConfiguration.getAzureBlockSize();
-
- for (ListResultEntrySchema entry : retrievedSchema.paths()) {
- final String owner = entry.owner() == null ? userName : entry.owner();
- final String group = entry.group() == null ? primaryUserGroup : entry.group();
- final FsPermission fsPermission = entry.permissions() == null
- ? new AbfsPermission(FsAction.ALL, FsAction.ALL, FsAction.ALL)
- : AbfsPermission.valueOf(entry.permissions());
- final boolean hasAcl = AbfsPermission.isExtendedAcl(entry.permissions());
-
- long lastModifiedMillis = 0;
- long contentLength = entry.contentLength() == null ? 0 : entry.contentLength();
- boolean isDirectory = entry.isDirectory() == null ? false : entry.isDirectory();
- if (entry.lastModified() != null && !entry.lastModified().isEmpty()) {
- lastModifiedMillis = parseLastModifiedTime(entry.lastModified());
- }
-
- Path entryPath = new Path(File.separator + entry.name());
- entryPath = entryPath.makeQualified(this.uri, entryPath);
-
- fileStatuses.add(
- new VersionedFileStatus(
- owner,
- group,
- fsPermission,
- hasAcl,
- contentLength,
- isDirectory,
- 1,
- blockSize,
- lastModifiedMillis,
- entryPath,
- entry.eTag()));
- }
-
- } while (continuation != null && !continuation.isEmpty());
-
- return fileStatuses.toArray(new FileStatus[0]);
- }
-
- public void setOwner(final Path path, final String owner, final String group) throws
- AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "setOwner filesystem: {} path: {} owner: {} group: {}",
- client.getFileSystem(),
- path.toString(),
- owner,
- group);
- client.setOwner(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true), owner, group);
- }
-
- public void setPermission(final Path path, final FsPermission permission) throws
- AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "setPermission filesystem: {} path: {} permission: {}",
- client.getFileSystem(),
- path.toString(),
- permission.toString());
- client.setPermission(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true),
- String.format(AbfsHttpConstants.PERMISSION_FORMAT, permission.toOctal()));
- }
-
- public void modifyAclEntries(final Path path, final List aclSpec) throws
- AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "modifyAclEntries filesystem: {} path: {} aclSpec: {}",
- client.getFileSystem(),
- path.toString(),
- AclEntry.aclSpecToString(aclSpec));
-
- final Map modifyAclEntries = AbfsAclHelper.deserializeAclSpec(AclEntry.aclSpecToString(aclSpec));
-
- final AbfsRestOperation op = client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true));
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
-
- final Map aclEntries = AbfsAclHelper.deserializeAclSpec(op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_ACL));
-
- for (Map.Entry modifyAclEntry : modifyAclEntries.entrySet()) {
- aclEntries.put(modifyAclEntry.getKey(), modifyAclEntry.getValue());
- }
-
- if (!modifyAclEntries.containsKey(AbfsHttpConstants.ACCESS_MASK)) {
- aclEntries.remove(AbfsHttpConstants.ACCESS_MASK);
- }
-
- if (!modifyAclEntries.containsKey(AbfsHttpConstants.DEFAULT_MASK)) {
- aclEntries.remove(AbfsHttpConstants.DEFAULT_MASK);
- }
-
- client.setAcl(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true),
- AbfsAclHelper.serializeAclSpec(aclEntries), eTag);
- }
-
- public void removeAclEntries(final Path path, final List aclSpec) throws AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "removeAclEntries filesystem: {} path: {} aclSpec: {}",
- client.getFileSystem(),
- path.toString(),
- AclEntry.aclSpecToString(aclSpec));
-
- final Map removeAclEntries = AbfsAclHelper.deserializeAclSpec(AclEntry.aclSpecToString(aclSpec));
- final AbfsRestOperation op = client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true));
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
-
- final Map aclEntries = AbfsAclHelper.deserializeAclSpec(op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_ACL));
-
- AbfsAclHelper.removeAclEntriesInternal(aclEntries, removeAclEntries);
-
- client.setAcl(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true),
- AbfsAclHelper.serializeAclSpec(aclEntries), eTag);
- }
-
- public void removeDefaultAcl(final Path path) throws AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "removeDefaultAcl filesystem: {} path: {}",
- client.getFileSystem(),
- path.toString());
-
- final AbfsRestOperation op = client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true));
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
- final Map aclEntries = AbfsAclHelper.deserializeAclSpec(op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_ACL));
- final Map defaultAclEntries = new HashMap<>();
-
- for (Map.Entry aclEntry : aclEntries.entrySet()) {
- if (aclEntry.getKey().startsWith("default:")) {
- defaultAclEntries.put(aclEntry.getKey(), aclEntry.getValue());
- }
- }
-
- for (Map.Entry defaultAclEntry : defaultAclEntries.entrySet()) {
- aclEntries.remove(defaultAclEntry.getKey());
- }
-
- client.setAcl(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true),
- AbfsAclHelper.serializeAclSpec(aclEntries), eTag);
- }
-
- public void removeAcl(final Path path) throws AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "removeAcl filesystem: {} path: {}",
- client.getFileSystem(),
- path.toString());
- final AbfsRestOperation op = client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true));
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
-
- final Map aclEntries = AbfsAclHelper.deserializeAclSpec(op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_ACL));
- final Map newAclEntries = new HashMap<>();
-
- newAclEntries.put(AbfsHttpConstants.ACCESS_USER, aclEntries.get(AbfsHttpConstants.ACCESS_USER));
- newAclEntries.put(AbfsHttpConstants.ACCESS_GROUP, aclEntries.get(AbfsHttpConstants.ACCESS_GROUP));
- newAclEntries.put(AbfsHttpConstants.ACCESS_OTHER, aclEntries.get(AbfsHttpConstants.ACCESS_OTHER));
-
- client.setAcl(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true),
- AbfsAclHelper.serializeAclSpec(newAclEntries), eTag);
- }
-
- public void setAcl(final Path path, final List aclSpec) throws AzureBlobFileSystemException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "setAcl filesystem: {} path: {} aclspec: {}",
- client.getFileSystem(),
- path.toString(),
- AclEntry.aclSpecToString(aclSpec));
- final Map aclEntries = AbfsAclHelper.deserializeAclSpec(AclEntry.aclSpecToString(aclSpec));
- final AbfsRestOperation op = client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true));
- final String eTag = op.getResult().getResponseHeader(HttpHeaderConfigurations.ETAG);
-
- final Map getAclEntries = AbfsAclHelper.deserializeAclSpec(op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_ACL));
- for (Map.Entry ace : getAclEntries.entrySet()) {
- if (ace.getKey().startsWith("default:") && (ace.getKey() != AbfsHttpConstants.DEFAULT_MASK)
- && !aclEntries.containsKey(ace.getKey())) {
- aclEntries.put(ace.getKey(), ace.getValue());
- }
- }
-
- client.setAcl(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true),
- AbfsAclHelper.serializeAclSpec(aclEntries), eTag);
- }
-
- public AclStatus getAclStatus(final Path path) throws IOException {
- if (!getIsNamespaceEnabled()) {
- throw new UnsupportedOperationException(
- "This operation is only valid for storage accounts with the hierarchical namespace enabled.");
- }
-
- LOG.debug(
- "getAclStatus filesystem: {} path: {}",
- client.getFileSystem(),
- path.toString());
- AbfsRestOperation op = client.getAclStatus(AbfsHttpConstants.FORWARD_SLASH + getRelativePath(path, true));
- AbfsHttpOperation result = op.getResult();
-
- final String owner = result.getResponseHeader(HttpHeaderConfigurations.X_MS_OWNER);
- final String group = result.getResponseHeader(HttpHeaderConfigurations.X_MS_GROUP);
- final String permissions = result.getResponseHeader(HttpHeaderConfigurations.X_MS_PERMISSIONS);
- final String aclSpecString = op.getResult().getResponseHeader(HttpHeaderConfigurations.X_MS_ACL);
-
- final List processedAclEntries = AclEntry.parseAclSpec(AbfsAclHelper.processAclString(aclSpecString), true);
- final FsPermission fsPermission = permissions == null ? new AbfsPermission(FsAction.ALL, FsAction.ALL, FsAction.ALL)
- : AbfsPermission.valueOf(permissions);
-
- final AclStatus.Builder aclStatusBuilder = new AclStatus.Builder();
- aclStatusBuilder.owner(owner == null ? userName : owner);
- aclStatusBuilder.group(group == null ? primaryUserGroup : group);
-
- aclStatusBuilder.setPermission(fsPermission);
- aclStatusBuilder.stickyBit(fsPermission.getStickyBit());
- aclStatusBuilder.addEntries(processedAclEntries);
- return aclStatusBuilder.build();
- }
-
- public boolean isAtomicRenameKey(String key) {
- return isKeyForDirectorySet(key, azureAtomicRenameDirSet);
- }
-
- private void initializeClient(URI uri, String fileSystemName, String accountName, boolean isSecure) throws AzureBlobFileSystemException {
- if (this.client != null) {
- return;
- }
-
- final URIBuilder uriBuilder = getURIBuilder(accountName, isSecure);
-
- final String url = uriBuilder.toString() + AbfsHttpConstants.FORWARD_SLASH + fileSystemName;
-
- URL baseUrl;
- try {
- baseUrl = new URL(url);
- } catch (MalformedURLException e) {
- throw new InvalidUriException(uri.toString());
- }
-
- SharedKeyCredentials creds = null;
- AccessTokenProvider tokenProvider = null;
-
- if (abfsConfiguration.getAuthType(accountName) == AuthType.SharedKey) {
- int dotIndex = accountName.indexOf(AbfsHttpConstants.DOT);
- if (dotIndex <= 0) {
- throw new InvalidUriException(
- uri.toString() + " - account name is not fully qualified.");
- }
- creds = new SharedKeyCredentials(accountName.substring(0, dotIndex),
- abfsConfiguration.getStorageAccountKey());
- } else {
- tokenProvider = abfsConfiguration.getTokenProvider();
- }
-
- this.client = new AbfsClient(baseUrl, creds, abfsConfiguration, new ExponentialRetryPolicy(), tokenProvider);
- }
-
- private String getOctalNotation(FsPermission fsPermission) {
- Preconditions.checkNotNull(fsPermission, "fsPermission");
- return String.format(AbfsHttpConstants.PERMISSION_FORMAT, fsPermission.toOctal());
- }
-
- private String getRelativePath(final Path path) {
- return getRelativePath(path, false);
- }
-
- private String getRelativePath(final Path path, final boolean allowRootPath) {
- Preconditions.checkNotNull(path, "path");
- final String relativePath = path.toUri().getPath();
-
- if (relativePath.length() == 0 || (relativePath.length() == 1 && relativePath.charAt(0) == Path.SEPARATOR_CHAR)) {
- return allowRootPath ? AbfsHttpConstants.ROOT_PATH : AbfsHttpConstants.EMPTY_STRING;
- }
-
- if (relativePath.charAt(0) == Path.SEPARATOR_CHAR) {
- return relativePath.substring(1);
- }
-
- return relativePath;
- }
-
- private long parseContentLength(final String contentLength) {
- if (contentLength == null) {
- return -1;
- }
-
- return Long.parseLong(contentLength);
- }
-
- private boolean parseIsDirectory(final String resourceType) {
- return resourceType != null
- && resourceType.equalsIgnoreCase(AbfsHttpConstants.DIRECTORY);
- }
-
- private long parseLastModifiedTime(final String lastModifiedTime) {
- long parsedTime = 0;
- try {
- Date utcDate = new SimpleDateFormat(DATE_TIME_PATTERN).parse(lastModifiedTime);
- parsedTime = utcDate.getTime();
- } catch (ParseException e) {
- LOG.error("Failed to parse the date {}", lastModifiedTime);
- } finally {
- return parsedTime;
- }
- }
-
- private String convertXmsPropertiesToCommaSeparatedString(final Hashtable properties) throws
- CharacterCodingException {
- StringBuilder commaSeparatedProperties = new StringBuilder();
-
- final CharsetEncoder encoder = Charset.forName(XMS_PROPERTIES_ENCODING).newEncoder();
-
- for (Map.Entry propertyEntry : properties.entrySet()) {
- String key = propertyEntry.getKey();
- String value = propertyEntry.getValue();
-
- Boolean canEncodeValue = encoder.canEncode(value);
- if (!canEncodeValue) {
- throw new CharacterCodingException();
- }
-
- String encodedPropertyValue = Base64.encode(encoder.encode(CharBuffer.wrap(value)).array());
- commaSeparatedProperties.append(key)
- .append(AbfsHttpConstants.EQUAL)
- .append(encodedPropertyValue);
-
- commaSeparatedProperties.append(AbfsHttpConstants.COMMA);
- }
-
- if (commaSeparatedProperties.length() != 0) {
- commaSeparatedProperties.deleteCharAt(commaSeparatedProperties.length() - 1);
- }
-
- return commaSeparatedProperties.toString();
- }
-
- private Hashtable parseCommaSeparatedXmsProperties(String xMsProperties) throws
- InvalidFileSystemPropertyException, InvalidAbfsRestOperationException {
- Hashtable properties = new Hashtable<>();
-
- final CharsetDecoder decoder = Charset.forName(XMS_PROPERTIES_ENCODING).newDecoder();
-
- if (xMsProperties != null && !xMsProperties.isEmpty()) {
- String[] userProperties = xMsProperties.split(AbfsHttpConstants.COMMA);
-
- if (userProperties.length == 0) {
- return properties;
- }
-
- for (String property : userProperties) {
- if (property.isEmpty()) {
- throw new InvalidFileSystemPropertyException(xMsProperties);
- }
-
- String[] nameValue = property.split(AbfsHttpConstants.EQUAL, 2);
- if (nameValue.length != 2) {
- throw new InvalidFileSystemPropertyException(xMsProperties);
- }
-
- byte[] decodedValue = Base64.decode(nameValue[1]);
-
- final String value;
- try {
- value = decoder.decode(ByteBuffer.wrap(decodedValue)).toString();
- } catch (CharacterCodingException ex) {
- throw new InvalidAbfsRestOperationException(ex);
- }
- properties.put(nameValue[0], value);
- }
- }
-
- return properties;
- }
-
- private boolean isKeyForDirectorySet(String key, Set dirSet) {
- for (String dir : dirSet) {
- if (dir.isEmpty() || key.startsWith(dir + AbfsHttpConstants.FORWARD_SLASH)) {
- return true;
- }
-
- try {
- URI uri = new URI(dir);
- if (null == uri.getAuthority()) {
- if (key.startsWith(dir + "/")){
- return true;
- }
- }
- } catch (URISyntaxException e) {
- LOG.info("URI syntax error creating URI for {}", dir);
- }
- }
-
- return false;
- }
-
- private static class VersionedFileStatus extends FileStatus {
- private final String version;
-
- VersionedFileStatus(
- final String owner, final String group, final FsPermission fsPermission, final boolean hasAcl,
- final long length, final boolean isdir, final int blockReplication,
- final long blocksize, final long modificationTime, final Path path,
- String version) {
- super(length, isdir, blockReplication, blocksize, modificationTime, 0,
- fsPermission,
- owner,
- group,
- null,
- path,
- hasAcl, false, false);
-
- this.version = version;
- }
-
- /** Compare if this object is equal to another object.
- * @param obj the object to be compared.
- * @return true if two file status has the same path name; false if not.
- */
- @Override
- public boolean equals(Object obj) {
- if (!(obj instanceof FileStatus)) {
- return false;
- }
-
- FileStatus other = (FileStatus) obj;
-
- if (!this.getPath().equals(other.getPath())) {
- return false;
- }
-
- if (other instanceof VersionedFileStatus) {
- return this.version.equals(((VersionedFileStatus) other).version);
- }
-
- return true;
- }
-
- /**
- * Returns a hash code value for the object, which is defined as
- * the hash code of the path name.
- *
- * @return a hash code value for the path name and version
- */
- @Override
- public int hashCode() {
- int hash = getPath().hashCode();
- hash = 89 * hash + (this.version != null ? this.version.hashCode() : 0);
- return hash;
- }
-
- /**
- * Returns the version of this FileStatus
- *
- * @return a string value for the FileStatus version
- */
- public String getVersion() {
- return this.version;
- }
-
- @Override
- public String toString() {
- final StringBuilder sb = new StringBuilder(
- "VersionedFileStatus{");
- sb.append(super.toString());
- sb.append("; version='").append(version).append('\'');
- sb.append('}');
- return sb.toString();
- }
- }
-
- @VisibleForTesting
- AbfsClient getClient() {
- return this.client;
- }
-}
diff --git a/src/main/java/org/apache/hadoop/security/LdapGroupsMapping.java b/src/main/java/org/apache/hadoop/security/LdapGroupsMapping.java
deleted file mode 100644
index 4c7370f..0000000
--- a/src/main/java/org/apache/hadoop/security/LdapGroupsMapping.java
+++ /dev/null
@@ -1,749 +0,0 @@
-/**
- * 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.security;
-
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.Reader;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Hashtable;
-import java.util.List;
-import java.util.HashSet;
-import java.util.Collection;
-import java.util.Set;
-
-import javax.naming.Context;
-import javax.naming.NamingEnumeration;
-import javax.naming.NamingException;
-import javax.naming.directory.Attribute;
-import javax.naming.directory.DirContext;
-import javax.naming.directory.InitialDirContext;
-import javax.naming.directory.SearchControls;
-import javax.naming.directory.SearchResult;
-import javax.naming.ldap.LdapName;
-import javax.naming.ldap.Rdn;
-
-import org.apache.hadoop.classification.InterfaceAudience;
-import org.apache.hadoop.classification.InterfaceStability;
-import org.apache.hadoop.conf.Configurable;
-import org.apache.hadoop.conf.Configuration;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * An implementation of {@link GroupMappingServiceProvider} which
- * connects directly to an LDAP server for determining group membership.
- *
- * This provider should be used only if it is necessary to map users to
- * groups that reside exclusively in an Active Directory or LDAP installation.
- * The common case for a Hadoop installation will be that LDAP users and groups
- * materialized on the Unix servers, and for an installation like that,
- * ShellBasedUnixGroupsMapping is preferred. However, in cases where
- * those users and groups aren't materialized in Unix, but need to be used for
- * access control, this class may be used to communicate directly with the LDAP
- * server.
- *
- * It is important to note that resolving group mappings will incur network
- * traffic, and may cause degraded performance, although user-group mappings
- * will be cached via the infrastructure provided by {@link Groups}.
- *
- * This implementation does not support configurable search limits. If a filter
- * is used for searching users or groups which returns more results than are
- * allowed by the server, an exception will be thrown.
- *
- * The implementation attempts to resolve group hierarchies,
- * to a configurable limit.
- * If the limit is 0, in order to be considered a member of a group,
- * the user must be an explicit member in LDAP. Otherwise, it will traverse the
- * group hierarchy n levels up.
- */
-@InterfaceAudience.LimitedPrivate({"HDFS", "MapReduce"})
-@InterfaceStability.Evolving
-public class LdapGroupsMapping
- implements GroupMappingServiceProvider, Configurable {
-
- public static final String LDAP_CONFIG_PREFIX = "hadoop.security.group.mapping.ldap";
-
- /*
- * URL of the LDAP server
- */
- public static final String LDAP_URL_KEY = LDAP_CONFIG_PREFIX + ".url";
- public static final String LDAP_URL_DEFAULT = "";
-
- /*
- * Should SSL be used to connect to the server
- */
- public static final String LDAP_USE_SSL_KEY = LDAP_CONFIG_PREFIX + ".ssl";
- public static final Boolean LDAP_USE_SSL_DEFAULT = false;
-
- /*
- * File path to the location of the SSL keystore to use
- */
- public static final String LDAP_KEYSTORE_KEY = LDAP_CONFIG_PREFIX + ".ssl.keystore";
- public static final String LDAP_KEYSTORE_DEFAULT = "";
-
- /*
- * Password for the keystore
- */
- public static final String LDAP_KEYSTORE_PASSWORD_KEY = LDAP_CONFIG_PREFIX + ".ssl.keystore.password";
- public static final String LDAP_KEYSTORE_PASSWORD_DEFAULT = "";
-
- public static final String LDAP_KEYSTORE_PASSWORD_FILE_KEY = LDAP_KEYSTORE_PASSWORD_KEY + ".file";
- public static final String LDAP_KEYSTORE_PASSWORD_FILE_DEFAULT = "";
-
-
- /**
- * File path to the location of the SSL truststore to use
- */
- public static final String LDAP_TRUSTSTORE_KEY = LDAP_CONFIG_PREFIX +
- ".ssl.truststore";
-
- /**
- * The key of the credential entry containing the password for
- * the LDAP SSL truststore
- */
- public static final String LDAP_TRUSTSTORE_PASSWORD_KEY =
- LDAP_CONFIG_PREFIX +".ssl.truststore.password";
-
- /**
- * The path to a file containing the password for
- * the LDAP SSL truststore
- */
- public static final String LDAP_TRUSTSTORE_PASSWORD_FILE_KEY =
- LDAP_TRUSTSTORE_PASSWORD_KEY + ".file";
-
- /*
- * User to bind to the LDAP server with
- */
- public static final String BIND_USER_KEY = LDAP_CONFIG_PREFIX + ".bind.user";
- public static final String BIND_USER_DEFAULT = "";
-
- /*
- * Password for the bind user
- */
- public static final String BIND_PASSWORD_KEY = LDAP_CONFIG_PREFIX + ".bind.password";
- public static final String BIND_PASSWORD_DEFAULT = "";
-
- public static final String BIND_PASSWORD_FILE_KEY = BIND_PASSWORD_KEY + ".file";
- public static final String BIND_PASSWORD_FILE_DEFAULT = "";
-
- /*
- * Base distinguished name to use for searches
- */
- public static final String BASE_DN_KEY = LDAP_CONFIG_PREFIX + ".base";
- public static final String BASE_DN_DEFAULT = "";
-
- /*
- * Base DN used in user search.
- */
- public static final String USER_BASE_DN_KEY =
- LDAP_CONFIG_PREFIX + ".userbase";
-
- /*
- * Base DN used in group search.
- */
- public static final String GROUP_BASE_DN_KEY =
- LDAP_CONFIG_PREFIX + ".groupbase";
-
-
- /*
- * Any additional filters to apply when searching for users
- */
- public static final String USER_SEARCH_FILTER_KEY = LDAP_CONFIG_PREFIX + ".search.filter.user";
- public static final String USER_SEARCH_FILTER_DEFAULT = "(&(objectClass=user)(sAMAccountName={0}))";
-
- /*
- * Any additional filters to apply when finding relevant groups
- */
- public static final String GROUP_SEARCH_FILTER_KEY = LDAP_CONFIG_PREFIX + ".search.filter.group";
- public static final String GROUP_SEARCH_FILTER_DEFAULT = "(objectClass=group)";
-
- /*
- * LDAP attribute to use for determining group membership
- */
- public static final String MEMBEROF_ATTR_KEY =
- LDAP_CONFIG_PREFIX + ".search.attr.memberof";
- public static final String MEMBEROF_ATTR_DEFAULT = "";
-
- /*
- * LDAP attribute to use for determining group membership
- */
- public static final String GROUP_MEMBERSHIP_ATTR_KEY = LDAP_CONFIG_PREFIX + ".search.attr.member";
- public static final String GROUP_MEMBERSHIP_ATTR_DEFAULT = "member";
-
- /*
- * LDAP attribute to use for identifying a group's name
- */
- public static final String GROUP_NAME_ATTR_KEY = LDAP_CONFIG_PREFIX + ".search.attr.group.name";
- public static final String GROUP_NAME_ATTR_DEFAULT = "cn";
-
- /*
- * How many levels to traverse when checking for groups in the org hierarchy
- */
- public static final String GROUP_HIERARCHY_LEVELS_KEY =
- LDAP_CONFIG_PREFIX + ".search.group.hierarchy.levels";
- public static final int GROUP_HIERARCHY_LEVELS_DEFAULT = 0;
-
- /*
- * LDAP attribute names to use when doing posix-like lookups
- */
- public static final String POSIX_UID_ATTR_KEY = LDAP_CONFIG_PREFIX + ".posix.attr.uid.name";
- public static final String POSIX_UID_ATTR_DEFAULT = "uidNumber";
-
- public static final String POSIX_GID_ATTR_KEY = LDAP_CONFIG_PREFIX + ".posix.attr.gid.name";
- public static final String POSIX_GID_ATTR_DEFAULT = "gidNumber";
-
- /*
- * Posix attributes
- */
- public static final String POSIX_GROUP = "posixGroup";
- public static final String POSIX_ACCOUNT = "posixAccount";
-
- /*
- * LDAP {@link SearchControls} attribute to set the time limit
- * for an invoked directory search. Prevents infinite wait cases.
- */
- public static final String DIRECTORY_SEARCH_TIMEOUT =
- LDAP_CONFIG_PREFIX + ".directory.search.timeout";
- public static final int DIRECTORY_SEARCH_TIMEOUT_DEFAULT = 10000; // 10s
-
- public static final String CONNECTION_TIMEOUT =
- LDAP_CONFIG_PREFIX + ".connection.timeout.ms";
- public static final int CONNECTION_TIMEOUT_DEFAULT = 60 * 1000; // 60 seconds
- public static final String READ_TIMEOUT =
- LDAP_CONFIG_PREFIX + ".read.timeout.ms";
- public static final int READ_TIMEOUT_DEFAULT = 60 * 1000; // 60 seconds
-
- private static final Logger LOG =
- LoggerFactory.getLogger(LdapGroupsMapping.class);
-
- static final SearchControls SEARCH_CONTROLS = new SearchControls();
- static {
- SEARCH_CONTROLS.setSearchScope(SearchControls.SUBTREE_SCOPE);
- }
-
- private DirContext ctx;
- private Configuration conf;
-
- private String ldapUrl;
- private boolean useSsl;
- private String keystore;
- private String keystorePass;
- private String truststore;
- private String truststorePass;
- private String bindUser;
- private String bindPassword;
- private String userbaseDN;
- private String groupbaseDN;
- private String groupSearchFilter;
- private String userSearchFilter;
- private String memberOfAttr;
- private String groupMemberAttr;
- private String groupNameAttr;
- private int groupHierarchyLevels;
- private String posixUidAttr;
- private String posixGidAttr;
- private boolean isPosix;
- private boolean useOneQuery;
-
- public static final int RECONNECT_RETRY_COUNT = 3;
-
- /**
- * Returns list of groups for a user.
- *
- * The LdapCtx which underlies the DirContext object is not thread-safe, so
- * we need to block around this whole method. The caching infrastructure will
- * ensure that performance stays in an acceptable range.
- *
- * @param user get groups for this user
- * @return list of groups for a given user
- */
- @Override
- public synchronized List getGroups(String user) {
- /*
- * Normal garbage collection takes care of removing Context instances when they are no longer in use.
- * Connections used by Context instances being garbage collected will be closed automatically.
- * So in case connection is closed and gets CommunicationException, retry some times with new new DirContext/connection.
- */
- for(int retry = 0; retry < RECONNECT_RETRY_COUNT; retry++) {
- try {
- return doGetGroups(user, groupHierarchyLevels);
- } catch (NamingException e) {
- LOG.warn("Failed to get groups for user " + user + " (retry=" + retry
- + ") by " + e);
- LOG.trace("TRACE", e);
- }
-
- //reset ctx so that new DirContext can be created with new connection
- this.ctx = null;
- }
-
- return Collections.emptyList();
- }
-
- /**
- * A helper method to get the Relative Distinguished Name (RDN) from
- * Distinguished name (DN). According to Active Directory documentation,
- * a group object's RDN is a CN.
- *
- * @param distinguishedName A string representing a distinguished name.
- * @throws NamingException if the DN is malformed.
- * @return a string which represents the RDN
- */
- private String getRelativeDistinguishedName(String distinguishedName)
- throws NamingException {
- LdapName ldn = new LdapName(distinguishedName);
- List rdns = ldn.getRdns();
- if (rdns.isEmpty()) {
- throw new NamingException("DN is empty");
- }
- Rdn rdn = rdns.get(rdns.size()-1);
- if (rdn.getType().equalsIgnoreCase(groupNameAttr)) {
- String groupName = (String)rdn.getValue();
- return groupName;
- }
- throw new NamingException("Unable to find RDN: The DN " +
- distinguishedName + " is malformed.");
- }
-
- /**
- * Look up groups using posixGroups semantics. Use posix gid/uid to find
- * groups of the user.
- *
- * @param result the result object returned from the prior user lookup.
- * @param c the context object of the LDAP connection.
- * @return an object representing the search result.
- *
- * @throws NamingException if the server does not support posixGroups
- * semantics.
- */
- private NamingEnumeration lookupPosixGroup(SearchResult result,
- DirContext c) throws NamingException {
- String gidNumber = null;
- String uidNumber = null;
- Attribute gidAttribute = result.getAttributes().get(posixGidAttr);
- Attribute uidAttribute = result.getAttributes().get(posixUidAttr);
- String reason = "";
- if (gidAttribute == null) {
- reason = "Can't find attribute '" + posixGidAttr + "'.";
- } else {
- gidNumber = gidAttribute.get().toString();
- }
- if (uidAttribute == null) {
- reason = "Can't find attribute '" + posixUidAttr + "'.";
- } else {
- uidNumber = uidAttribute.get().toString();
- }
- if (uidNumber != null && gidNumber != null) {
- return c.search(groupbaseDN,
- "(&"+ groupSearchFilter + "(|(" + posixGidAttr + "={0})" +
- "(" + groupMemberAttr + "={1})))",
- new Object[] {gidNumber, uidNumber},
- SEARCH_CONTROLS);
- }
- throw new NamingException("The server does not support posixGroups " +
- "semantics. Reason: " + reason +
- " Returned user object: " + result.toString());
- }
-
- /**
- * Perform the second query to get the groups of the user.
- *
- * If posixGroups is enabled, use use posix gid/uid to find.
- * Otherwise, use the general group member attribute to find it.
- *
- * @param result the result object returned from the prior user lookup.
- * @param c the context object of the LDAP connection.
- * @return a list of strings representing group names of the user.
- * @throws NamingException if unable to find group names
- */
- private List lookupGroup(SearchResult result, DirContext c,
- int goUpHierarchy)
- throws NamingException {
- List groups = new ArrayList();
- Set groupDNs = new HashSet();
-
- NamingEnumeration groupResults = null;
- // perform the second LDAP query
- if (isPosix) {
- groupResults = lookupPosixGroup(result, c);
- } else {
- String userDn = result.getNameInNamespace();
- groupResults =
- c.search(groupbaseDN,
- "(&" + groupSearchFilter + "(" + groupMemberAttr + "={0}))",
- new Object[]{userDn},
- SEARCH_CONTROLS);
- }
- // if the second query is successful, group objects of the user will be
- // returned. Get group names from the returned objects.
- if (groupResults != null) {
- while (groupResults.hasMoreElements()) {
- SearchResult groupResult = groupResults.nextElement();
- getGroupNames(groupResult, groups, groupDNs, goUpHierarchy > 0);
- }
- if (goUpHierarchy > 0 && !isPosix) {
- // convert groups to a set to ensure uniqueness
- Set groupset = new HashSet(groups);
- goUpGroupHierarchy(groupDNs, goUpHierarchy, groupset);
- // convert set back to list for compatibility
- groups = new ArrayList(groupset);
- }
- }
- return groups;
- }
-
- /**
- * Perform LDAP queries to get group names of a user.
- *
- * Perform the first LDAP query to get the user object using the user's name.
- * If one-query is enabled, retrieve the group names from the user object.
- * If one-query is disabled, or if it failed, perform the second query to
- * get the groups.
- *
- * @param user user name
- * @return a list of group names for the user. If the user can not be found,
- * return an empty string array.
- * @throws NamingException if unable to get group names
- */
- List doGetGroups(String user, int goUpHierarchy)
- throws NamingException {
- DirContext c = getDirContext();
-
- // Search for the user. We'll only ever need to look at the first result
- NamingEnumeration results = c.search(userbaseDN,
- userSearchFilter, new Object[]{user}, SEARCH_CONTROLS);
- // return empty list if the user can not be found.
- if (!results.hasMoreElements()) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("doGetGroups(" + user + ") returned no groups because the " +
- "user is not found.");
- }
- return new ArrayList();
- }
- SearchResult result = results.nextElement();
-
- List groups = null;
- if (useOneQuery) {
- try {
- /**
- * For Active Directory servers, the user object has an attribute
- * 'memberOf' that represents the DNs of group objects to which the
- * user belongs. So the second query may be skipped.
- */
- Attribute groupDNAttr = result.getAttributes().get(memberOfAttr);
- if (groupDNAttr == null) {
- throw new NamingException("The user object does not have '" +
- memberOfAttr + "' attribute." +
- "Returned user object: " + result.toString());
- }
- groups = new ArrayList();
- NamingEnumeration groupEnumeration = groupDNAttr.getAll();
- while (groupEnumeration.hasMore()) {
- String groupDN = groupEnumeration.next().toString();
- groups.add(getRelativeDistinguishedName(groupDN));
- }
- } catch (NamingException e) {
- // If the first lookup failed, fall back to the typical scenario.
- LOG.info("Failed to get groups from the first lookup. Initiating " +
- "the second LDAP query using the user's DN.", e);
- }
- }
- if (groups == null || groups.isEmpty() || goUpHierarchy > 0) {
- groups = lookupGroup(result, c, goUpHierarchy);
- }
- if (LOG.isDebugEnabled()) {
- LOG.debug("doGetGroups(" + user + ") returned " + groups);
- }
- return groups;
- }
-
- /* Helper function to get group name from search results.
- */
- void getGroupNames(SearchResult groupResult, Collection groups,
- Collection groupDNs, boolean doGetDNs)
- throws NamingException {
- Attribute groupName = groupResult.getAttributes().get(groupNameAttr);
- if (groupName == null) {
- throw new NamingException("The group object does not have " +
- "attribute '" + groupNameAttr + "'.");
- }
- groups.add(groupName.get().toString());
- if (doGetDNs) {
- groupDNs.add(groupResult.getNameInNamespace());
- }
- }
-
- /* Implementation for walking up the ldap hierarchy
- * This function will iteratively find the super-group memebership of
- * groups listed in groupDNs and add them to
- * the groups set. It will walk up the hierarchy goUpHierarchy levels.
- * Note: This is an expensive operation and settings higher than 1
- * are NOT recommended as they will impact both the speed and
- * memory usage of all operations.
- * The maximum time for this function will be bounded by the ldap query
- * timeout and the number of ldap queries that it will make, which is
- * max(Recur Depth in LDAP, goUpHierarcy) * DIRECTORY_SEARCH_TIMEOUT
- *
- * @param ctx - The context for contacting the ldap server
- * @param groupDNs - the distinguished name of the groups whose parents we
- * want to look up
- * @param goUpHierarchy - the number of levels to go up,
- * @param groups - Output variable to store all groups that will be added
- */
- void goUpGroupHierarchy(Set groupDNs,
- int goUpHierarchy,
- Set groups)
- throws NamingException {
- if (goUpHierarchy <= 0 || groups.isEmpty()) {
- return;
- }
- DirContext context = getDirContext();
- Set nextLevelGroups = new HashSet();
- StringBuilder filter = new StringBuilder();
- filter.append("(&").append(groupSearchFilter).append("(|");
- for (String dn : groupDNs) {
- filter.append("(").append(groupMemberAttr).append("=")
- .append(dn).append(")");
- }
- filter.append("))");
- LOG.debug("Ldap group query string: " + filter.toString());
- NamingEnumeration groupResults =
- context.search(groupbaseDN,
- filter.toString(),
- SEARCH_CONTROLS);
- while (groupResults.hasMoreElements()) {
- SearchResult groupResult = groupResults.nextElement();
- getGroupNames(groupResult, groups, nextLevelGroups, true);
- }
- goUpGroupHierarchy(nextLevelGroups, goUpHierarchy - 1, groups);
- }
-
- DirContext getDirContext() throws NamingException {
- if (ctx == null) {
- // Set up the initial environment for LDAP connectivity
- Hashtable env = new Hashtable();
- env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
- env.put(Context.PROVIDER_URL, ldapUrl);
- env.put(Context.SECURITY_AUTHENTICATION, "simple");
-
- // Set up SSL security, if necessary
- if (useSsl) {
- env.put(Context.SECURITY_PROTOCOL, "ssl");
- if (!keystore.isEmpty()) {
- System.setProperty("javax.net.ssl.keyStore", keystore);
- }
- if (!keystorePass.isEmpty()) {
- System.setProperty("javax.net.ssl.keyStorePassword", keystorePass);
- }
- if (!truststore.isEmpty()) {
- System.setProperty("javax.net.ssl.trustStore", truststore);
- }
- if (!truststorePass.isEmpty()) {
- System.setProperty("javax.net.ssl.trustStorePassword",
- truststorePass);
- }
- }
-
- env.put(Context.SECURITY_PRINCIPAL, bindUser);
- env.put(Context.SECURITY_CREDENTIALS, bindPassword);
-
- env.put("com.sun.jndi.ldap.connect.timeout", conf.get(CONNECTION_TIMEOUT,
- String.valueOf(CONNECTION_TIMEOUT_DEFAULT)));
- env.put("com.sun.jndi.ldap.read.timeout", conf.get(READ_TIMEOUT,
- String.valueOf(READ_TIMEOUT_DEFAULT)));
-
- ctx = new InitialDirContext(env);
- }
- return ctx;
- }
-
- /**
- * Caches groups, no need to do that for this provider
- */
- @Override
- public void cacheGroupsRefresh() throws IOException {
- // does nothing in this provider of user to groups mapping
- }
-
- /**
- * Adds groups to cache, no need to do that for this provider
- *
- * @param groups unused
- */
- @Override
- public void cacheGroupsAdd(List groups) throws IOException {
- // does nothing in this provider of user to groups mapping
- }
-
- @Override
- public synchronized Configuration getConf() {
- return conf;
- }
-
- @Override
- public synchronized void setConf(Configuration conf) {
- ldapUrl = conf.get(LDAP_URL_KEY, LDAP_URL_DEFAULT);
- if (ldapUrl == null || ldapUrl.isEmpty()) {
- throw new RuntimeException("LDAP URL is not configured");
- }
-
- useSsl = conf.getBoolean(LDAP_USE_SSL_KEY, LDAP_USE_SSL_DEFAULT);
- if (useSsl) {
- loadSslConf(conf);
- }
-
- bindUser = conf.get(BIND_USER_KEY, BIND_USER_DEFAULT);
- bindPassword = getPassword(conf, BIND_PASSWORD_KEY, BIND_PASSWORD_DEFAULT);
- if (bindPassword.isEmpty()) {
- bindPassword = extractPassword(
- conf.get(BIND_PASSWORD_FILE_KEY, BIND_PASSWORD_FILE_DEFAULT));
- }
-
- String baseDN = conf.getTrimmed(BASE_DN_KEY, BASE_DN_DEFAULT);
-
- //User search base which defaults to base dn.
- userbaseDN = conf.getTrimmed(USER_BASE_DN_KEY, baseDN);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Usersearch baseDN: " + userbaseDN);
- }
-
- //Group search base which defaults to base dn.
- groupbaseDN = conf.getTrimmed(GROUP_BASE_DN_KEY, baseDN);
- if (LOG.isDebugEnabled()) {
- LOG.debug("Groupsearch baseDN: " + userbaseDN);
- }
-
- groupSearchFilter =
- conf.get(GROUP_SEARCH_FILTER_KEY, GROUP_SEARCH_FILTER_DEFAULT);
- userSearchFilter =
- conf.get(USER_SEARCH_FILTER_KEY, USER_SEARCH_FILTER_DEFAULT);
- isPosix = groupSearchFilter.contains(POSIX_GROUP) && userSearchFilter
- .contains(POSIX_ACCOUNT);
- memberOfAttr =
- conf.get(MEMBEROF_ATTR_KEY, MEMBEROF_ATTR_DEFAULT);
- // if memberOf attribute is set, resolve group names from the attribute
- // of user objects.
- useOneQuery = !memberOfAttr.isEmpty();
- groupMemberAttr =
- conf.get(GROUP_MEMBERSHIP_ATTR_KEY, GROUP_MEMBERSHIP_ATTR_DEFAULT);
- groupNameAttr =
- conf.get(GROUP_NAME_ATTR_KEY, GROUP_NAME_ATTR_DEFAULT);
- groupHierarchyLevels =
- conf.getInt(GROUP_HIERARCHY_LEVELS_KEY, GROUP_HIERARCHY_LEVELS_DEFAULT);
- posixUidAttr =
- conf.get(POSIX_UID_ATTR_KEY, POSIX_UID_ATTR_DEFAULT);
- posixGidAttr =
- conf.get(POSIX_GID_ATTR_KEY, POSIX_GID_ATTR_DEFAULT);
-
- int dirSearchTimeout = conf.getInt(DIRECTORY_SEARCH_TIMEOUT, DIRECTORY_SEARCH_TIMEOUT_DEFAULT);
- SEARCH_CONTROLS.setTimeLimit(dirSearchTimeout);
- // Limit the attributes returned to only those required to speed up the search.
- // See HADOOP-10626 and HADOOP-12001 for more details.
- String[] returningAttributes;
- if (useOneQuery) {
- returningAttributes = new String[] {
- groupNameAttr, posixUidAttr, posixGidAttr, memberOfAttr};
- } else {
- returningAttributes = new String[] {
- groupNameAttr, posixUidAttr, posixGidAttr};
- }
- SEARCH_CONTROLS.setReturningAttributes(returningAttributes);
-
- this.conf = conf;
- }
-
- private void loadSslConf(Configuration sslConf) {
- keystore = sslConf.get(LDAP_KEYSTORE_KEY, LDAP_KEYSTORE_DEFAULT);
- keystorePass = getPassword(sslConf, LDAP_KEYSTORE_PASSWORD_KEY,
- LDAP_KEYSTORE_PASSWORD_DEFAULT);
- if (keystorePass.isEmpty()) {
- keystorePass = extractPassword(sslConf.get(
- LDAP_KEYSTORE_PASSWORD_FILE_KEY,
- LDAP_KEYSTORE_PASSWORD_FILE_DEFAULT));
- }
-
- truststore = sslConf.get(LDAP_TRUSTSTORE_KEY, "");
- truststorePass = getPasswordFromCredentialProviders(
- sslConf, LDAP_TRUSTSTORE_PASSWORD_KEY, "");
- if (truststorePass.isEmpty()) {
- truststorePass = extractPassword(
- sslConf.get(LDAP_TRUSTSTORE_PASSWORD_FILE_KEY, ""));
- }
- }
-
- String getPasswordFromCredentialProviders(
- Configuration conf, String alias, String defaultPass) {
- String password = defaultPass;
- try {
- char[] passchars = conf.getPasswordFromCredentialProviders(alias);
- if (passchars != null) {
- password = new String(passchars);
- }
- } catch (IOException ioe) {
- LOG.warn("Exception while trying to get password for alias {}: {}",
- alias, ioe);
- }
- return password;
- }
-
- /**
- * Passwords should not be stored in configuration. Use
- * {@link #getPasswordFromCredentialProviders(
- * Configuration, String, String)}
- * to avoid reading passwords from a configuration file.
- */
- @Deprecated
- String getPassword(Configuration conf, String alias, String defaultPass) {
- String password = defaultPass;
- try {
- char[] passchars = conf.getPassword(alias);
- if (passchars != null) {
- password = new String(passchars);
- }
- } catch (IOException ioe) {
- LOG.warn("Exception while trying to get password for alias " + alias
- + ": ", ioe);
- }
- return password;
- }
-
- String extractPassword(String pwFile) {
- if (pwFile.isEmpty()) {
- // If there is no password file defined, we'll assume that we should do
- // an anonymous bind
- return "";
- }
-
- StringBuilder password = new StringBuilder();
- try (Reader reader = new InputStreamReader(
- new FileInputStream(pwFile), StandardCharsets.UTF_8)) {
- int c = reader.read();
- while (c > -1) {
- password.append((char)c);
- c = reader.read();
- }
- return password.toString().trim();
- } catch (IOException ioe) {
- throw new RuntimeException("Could not read password file: " + pwFile, ioe);
- }
- }
-}
diff --git a/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java b/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java
deleted file mode 100644
index 5500a75..0000000
--- a/src/main/java/org/apache/hadoop/security/authentication/util/KerberosUtil.java
+++ /dev/null
@@ -1,462 +0,0 @@
-/**
- * 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.security.authentication.util;
-
-import static org.apache.hadoop.util.PlatformName.IBM_JAVA;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.lang.reflect.InvocationTargetException;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.nio.ByteBuffer;
-import java.nio.charset.IllegalCharsetNameException;
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Locale;
-import java.util.NoSuchElementException;
-import java.util.Set;
-import java.util.regex.Pattern;
-
-import org.apache.kerby.kerberos.kerb.keytab.Keytab;
-import org.apache.kerby.kerberos.kerb.type.base.PrincipalName;
-import org.ietf.jgss.GSSException;
-import org.ietf.jgss.Oid;
-
-import javax.security.auth.Subject;
-import javax.security.auth.kerberos.KerberosPrincipal;
-import javax.security.auth.kerberos.KerberosTicket;
-import javax.security.auth.kerberos.KeyTab;
-
-public class KerberosUtil {
-
- /* Return the Kerberos login module name */
- public static String getKrb5LoginModuleName() {
- return (IBM_JAVA)
- ? "com.ibm.security.auth.module.Krb5LoginModule"
- : "com.sun.security.auth.module.Krb5LoginModule";
- }
-
- public static final Oid GSS_SPNEGO_MECH_OID =
- getNumericOidInstance("1.3.6.1.5.5.2");
- public static final Oid GSS_KRB5_MECH_OID =
- getNumericOidInstance("1.2.840.113554.1.2.2");
- public static final Oid NT_GSS_KRB5_PRINCIPAL_OID =
- getNumericOidInstance("1.2.840.113554.1.2.2.1");
-
- // numeric oids will never generate a GSSException for a malformed oid.
- // use to initialize statics.
- private static Oid getNumericOidInstance(String oidName) {
- try {
- return new Oid(oidName);
- } catch (GSSException ex) {
- throw new IllegalArgumentException(ex);
- }
- }
-
- /**
- * Returns the Oid instance from string oidName.
- * Use {@link GSS_SPNEGO_MECH_OID}, {@link GSS_KRB5_MECH_OID},
- * or {@link NT_GSS_KRB5_PRINCIPAL_OID} instead.
- *
- * @return Oid instance
- * @param oidName The oid Name
- * @throws NoSuchFieldException if the input is not supported.
- */
- @Deprecated
- public static Oid getOidInstance(String oidName)
- throws NoSuchFieldException {
- switch (oidName) {
- case "GSS_SPNEGO_MECH_OID":
- return GSS_SPNEGO_MECH_OID;
- case "GSS_KRB5_MECH_OID":
- return GSS_KRB5_MECH_OID;
- case "NT_GSS_KRB5_PRINCIPAL":
- return NT_GSS_KRB5_PRINCIPAL_OID;
- default:
- throw new NoSuchFieldException(
- "oidName: " + oidName + " is not supported.");
- }
- }
-
- /**
- * Return the default realm for this JVM.
- *
- * @return The default realm
- * @throws IllegalArgumentException If the default realm does not exist.
- * @throws ClassNotFoundException Not thrown. Exists for compatibility.
- * @throws NoSuchMethodException Not thrown. Exists for compatibility.
- * @throws IllegalAccessException Not thrown. Exists for compatibility.
- * @throws InvocationTargetException Not thrown. Exists for compatibility.
- */
- public static String getDefaultRealm()
- throws ClassNotFoundException, NoSuchMethodException,
- IllegalArgumentException, IllegalAccessException,
- InvocationTargetException {
- // Any name is okay.
- return new KerberosPrincipal("tmp", 1).getRealm();
- }
-
- /**
- * Return the default realm for this JVM.
- * If the default realm does not exist, this method returns null.
- *
- * @return The default realm
- */
- public static String getDefaultRealmProtected() {
- try {
- return getDefaultRealm();
- } catch (Exception e) {
- //silently catch everything
- return null;
- }
- }
-
- /*
- * For a Service Host Principal specification, map the host's domain
- * to kerberos realm, as specified by krb5.conf [domain_realm] mappings.
- * Unfortunately the mapping routines are private to the security.krb5
- * package, so have to construct a PrincipalName instance to derive the realm.
- *
- * Many things can go wrong with Kerberos configuration, and this is not
- * the place to be throwing exceptions to help debug them. Nor do we choose
- * to make potentially voluminous logs on every call to a communications API.
- * So we simply swallow all exceptions from the underlying libraries and
- * return null if we can't get a good value for the realmString.
- *
- * @param shortprinc A service principal name with host fqdn as instance, e.g.
- * "HTTP/myhost.mydomain"
- * @return String value of Kerberos realm, mapped from host fqdn
- * May be default realm, or may be null.
- */
- public static String getDomainRealm(String shortprinc) {
- Class> classRef;
- Object principalName; //of type sun.security.krb5.PrincipalName or IBM equiv
- String realmString = null;
- try {
- if (IBM_JAVA) {
- classRef = Class.forName("com.ibm.security.krb5.PrincipalName");
- } else {
- classRef = Class.forName("sun.security.krb5.PrincipalName");
- }
- int tKrbNtSrvHst = classRef.getField("KRB_NT_SRV_HST").getInt(null);
- principalName = classRef.getConstructor(String.class, int.class).
- newInstance(shortprinc, tKrbNtSrvHst);
- realmString = (String)classRef.getMethod("getRealmString", new Class[0]).
- invoke(principalName, new Object[0]);
- } catch (RuntimeException rte) {
- //silently catch everything
- } catch (Exception e) {
- //silently return default realm (which may itself be null)
- }
- if (null == realmString || realmString.equals("")) {
- return getDefaultRealmProtected();
- } else {
- return realmString;
- }
- }
-
- /* Return fqdn of the current host */
- static String getLocalHostName() throws UnknownHostException {
- return InetAddress.getLocalHost().getCanonicalHostName();
- }
-
- /**
- * Create Kerberos principal for a given service and hostname,
- * inferring realm from the fqdn of the hostname. It converts
- * hostname to lower case. If hostname is null or "0.0.0.0", it uses
- * dynamically looked-up fqdn of the current host instead.
- * If domain_realm mappings are inadequately specified, it will
- * use default_realm, per usual Kerberos behavior.
- * If default_realm also gives a null value, then a principal
- * without realm will be returned, which by Kerberos definitions is
- * just another way to specify default realm.
- *
- * @param service
- * Service for which you want to generate the principal.
- * @param hostname
- * Fully-qualified domain name.
- * @return Converted Kerberos principal name.
- * @throws UnknownHostException
- * If no IP address for the local host could be found.
- */
- public static final String getServicePrincipal(String service,
- String hostname)
- throws UnknownHostException {
- String fqdn = hostname;
- String shortprinc = null;
- String realmString = null;
- if (null == fqdn || fqdn.equals("") || fqdn.equals("0.0.0.0")) {
- fqdn = getLocalHostName();
- }
- // convert hostname to lowercase as kerberos does not work with hostnames
- // with uppercase characters.
- fqdn = fqdn.toLowerCase(Locale.US);
- shortprinc = service + "/" + fqdn;
- // Obtain the realm name inferred from the domain of the host
- realmString = getDomainRealm(shortprinc);
- if (null == realmString || realmString.equals("")) {
- return shortprinc;
- } else {
- return shortprinc + "@" + realmString;
- }
- }
-
- /**
- * Get all the unique principals present in the keytabfile.
- *
- * @param keytabFileName
- * Name of the keytab file to be read.
- * @return list of unique principals in the keytab.
- * @throws IOException
- * If keytab entries cannot be read from the file.
- */
- static final String[] getPrincipalNames(String keytabFileName) throws IOException {
- Keytab keytab = Keytab.loadKeytab(new File(keytabFileName));
- Set principals = new HashSet();
- List entries = keytab.getPrincipals();
- for (PrincipalName entry : entries) {
- principals.add(entry.getName().replace("\\", "/"));
- }
- return principals.toArray(new String[0]);
- }
-
- /**
- * Get all the unique principals from keytabfile which matches a pattern.
- *
- * @param keytab Name of the keytab file to be read.
- * @param pattern pattern to be matched.
- * @return list of unique principals which matches the pattern.
- * @throws IOException if cannot get the principal name
- */
- public static final String[] getPrincipalNames(String keytab,
- Pattern pattern) throws IOException {
- String[] principals = getPrincipalNames(keytab);
- if (principals.length != 0) {
- List matchingPrincipals = new ArrayList();
- for (String principal : principals) {
- if (pattern.matcher(principal).matches()) {
- matchingPrincipals.add(principal);
- }
- }
- principals = matchingPrincipals.toArray(new String[0]);
- }
- return principals;
- }
-
- /**
- * Check if the subject contains Kerberos keytab related objects.
- * The Kerberos keytab object attached in subject has been changed
- * from KerberosKey (JDK 7) to KeyTab (JDK 8)
- *
- *
- * @param subject subject to be checked
- * @return true if the subject contains Kerberos keytab
- */
- public static boolean hasKerberosKeyTab(Subject subject) {
- return !subject.getPrivateCredentials(KeyTab.class).isEmpty();
- }
-
- /**
- * Check if the subject contains Kerberos ticket.
- *
- *
- * @param subject subject to be checked
- * @return true if the subject contains Kerberos ticket
- */
- public static boolean hasKerberosTicket(Subject subject) {
- return !subject.getPrivateCredentials(KerberosTicket.class).isEmpty();
- }
-
- /**
- * Extract the TGS server principal from the given gssapi kerberos or spnego
- * wrapped token.
- * @param rawToken bytes of the gss token
- * @return String of server principal
- * @throws IllegalArgumentException if token is undecodable
- */
- public static String getTokenServerName(byte[] rawToken) {
- // subsequent comments include only relevant portions of the kerberos
- // DER encoding that will be extracted.
- DER token = new DER(rawToken);
- // InitialContextToken ::= [APPLICATION 0] IMPLICIT SEQUENCE {
- // mech OID
- // mech-token (NegotiationToken or InnerContextToken)
- // }
- DER oid = token.next();
- if (oid.equals(DER.SPNEGO_MECH_OID)) {
- // NegotiationToken ::= CHOICE {
- // neg-token-init[0] NegTokenInit
- // }
- // NegTokenInit ::= SEQUENCE {
- // mech-token[2] InitialContextToken
- // }
- token = token.next().get(0xa0, 0x30, 0xa2, 0x04).next();
- oid = token.next();
- }
- if (!oid.equals(DER.KRB5_MECH_OID)) {
- throw new IllegalArgumentException("Malformed gss token");
- }
- // InnerContextToken ::= {
- // token-id[1]
- // AP-REQ
- // }
- if (token.next().getTag() != 1) {
- throw new IllegalArgumentException("Not an AP-REQ token");
- }
- // AP-REQ ::= [APPLICATION 14] SEQUENCE {
- // ticket[3] Ticket
- // }
- DER ticket = token.next().get(0x6e, 0x30, 0xa3, 0x61, 0x30);
- // Ticket ::= [APPLICATION 1] SEQUENCE {
- // realm[1] String
- // sname[2] PrincipalName
- // }
- // PrincipalName ::= SEQUENCE {
- // name-string[1] SEQUENCE OF String
- // }
- String realm = ticket.get(0xa1, 0x1b).getAsString();
- DER names = ticket.get(0xa2, 0x30, 0xa1, 0x30);
- StringBuilder sb = new StringBuilder();
- while (names.hasNext()) {
- if (sb.length() > 0) {
- sb.append('/');
- }
- sb.append(names.next().getAsString());
- }
- return sb.append('@').append(realm).toString();
- }
-
- // basic ASN.1 DER decoder to traverse encoded byte arrays.
- private static class DER implements Iterator {
- static final DER SPNEGO_MECH_OID = getDER(GSS_SPNEGO_MECH_OID);
- static final DER KRB5_MECH_OID = getDER(GSS_KRB5_MECH_OID);
-
- private static DER getDER(Oid oid) {
- try {
- return new DER(oid.getDER());
- } catch (GSSException ex) {
- // won't happen. a proper OID is encodable.
- throw new IllegalArgumentException(ex);
- }
- }
-
- private final int tag;
- private final ByteBuffer bb;
-
- DER(byte[] buf) {
- this(ByteBuffer.wrap(buf));
- }
-
- DER(ByteBuffer srcbb) {
- tag = srcbb.get() & 0xff;
- int length = readLength(srcbb);
- bb = srcbb.slice();
- bb.limit(length);
- srcbb.position(srcbb.position() + length);
- }
-
- int getTag() {
- return tag;
- }
-
- // standard ASN.1 encoding.
- private static int readLength(ByteBuffer bb) {
- int length = bb.get();
- if ((length & (byte)0x80) != 0) {
- int varlength = length & 0x7f;
- length = 0;
- for (int i=0; i < varlength; i++) {
- length = (length << 8) | (bb.get() & 0xff);
- }
- }
- return length;
- }
-
- DER choose(int subtag) {
- while (hasNext()) {
- DER der = next();
- if (der.getTag() == subtag) {
- return der;
- }
- }
- return null;
- }
-
- DER get(int... tags) {
- DER der = this;
- for (int i=0; i < tags.length; i++) {
- int expectedTag = tags[i];
- // lookup for exact match, else scan if it's sequenced.
- if (der.getTag() != expectedTag) {
- der = der.hasNext() ? der.choose(expectedTag) : null;
- }
- if (der == null) {
- StringBuilder sb = new StringBuilder("Tag not found:");
- for (int ii=0; ii <= i; ii++) {
- sb.append(" 0x").append(Integer.toHexString(tags[ii]));
- }
- throw new IllegalStateException(sb.toString());
- }
- }
- return der;
- }
-
- String getAsString() {
- try {
- return new String(bb.array(), bb.arrayOffset() + bb.position(),
- bb.remaining(), "UTF-8");
- } catch (UnsupportedEncodingException e) {
- throw new IllegalCharsetNameException("UTF-8"); // won't happen.
- }
- }
-
- @Override
- public int hashCode() {
- return 31 * tag + bb.hashCode();
- }
-
- @Override
- public boolean equals(Object o) {
- return (o instanceof DER) &&
- tag == ((DER)o).tag && bb.equals(((DER)o).bb);
- }
-
- @Override
- public boolean hasNext() {
- // it's a sequence or an embedded octet.
- return ((tag & 0x30) != 0 || tag == 0x04) && bb.hasRemaining();
- }
-
- @Override
- public DER next() {
- if (!hasNext()) {
- throw new NoSuchElementException();
- }
- return new DER(bb);
- }
-
- @Override
- public String toString() {
- return "[tag=0x"+Integer.toHexString(tag)+" bb="+bb+"]";
- }
- }
-}
diff --git a/src/main/java/org/apache/hadoop/util/LineReader.java b/src/main/java/org/apache/hadoop/util/LineReader.java
index 08bd810..f34f61c 100644
--- a/src/main/java/org/apache/hadoop/util/LineReader.java
+++ b/src/main/java/org/apache/hadoop/util/LineReader.java
@@ -22,6 +22,7 @@
import java.io.IOException;
import java.io.InputStream;
+import io.trino.hadoop.TextLineLengthLimitExceededException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.conf.Configuration;
@@ -46,6 +47,10 @@
@InterfaceAudience.LimitedPrivate({"MapReduce"})
@InterfaceStability.Unstable
public class LineReader implements Closeable, IOStatisticsSource {
+ // Limitation for array size is VM specific. Current HotSpot VM limitation
+ // for array size is Integer.MAX_VALUE - 5 (2^31 - 1 - 5).
+ // Integer.MAX_VALUE - 8 should be safe enough.
+ private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private static final int DEFAULT_BUFFER_SIZE = 64 * 1024;
private int bufferSize = DEFAULT_BUFFER_SIZE;
private InputStream in;
@@ -179,6 +184,8 @@ public IOStatistics getIOStatistics() {
*/
public int readLine(Text str, int maxLineLength,
int maxBytesToConsume) throws IOException {
+ maxLineLength = Math.min(maxLineLength, MAX_ARRAY_SIZE);
+ maxBytesToConsume = Math.min(maxBytesToConsume, MAX_ARRAY_SIZE);
if (this.recordDelimiterBytes != null) {
return readCustomLine(str, maxLineLength, maxBytesToConsume);
} else {
@@ -249,15 +256,28 @@ private int readDefaultLine(Text str, int maxLineLength, int maxBytesToConsume)
int appendLength = readLength - newlineLength;
if (appendLength > maxLineLength - txtLength) {
appendLength = maxLineLength - txtLength;
+ if (appendLength > 0) {
+ // We want to fail the read when the line length is over the limit.
+ throw new TextLineLengthLimitExceededException("Too many bytes before newline: " + maxLineLength);
+ }
}
if (appendLength > 0) {
+ int newTxtLength = txtLength + appendLength;
+ if (str.getBytes().length < newTxtLength && Math.max(newTxtLength, txtLength << 1) > MAX_ARRAY_SIZE) {
+ // If str need to be resized but the target capacity is over VM limit, it will trigger OOM.
+ // In such case we will throw an IOException so the caller can deal with it.
+ throw new TextLineLengthLimitExceededException("Too many bytes before newline: " + newTxtLength);
+ }
str.append(buffer, startPosn, appendLength);
- txtLength += appendLength;
+ txtLength = newTxtLength;
}
} while (newlineLength == 0 && bytesConsumed < maxBytesToConsume);
- if (bytesConsumed > Integer.MAX_VALUE) {
- throw new IOException("Too many bytes before newline: " + bytesConsumed);
+ if (newlineLength == 0 && bytesConsumed >= maxBytesToConsume) {
+ // It is possible that bytesConsumed is over the maxBytesToConsume but we
+ // didn't append anything to str.bytes. If we have consumed over maxBytesToConsume
+ // bytes but still haven't seen a line terminator, we will fail the read.
+ throw new TextLineLengthLimitExceededException("Too many bytes before newline: " + bytesConsumed);
}
return (int)bytesConsumed;
}
@@ -341,6 +361,10 @@ private int readCustomLine(Text str, int maxLineLength, int maxBytesToConsume)
int appendLength = readLength - delPosn;
if (appendLength > maxLineLength - txtLength) {
appendLength = maxLineLength - txtLength;
+ if (appendLength > 0) {
+ // We want to fail the read when the line length is over the limit.
+ throw new TextLineLengthLimitExceededException("Too many bytes before delimiter: " + maxLineLength);
+ }
}
bytesConsumed += ambiguousByteCount;
if (appendLength >= 0 && ambiguousByteCount > 0) {
@@ -353,8 +377,14 @@ private int readCustomLine(Text str, int maxLineLength, int maxBytesToConsume)
unsetNeedAdditionalRecordAfterSplit();
}
if (appendLength > 0) {
+ int newTxtLength = txtLength + appendLength;
+ if (str.getBytes().length < newTxtLength && Math.max(newTxtLength, txtLength << 1) > MAX_ARRAY_SIZE) {
+ // If str need to be resized but the target capacity is over VM limit, it will trigger OOM.
+ // In such case we will throw an IOException so the caller can deal with it.
+ throw new TextLineLengthLimitExceededException("Too many bytes before delimiter: " + newTxtLength);
+ }
str.append(buffer, startPosn, appendLength);
- txtLength += appendLength;
+ txtLength = newTxtLength;
}
if (bufferPosn >= bufferLength) {
if (delPosn > 0 && delPosn < recordDelimiterBytes.length) {
@@ -364,8 +394,12 @@ private int readCustomLine(Text str, int maxLineLength, int maxBytesToConsume)
}
} while (delPosn < recordDelimiterBytes.length
&& bytesConsumed < maxBytesToConsume);
- if (bytesConsumed > Integer.MAX_VALUE) {
- throw new IOException("Too many bytes before delimiter: " + bytesConsumed);
+ if (delPosn < recordDelimiterBytes.length
+ && bytesConsumed >= maxBytesToConsume) {
+ // It is possible that bytesConsumed is over the maxBytesToConsume but we
+ // didn't append anything to str.bytes. If we have consumed over maxBytesToConsume
+ // bytes but still haven't seen a line terminator, we will fail the read.
+ throw new TextLineLengthLimitExceededException("Too many bytes before delimiter: " + bytesConsumed);
}
return (int) bytesConsumed;
}
diff --git a/src/main/java/org/wildfly/openssl/OpenSSLProvider.java b/src/main/java/org/wildfly/openssl/OpenSSLProvider.java
index e1e6403..569560e 100644
--- a/src/main/java/org/wildfly/openssl/OpenSSLProvider.java
+++ b/src/main/java/org/wildfly/openssl/OpenSSLProvider.java
@@ -13,23 +13,9 @@
*/
package org.wildfly.openssl;
-import org.apache.hadoop.fs.azurebfs.utils.SSLSocketFactoryEx;
-import org.apache.hadoop.fs.azurebfs.utils.SSLSocketFactoryEx.SSLChannelMode;
-
-import java.io.IOException;
-import java.io.UncheckedIOException;
-
public final class OpenSSLProvider
{
private OpenSSLProvider() {}
- public static void register()
- {
- try {
- SSLSocketFactoryEx.initializeDefaultFactory(SSLChannelMode.Default_JSSE);
- }
- catch (IOException e) {
- throw new UncheckedIOException(e);
- }
- }
+ public static void register() {}
}
diff --git a/src/main/resources/nativelib/Linux-aarch64/libhadoop.so b/src/main/resources/nativelib/Linux-aarch64/libhadoop.so
index eac5006ae42c3cdaa9b3bf8c60966a41990c7127..42958bd49371481010dc6a7ae1acbc89e4749146 100755
GIT binary patch
literal 742544
zcmeEv3w)Ht)%WaffN&85gd{38y8&-&tK2W%*lbX|R*Y1swaw;+AeV$BR1lRVprysO
zJc`BEwk~*iO?r{G&=PBP0rj=jwiGS3_O=P2RBekQk_9o}|38=A+08BzXy5Ps{k|{!
zmfbls&zUo4&YbzrnR%XP>7`d+=61P+`SXaM38uoIQHg!@Unt(@TgWTa|GiPX{#Po${#R~!vH9MRt9Z>AV0fx>EXC_OUsmbPm(_LV
zJ5MEr`POVi4zA(nMRi%X?-9u3pZR84{!QD{RKwpFIqJgn@0)ew&9}MkI$TFNkM%E4
z7cAHHWz)-QU7z{Z&5>=(2M-TEQ$PI~amwMpZ}_h%-`()JS-1J7H(&SU!gKF=m{lyt
z=Q?~?Uq5_?T<(9g*IA;xNW8S~+7aKI5ePt5ALs0uSv76uFAv{0wK4C<*Er|*`%?MRnZxVOSUBv!hwWGT
zf3SD?X-&QJe!uL#{WIoouKV>v*M4E}4Ric1i2Q!5t@m^n){F7nQzu
z=icrA{aDr39h*N@-1(QhJ9k}l!^EpUJM7%2${NP@
z_=#2PFZs(Cmru)iWnaYo*KO;c{@pkGw|?fmqEBp|bJaiQ{ptHJed*lXbNl`J)v9~;
z9vIPn?!7w)UGlR}eSY6t7u<|b{gJ>6zn!w|yA}BT?c1k(`3v}7Iq{afgFnBxK7`+&
z`smpKP4gdnu@b*`?i+ng6~4t~%dc1v{_DjJKl#HDrhj_Y*~6CKvvk2#wWC*#d1Uv0
zKXCq}+^^o=vgx9Y&))X0d4a;Szxm{wpMBR;dEwT{
zSAF6yqg`KmzHr@-e=2qdPHW%a`}^B|u)FBPwoP9-_qx^xZk$s3v-$^yg@db{vp#?I
zsZZT<{(rr7$AOyDKJ%lOesSwl2TyN)Z^;?sRy}!{zq#-1_CM6Qrfqz5c=dH>xBhc|
z{*iO?CLj6elHFHU9r$PNALhRClRs^_+VjxDcfYyl=NFE;`=iMV<1-eG_fDR+?O*Tu
z-V06XS9@b;`SPFnzWl(m<-fV{g8pB=d<(v3MlbypzqjIZ(y5c4+J^7v@cHUv54_sZ
zXYwZo@Bdvb)8Be)X7q_Ir=30KPvb)`AnlvD=jQ7U?rFXA_Tl5#zxUmt|2<^Rj6W``
z_~BJmRpW18am`fM6F*q=+r#fJ(B_%(3m#95{7m;Ok0=niueeh(?(_*UIEA7TKOL5t
zKBp)%{YkLR#OH!6{I5DCbNM_}@6903@3WMz9F&>QzlLU}UzUZ>WvH_xL%r2m+S?C}
z&D7p&hh(NdIwCXuLRV(`521)m{Qm+O(d2G_!7O~<&C*`ZDVg}+orQi@7C!X1ndG4l
z&BXsB5Hr!~hcnS1EzV4D$kH!^v&dPTh5q=dnfcGoV*h9M$y|Qv>6z(+vheB5BL82q
z$hjg*dCujT^j4KcZ!nXLT1et;rer0IoNLW@5VRFN19c7jrtNxrJhlqjvap&sV`9j
zek>oFqX?$_KXrQ-J)-D(xhoo^eo+3fqU&aht8~5fcPaWsTFwW2ihuneCFdO~TYN|M
zBjn+N0DqUOFEI%Epggl06yeXBPw_xSpYf!k8-3PFyG8jeif+~uv%RW((Lh!2NR=&4
z)pEXeyCSUdsPEI|`06iSd|Rcb=z7=Sa>_GurlN0DU*cH^K>FtU6yZTlztF4b8%|R7
z)@XX~lN7!4@znCamh(%0k=v!{GjzQV>HeDdilXNzb|OdXZTnV5f6d4#?YX}Q|3T5W
z>vsJ@>t~`aZ~C`I+s|sZV!T0JCRX7(wkvmmA{csy?w9(-ie97T>8tCF{9e&xn*J#{
zpTbVQs^}v$y&mlvBnm}zrxM)s65hn!&(6WjA#CBDNyC*f)(kxlB$L|`EU4w
zB0Q?;?Os*BsIOAMbGpAiul+;rcT_>s-h1VIf$_Ub(f8_pEbguN%vz!7A<)(H0FNn+N$x?^)xJ=Yuh
z%2fLAbU%i^ugZ05`G*Zx@^}kXdDD-V8$K&i`Mj>j>E3@TJ`wdLhMDr=)bc;p<2~H0
z3J%ldcj)%c$XDfG)bd~$
ze*Zo4N#8qD(Ko0s(V+dw%C9NH`&w^vbieF-M$uR6@=Lvn&*j=-oUQ3Yv^*1kt_Wtl
zJgxbM)5gn%dc1polUn|H&Byx-MSo56ze)S&C!SXXGcSBf>rL!c^wnAqvvmJ%*Zr&2
zBwo^Xb@?|{`Hkog{*`-myY##^L-V;=kE4(tNAz)Z=}F9`9o{{Tp6YZ>+ykz;sRDW$g29MVO}PcWFPcA*={5YrSpMa?a5H-`K+>
zjdLa*QsvLqxl3C*AKWUr>a1w0*A7{V0R6$TGO`8LhX-K~?_yntz#G
zM*tVSr1_{XQR`FnPSo}c^OJvD^tg*YrP58BK1ADt@G8az=X_t|fm}Ub8~eOW%UQHm
zG0T%bp%O4u3_}4$F2oX*Hg65N##%C%RQl8a%ysy;df0(1_o4=?C
z8+AXjn^=Bim7>qkcK(63lkFcWdPseV-|7C^mxgD4hK?hjMLSeMGu~Hdd5Z2+^oZuO
zN%OBy<6oo4MV=lP-`3^N)$>c_JE?qr1$&V5^;~UeXea+3*El3kmp6JoWXjJ@eJ|7Q$6l*-4g?WaD?emYEfvjEb3er)K*;c5C|Jzkz5z{8)h|95n|
zigde7|NdB)_v-S7{iKcTRX;{fR^_+r%Pu-y+sP4CaGx%}
z)9{~`%Ky_E7e${?1y^f(yGzeItAC^D#xD&;KJDkhTNM2{kNQ45Owp&sReHDj68D!V
zdTtu73u`@$d|4HIN0)y~kN1766v3FwmrquF^0eK~(&cZ__7F<5hmf}OC(`CwQBiTr
zyt?@njZKveO%)ZQ;)?0lRMgZq)ZQ|?v8lFU`ZZ;9>*m)^udJF|tMW4ER8%jjte81_
ze&yWRx7UjKwYOcmsJgbkX?ETG8&Ct7SI?=ao;9c9mW7oKHK2U4aejTn?Dx(3m>xUphBe##&Co{Of4nz@aO
z=iz@<biOL4YiGp6}LAw)l}SceN*N9
z8pvFx$#o4Cv**ulx?)Nrc-7U{&ZpFNTTX;?bwh1sQ>{1=78LG;7}U!9GZMu4PRYPUN;!ju3tA)-hYib%!NI-DBz6pw+cRfwD$GVt%!b+%SICL?eoC{Bskf>d7S}h`Ra}eV*w{F?YgeoANVWu&}=7C?h@NB{g-I(ZBYnn(Dc=mGgUaMcQ~fy7C&}
z8|&s7yNk39iQ86Z%jaQ1#o;$m$;wJo;!Chx*
z%CTH7s?`K_oGcn^o2JaJ=41#rcD&6vPQRK#A?RYM)$-cPV{`RK;c;Wb?4~A6g2$;D
zOtiIGN7~2r%(2c?nZ>`_npJuteTsK7P~e0Z^hkLr)gQlMrLdVj|G1pC(Q68?<2J60
zT#m=Q(ixT4&Z|T}&A;XN_1tqaO%dW44Ylg_+4Z9C*OOmJlgM(gRm=G!{MV;xrMC#}8za<^;MqMJmJR
z_&PeBRgb(uwx%83Wad{k&HiF7`ux-K#}(Jg6{AXs`r9h7)SXjNS9L3wMl)-$*k;!Y
ztV`$DRn*R3IL}a!m!2cj6&00@jkQ><%QbTLsi(UfBS(%LJ@&@g0ByH5R;X2nTB6l8
zEWUAe)2wUiYHCLw#e$BB<7Knw*8W?WiKbZ%b+=({UMtK>TWbrLp9<@_#M7?5{)!nD
zGZB}NT>ulqGJfVFrpJ%PPt_UdDD+_6(a=p6jVS(-eMN;~+vA>6T}3gDLu|ViaKbEzm6~lAzMZ$#|YL?@>xILZ*&aMjc+^*Z8p&
zqd8dgQdyCXmv8l~d37~c%&VU(QDoD<^XOlm@I!PK3er@Y%^SP2i2(|`f7-IF@y2e_TSU8_1exjmiV3QDWq^JjiKy4NC
z79z4I@t0ueDUFWWfy@MKi$RIo5ZR~|a~o@G=RibI8j)F7--MsIk_i-o&!0~iR|QNZ
zxXq9~6f(cIsbb-L%;Zz*xE{S;GN`DWUp=c1QwR}bwsvQ}@{8DNVGL0*Vi(Hz+w7*r
za+KF%5~8YW7S_vsl?Ff=xz_?(Q{9~G^Hb*9*ubnKKJz6?MfH`l8%An%@psP}Nrznz
zx23*&cCGH4o=RQYRNBh8-cdetV*n3<5RO&a3O81I@sN19*N-8TL#X%At4m
z_GLAVJ+hMwT;$voUDorVE^Y#g+4C2P-j+eg<=l#P5v&H}!z2
z$JF!3zy{h;d@rkO;JQcXJ@bY5jX=fBI%O8!$unzVff>l$ZPYw&;etakk(-d+$;wQ+
zRSBWwwT+oL$$GO`)=?V#Y0cB<4|TA;jcB*Yy7JRk5E5Xp-vS)pL|C2p4L_Iojmfqe
z5V3o6xG~qDR*P5=U^X@lk!KaM$h@#=_FNrQDnq2^>bgoC)TqFf$FP@?`P!<5v**@a
zzpx&7t*P<)`7oHQc=i|=+=LSo+3fTfF27c7-<|-6D;l%!kJRF4^5p%!P1v}39-
zo3(KMoLZ=2UZbd5I2VUi8nOcGESzqrZJ0fCvCZfhy78!z(DBPjLzI=5jTv25F@0ji
zc-QMz_eksa1nc)$>-T8u_bBz-V$YwNJAZ2C{Hb~Kr)JHcnlpcD#{4--)X%Q3
z1*ETK;D$QCoqI+z7h=(pmV?L>sf=${RM#)8XjC>?fq?Wa92=Dj7Xk1BVprA7J+HCu
zywUurVLj6$nXcxq@nUYxr%>m@MW3pyY^a_!ek`e@n6HMROrAFqmrT9-ieOpA=<`NR
z5F@TFzhdeYpFUroqH^QdtsBo$aO0nl|MOS!pUO$5Raryz;A&oP|KqW+UfrmsM0$B%
zZ8)%gl~xp&`2YX@JsNmrgnIsk7k#tV`36qf^%5U~#y{5Z!*2OGDfw;P@;xc}zts8U
zr}(=Q`R4g1o*d@lIT1Ij@34OUiJ@Pv>8sP|`}B=%ku(l77G(DU~uh;a|Y4k;!9!aBzHGM-GeWj*H)99-;eR~@HK~0aP(Ic9^FOB|$
zrVD*zpwa&ZP0vfCZ`O2g8a=A%Bh%2a1(E=G?rY
zhfi1ZMS2|Zc9S0}7a;%7`>h&BF8GJPTl6A}{wIs>wdjAf=x1B>c8fmJqT^PB#NR}V
zj-$khzko$wouK0W35))KMW1HTakM${H^ZXi$b8~2WYNtTaAwZ3=wD5g#`B^q`ll@V
zB8z^WMGsqab0(eHD=qpStNdz<9<%5VTJ%3!^oT|Oi$#CJqJP1nZ?Ne1TlCEqUEgXX
zbE6hr--;sXFIx03S$wuzbbWMO=Dudpe`A%8S@bI`y4bGl&<#7>V$pLgx<2wQbMq{^
zp4ue6$fD!cpTwWnqGJM1{GDylaVuEjZ=^-fO;ClHXwh-2LEwhV^iL$HLhQ5XxHTm4*J;sl3t;l^cS-xd
zAxRav7X4j|o@dd$7QM)#`z*TGqE}n=vn_gwMIUL=hg8hXp6qtqK~oYQH%bxMSszvf5)P4x9GpL=&xDyu@*gM
z(Vw&EdoB7ni@wjIkGJTZ7JY(67dw*nKhdJ+TJ%X4J#`h^zVYtb*V=x1B>
zUs?2#7X4z2KGCATY0(1~{Su3QxkaCB(WhDTfJL8S(LZa^Ll%9pMW1ESOD%f6MGso^
zMHaoxqK7T|6pOynqF-v!S6lSUEc$~MeX2!|SoA+w^d~I()wdhfc
zex*f!(V|~v(YIUlt1bF#7X2EF9<%6Qx9EE<`ll`WK8t>>Menreb1b^h_o)*9Ota`m
zmOR^X#3P(XTw=|UC0m||dMb8W*Gf9nRHV0B>C=(!u+k++yIslp&qTV!
zO8b!xTIutUuCmgjk#4rq2jo-t@I5@ueZ`SA-&Z~e*x(ZD_w=O+moz+CekHV`c|ZaR(d|tRaUwI>1HeaMWokT
z>D!UsYNhW&y2DC0Bkj&f)_)JuC06F*-l
zVWrn2?e3MV|8b;CtaK~VK`Z@3q^qp-kCAS+(mzFdy_J3n>8)1!8KgU`^j4(Zy_5C7
zfOLtK{uRDQ6oYNh{*bcdDhK-%3WS^pbImsshykPceu
ze`Xi(}thBon_2(w*?~QbcmF|ah&`J+Py2?rqM!MNb
z7a+afN}r1KRx5ov(j8X11Zj8QWc_C%U1FvENC&O-c}Q1T>Cs3xTj}vgueZ_{AidQ}
zUxIXpl@229?w73pGNena^p!{lt@NjnuCmhQNH<&Q8<1XarEfxdtCju&(j8X13Tb!$
zWc@RdF0s5ER(aJuQ^*uN>4!#@rd0edm^HZksJ|SKz`Ecngr303&
z>gu!XZQqBb_w#zn7Th;AUL~)0C$2xTs>AH
zdpxcCJ)-rIXQsvvR(o1%L?FJqLd0_f5mO+(q9D++yRc8oih@#n=e7ik0?^Ckop`>^
z#&%rSiBHHKh{xyTv{1ij$A)S#!pT_?hzCO>oS0`wYZ&F@C>P5W8&{qgXgO5oa_%|3
zv}HKz*yB01wZr4_w?mHIo`TlcHn+3)tX?f|6!&hKhjvAKcTUL95rfv;6}Ug<$#0zt
z+TOFKwy^9Q#h176`gYI{g1<+E?<;{0%=LTE>I**oT0Vzs+C7D>RgjbN1iu}WZTNsP
z^%5IvK;MPm9iCIbtEiP_R@4We-5MF0`#ZM@*;8`>!u;IqXN-bLf
zt}DdHcTphj)%BY^-xNioEPuf@te<%YdU*YBP@ero{T^BJ!kMs8O9H$ZOdi%+ombcCq@mo>XTN6aQALRS{@B!z%_2d-L-7S0n>(Z9JXL(v;
z#o>$FhleljI4gYdu5-f|?>;|#@t!f^i{F?KzWB`xLA(FtmR-w*v*)3_mN&mH;)B>P
z69RGS(iZ!hHA`$$g9UW~i9A>UhP
z4LHZh|IW`K+mn~JP=4yD9dffz-kIQTsf6x*;StVT=YPD+9VegM(&5Q%rQEv<^I%Vd
zT6P`zc)1IXPwwp>Bll{bIODkG-gFFd??oBvJK*Y^0N=LV_kH+C5om+IoAU$Z@3#A%
zm>T!K7V3<8Dt3nWX!g(<4u7kD`UM}s_wIML($LG
z69e%O#!sfPnyC)@I>|qBMuI=be!#8xlQ-=%GynV)UyzAEWy(Z9vgB#y=e3@sKEr1^
z)Yk{lmCqY4KN-4-<%^96PZRM87@L%9G-&jDl&1=M35L&d)?p5aeZv($uzr9O9Zdgh
z<;Tu)zKQ%uZxN3TO3MqJ?cB$_ej<*C>7-s4Ionyqyn!O_%}dJ@=Qt~vH(102C#U6w
z&T;N!UV(^*2Bzgj&vCxUyi-Lyd{SDT@H=(PJ6*&ha*nau5b`^-m{%g=(f(=IMf^@B
z^Uf6UgRr4!Ke3VX680!ouW_I2&|mL@KB%jlMWyke_gpzQEkt@#@LVShTiFA>@|s<^
zp5>)pL+3hs{Fo1M4RvkGsX0THOZLIJ&I**-MrrTnPw?ZzLaMO
z#;Z^Jf&)Wf-?)x`ivD0Z%26ijlk?TzV*GOaQ(pSWnt=h?@2tPFL}38PKQ7sJ#nYDG$jI78HkS#!InDw@CS25TkZp;@!>A~1~vxH+;ORTBR;;1l7$*Hj>_QPwijAZx+Z)!(ey1-#FelvRTN=3k(3f&s
z4lM86(%IFo<(vJ)xcG9n6RQifMNSgqwxZqbz`ilhX|2Iw%4UhDcY;pLyG7evI8Tf-
z^6$D<$++jjK)hNEeQ7J&TP=F6+A|qzhRvnWYaqT!3|+;xg&`k(>Q=Owy2(Ml(Mf{e
zLjl>Yt>bb1M%8ZSvD~iv18ux+>jlZY*iQm&YtT>az}&Q1gCDmH1b(#eHnH;I#!C~p
zo4O)T>2m_-CFXkigs=-^8~Mq(epqW_Tub^8=m@&k<66MC{S9MycLCNv&?j}+F&uh@
z9@%cLjoP*DBJX#NO{v?$8PXpe!T6N26}7fwe11lcPiafw!+IkapIpzy`drb%^=IjO
zUDIE)`pymC%zkIzdEe{WM7{HR_n1H&YpwE}N0WO0GWZ-#9&fxWp^GZ%=g{uR`3XPw
z^v7L)unYdRP}=4ewC81%4;ERvNBb!&+a5iTp|0q8iMoJm3WJBuI^gFCzs|7?f4QB0
zvI@4~h24~5Ot+t}D^)3^D6vCKM}
zvgCO=$MITT*ItNqtzJLA2f2nN&@hOfr
zZ~xY|fzB$-Z^X9;foFrmfyIgkoO31cY&G%hjSA1QZ-`eP1eQJE>Fo~y&*HUQ8>@gJ
zsB6YgB|g-+lj{cJ%W91=nAd?ATQ%}S=+_R!_2jz$mOxt>=FBl-=v4=+!)-&O<5o2y
zmfNwbPfL55+d24$+?E*pPRA~nv%jlv3$Nu|Sqdye`v~+E<5V4|v<87=SWgY+`8_9J
z-tvg>Top!{#@>kGLXK~vt-DqcD=IA8d~OmiGN0G(TCVb&&rjyX9?@9x##Ai1u75Tx
z`I3rDX`a$H)39U!GAX%L+=w`m^|O7{DSs22rDMqu@+f~0cqED06rLo0=8rsHO8?HurO{La85S5h~omh>6K%|YTe
z@OkcIt_c9|Caxi_#5F&|H7|A=oR{4HAjS*9e~28$)`6#9hu=e8w2yxmo|=hzsjHju
z{c(6o>R9~e;*jZm$hqS`9f$1NmiN0OON6~gv;phl?d!DfxDowZ0{_xc=tdkUyyIZ=
zSjUIhBr&+O0r-bK2cf%x?l`f~YgKs;<<5mZ4OU@)2fgDYp27=n;~~Tow+sxzrin$6
z7ak|^lmLDX1Ba#L#l}fIB~Av$0S-&a3yha|N+3=XMcpZR(eW}4iC444W58M|d4UNs
z4k-{<73q1=2{H~TibdQDES7rRL>Y$^!x0w(7E8&COq6j*fi*}7{*!sHLKlfPib>9A
zfw{uKGa=xvlxstioO0#?!^E(5Ny&>$a;{RgW5*d&KBbet>A|2Fy%ac75EhnQy%`ZXEHD`=g9YgR{RYGJcIg+^}5N-+qA=yUSixX
zwNJWTgz_BMCj;?P=*BfN5O?9*_z_*gr;D;)V}AeOgPtACwrQ-P603
z7z7vsd&3?-V`PUsz5D_AYQ!b{N=JI{6xWd69u(J2UUXW+vH@axaX>5xd&Kk@CZkvhl=%J#9Z0tdP_
zrSQ>q$NFXd{-n)OzhBclC@?_a&d`@~Fe+hK1N!gcnKd#Vyb=3A@Tqf%AqHW;wBKXVb3-des
zDGWP`H3r)L<`#2a?ybhAIVWM_(2RAm4QibL|EJdp#UAl)4)%frxnlYx$kBC+Xv5hJ
zzbksdD$lkHS9RR)a?SwOWnV{77uFhntUJbjfOUhM6EHs3fv-7JQQD7ZDhmARW7+Pz
zWmr3qAMIut@Gi!u|IhAW{S7VTQSJG5A8}o=7&hlC@velwsYHCj=M^dbCV;t8jeWD{
zgZ4#*gR>gtslGpTPTP(?qTX1?tsitve-mk|pSRn4G_+#@zn0>^LoQVZ+W!WwWqm)#
zw@*@;hK^*^#HI7*MF{Or$VRh
z*7b~a%e|3QdpUfrh||{}hW#140frg34r^u16Mn{m*&jhZNwFwByEG&Eyo^A2-lKNBu9+h58Dushi6APmzvmrdmq}yknXmDb)fO}te17l
zIWVoCqwpi!kRLSj3i_C3O&=?n*~gGsua6jyUxrwQX`jWvTmMFZ=@Ps{ZijX4#r25E
zlruhlv}?J>O4
zv^^O=FA8Ripa1OrWc(a@+s<+13y=M`#Lurz*kRA&--jR**K7Om4ZR-q+z4g1V{x?S
zf9JUo$v`MlwBd(DRR+G^@#tM>EL(e5((bgbFv)7N0l#=ctuo$+i1@f-G~
z)+(EXPm6KyYG~_s7!w92O3=L?NnZ;(;xQZP8yLG_S(XWgJPu>*U+4NH=#H;-4|cfr
zNhuTYIG6+1#&G^5jB%cl7xOq_=HdKF1eiP}FOcIbX5K)o?SS7?^1?Yz1M>!pcns%&
zQu3lX4(8{zF~rLS@=RNl%)Oi%@
zX?3~ue2BfIHE1Jg9Q!P{4Dmsx`I~X&DEgos>lxcW`Ff?UXUtlL<=@8`rVcMce;iC%
ze=%-u&QTO{{k5IzuTxQOz4T>6>B~~=oBbL>eB}}3cgXb{_Ya3A{1~rG=t}!B%D{Pn
zGTZ{*c082)i2F;Rui?PNoELb0i|cw~)ezcDyQ>15BJR9J+oi=yN&q%
zf6?EipOceg*4ta|DICBYM_+g;bb~Vr8)*}_;&-CY>7$~(okuX{X@@sN2RnfoZW%{P
zDI@wg-$EJq85u`P$@BJc?q?o+6yr!KdEq|Jz08A;VjL+YFWSesn|bh2j3b4lEe>rh
z!A-)1m-G|HpXWNypPuBMRfZU3DCu4GyGIJ#+Ot)
zh2tw2%~f({-0w@2+aA>Web1k#_(eyn{k){VP-oMspbP3W-A78l0Zdgb`vY-nGqyv$
zGx`miFMUYzoY~VYKKMX`r{EX;^pEt1A9!;7Vj$L1=+}dYg&cSf^DgE-+8})@?8Kjg
zIpy*h!8qb<{#{ttbl@7?Z@90|z;O8s-o6gc6+u=%+wiKjKSqCT&fkrJtv@_ijEbNw
zVVtpjxSts11IBi1UrE2~LkxvJjrDplzj1CbbAXIxOjHJStn(bePqQ4C~NePDz`P^H=jm~#?wlDD?NLlXE)CN
ztGE=~!nF*qJqTS^FDi?7Voef4+e4U}hC}zR=!8}5{|--)zdFSDbAgOYGRC6jq~zF<
zbJ86uo^l)3jU2yS&@bBsoWS*ll3TZ*e7)A0;jLKzuuQeqFYVFz2V+y}zJTXa&e)~r
zMLiYr9HE-~R9~{Ls9qPyJ_PSQ2|cNG9rZ#zam{uCec!ghPBqS@n7I8`tmmk&_u)gS
zqX1&-91|)QmfyOy1lOaU>O0i=SC)y5!9JgE$5x!}k?mVuD%WTLno@^s<5sNOn73T#
zA&!>JlV{wJ2fpOV7#ejiuN$qdTY>As$TR&S^Rzx!SZCv?Q=W@!m-~a5pLOhkF^Pzd
zi?<`TMp;ZA_b-W6cm}R$+oV<7fcJ22eL;Jlhfm$?7V)3=6K%A~_3-Jwrqi$gS&3+)
zoMFT!igUz*5aJod`C=6A)hG|UQ{t5y(%1FKPxzVjN3jPp-msSJzJ`@&*i>wau@2Uo
zshnBoN}Zs+u$K)!Yj0B8r{(u(H`ELHWwOzu$z%f(g6lI@ycE!OX}@eZc|%a%QN)Ta7;LP1JAo1
zGfo!$75+UsP6qxzCY+3WUDZ7yJ;2E+V}d>)V$B!m3&j7pzV0bb#{C%Z9R?@w2Tmq$
z;^XsSgTQ4t13AP`{`848_XguZ;9n1LF#9$F?8|zU&ocK=2x1e7jT>mw@FmXz2NRpY
zuLGOqtNG8ureXN=f!HF4;lwp7!~@_m?Xco<0zQK_?=7`mHnacMG}uv59!x%eyM_7
ziSlW4Y&Z?W^4``XOP-SZ%?7`6jju86>Km%_yJ1-BFEB*4iFlT4N@7>ySlXG5U%8*F
z@GHt>#jnUq!>_=T8oweh1Aa~L)cCbp<6h!dw!a;Fe{$?-d@A>q5${xa-SH{0D{<)(
z;M0eINojWv0+(VOl{W*IuGN^7xD~O7v2(HiQJjZ$;2?Yt5Tn*1-k73yk-WE&b7VyC
zn^ITJ8EpXZ0E`359Dsf?7G%th)x8cwS|@sn`iCKVA|L&ogY$!I({f-<
z4=^b6Ij#&%@DW?v@;D`V|AFo`bWcO#=krReykh!pKJU)v^#!=z26O4
zhodc&P1+FfGw!!Z!_Pxyn`FL)pTinIW6WQ5Onqmq!8vw9?`3Zju>)qwY9kCsr9?uK^P8%CnBvJs?5g*`W6%iFf5$N5qVc
ze}WvVk-yrG3&WvF--NjBYU16`;(Ge540v}nEEl
zT|N^-#)UT!bN0g8%<#@C1LDHH5c5ngr~F(pw#Ie_bzHa?&RH6Ms+_7<`ZDTE$Ax=A
z7pdja_-VY`3+FFcPVij9aJ3Jn=4W+IDixzV|3vi@+$;CUdxz=kO}@GRi$0FBz;;&K
zYes1w*lSa~WgHp%T)@Y%{u$%Q#_#`G`%8JBvW1INAUr{9V@k(F^;zi;W8!s|8srnaX5aVM18f=#m?<~<6B2&Cm<4hCpWNb3QQ{zm-
zlXx+ar}ves5$_Dao@s}SWe&qV!P3q&mQ?g2`8*z*&N!zzgB1ZDB%XZOqvD*5dotF^
zK4QGnx2X7f#yd@{6Bts(IrD(Eh%JG+V)#xO3l`4fSO_Q971lY@46$!B4s0CAahHtg
zDV%3;mtD@riz%|CVX=_4Zao$(#`9Uq52K$E7q@XFeF^>2O|YkKu}s9SjvC8^|1nrH
zatv6km~qT#pfl=095WD<-}DnMeSfL%#Uy@W4AI07qp7jX)g}&YpJDcXZ2xF{sn@^q
zIR(IY^oJbV55B|a4(VqD=r!^EpmF>e`E>j+d0*~x`dks>hc5WKu#O+Ddpjk52)~JQ
zMVNn098lhudjj#paqsjzez+Slas0oI?_dF4||5E
zVmKEKKaV(P5>NGk-pfEs$5Wq&PT2;mRW~-{cM4VtpCs`VZD?Ys(DAHv|I%6L>0ZpFJy{B90@BgkuxJk(lNpASSkxf(pZZ3s{C)oNZs)fVurv(^l1kW
zL-fLqyx7O{Xl&Hs8S00RDX)G|pv^$(Fi5V`Gy{7-k*fQz`XX{V(f7|9%|KL0oS^$!v0yHFpQzmzA{nEUbH)CyO}zg!9SG`1Wpf1%_O3Ix?~iu*6{>uoz*&=}eC2C)`IV}CsK8m7rF{EN
zyZpV{9t)g1vXrma&z@f5k4pMwO2iI5kgi
z-;ESH!?KjG{H0ai`1NR^lb4}9+9dY?OJS2f*e&gobDzPGoKty5yb6AbbmDH7S&O+V
zI9<($Va$gt7uILO$HU+4tQz5zfoBKrIg1s_XJ{MWhqD23FYd|VI+A5L&sK{fS%&3J
zIl*VP=y_Yp7e$>K+j6gi_t+_Xq3+8kFJ4>iJ;hnEA|U-J;}}uwJ+m&Zh3Shqp6HK*
zfm2d^GVy&T|FH#Ysm;(qHF)5ht=vP9^<1vjWyBJ|+0#`%W#RK>@a$e-gz9>A7McFu
z@Fs2`4p|S3;3c-l_3Ln!%(QhK>M^?IH4&5}KZ9L}uibopDZWW(8Iwm|rB9{#BQeY&
z7BRZBWmyj#VqfdC&g>Dp7&+L#)HUm)o=YUo8|Lu2=+yN(@Zr1`0hTfCwE3>v)Fsas
zm+j5$!`5w7`H#GrCI2VMJ#=$coN|=n6JURaMmfINDUOJo=B&p#o0CwFJf{db`J8E<
z)vMhP#Z~A)N
zJQM$f>kE00qrRUeo~=R~|6n+tcZ746SPS^M#^=4+So{0sIm>I+IZK{%TZF#F*#PPo
zdxrfS7h)eguYTVfGJYmw8*k$`$99#}jefR*enuhtinisZe~J`2#&|me0_Dj^_UBmTtK@yy*QVZw&9P^2x4IYMSMoe(ay+XwGWXR&;QcSG;eH1>
z`5ab0|J$4cgMXCwLM%u7xlUm$vkGgM4m@X->&IbeKhHfGnM$p-2*(}vcKsbVi^?@P
zZjxRb!@YSkA-_O--DvNgs{(DV{4&{|T|=%sCzun6zkqf}Ars@ge1<5W+rNz0=;!t?
zL)%i%T&idGUjbRDBkIN{0&N!~y$|hrNVe-IiFTPYOR}HQRnGKZ
zzn1-o^WZ!7qiq9po0&H4?m~NcKlo~P?Ti!GwHnP=aTm$
z$>&MS@s;Acu}`Su4e~4(>&(;_5z`k|oK)+-mHmzN)Y!4`!_=P{3sN3kp5x-Sk7=v0
z_RAA(jUJ_~ns;hDQ~UXS++Ri8Gh+|2VR`0*<4xIMj-TT!gmFfEv(Y_r0@jQE7tx2b
zr^z|oA6I?%C(x*$q%Tu@VrDEcMw{LTQBMW@XFlw;&|j={<9{1t@VIPe8^Y(H^z~vTmgRqW>EMc|&;CjFLBbKdq6G`*!yE
zhC-|F*kApT=6QR&Jlmt>`jMQs^nUv4Q*hp;berTZZ+j;}R`mcFtO*RlMYx_ks@Y&frRpQQuiNBO{^{+}S<`%%}p
z_s3xGANcoScfs53z^86Jb1p_~iswj}JxBKYVche56WYi;mMO(2I7`XRad{)^oy6y1
z+y)GZJTq^Uo7U~a+&I$H&qt8h2#C+4A=hL@7!SP(Uhx6c%dp2(C
zx{up+8OCh@&*L=XwjDCrcg|*vya!`bQ`Z*z~`YOsrF^t
zmKn>NVPmvA-s}GXo(G?GECJSEqUvK`shtp0g@>doq>MDjgDS^QMCalFrWJoQ=cHOf+7Hotg$^+l5J
z{K!(@akVddKPKO2V%KMU($W3Y$M<7~dbbmg1jWb`wV&-r>(8w9MvuQCdQ}4y+OB&w^gRvm3GHU_dyP(J>D9e9au1gSB)i;-Hn7
zH_LKLH$N;qbCz`_`@micR~nsJ@j4gcBm#Z!!JZMHPrDmf-ZetSSgY<;&ub=@c0{7-YH{gSNW=2RJ$8Iml=ibv^D#5dxoj~43
zv|;H-qQ$KHI5uf-jDKyRUEx}uvkQ(bOWfC@=REm55ZXu;_63bi1kg_6r-+4-sR!w=
z!gxOTM71YZh5pWfj|VGTRPP)%c25~tkBQ0M5Qgn?zXQI5>%`KLW$~>TQ)a)9^=GoT
zbCTz7B9nLo-j0{3uI4)%||hBNDJ6=O-pVhnx2z~&uY)t`Jx#;o=8?s+y##@sM=
z80U&3?!kD*^N81T@Ae?p-^&qqTaR)b6LJ&xYe#YJ^byF#_VAqzY?HyA+&>6GHpakk
z&&0-lF%?q`BmQ&&^m^0s3ml%oJdD^@UyR3LSMyn;Lt0<^Ti06Jn>iCX4c7s?j%6F4
z$8W@&@vO`I*4y!$_t7%WMH$roDW37C_ghF)`>uQ!Mv~5c+Q~hS375yWpp1!KwPRgP
z-SBzq_C2D|)hgN+&Q#$2H^W-@V13Q_#BRKIneUBYoai8A7CCs9Zs+6$FXD_M>4V$5
z*79CiB?n`1#L}s9@Y$*FqaFFcXK0iE7x+z?5ocix&(HEc=*lN9aAqQp^T2w@OnOl7
zO+~?j`ll`@q0E51dsP3@59R7$-(w%RZi5B>Q+b
z-uuTsq+NOuf2c;?v=eh5x9Mw^p}h#~&lkYvcXphDzATXal?$0Bq76JhTlB~UlK#Oi
zm-7PX)ajd^Q~cED8|YK4*U@&qf4V^GbshAIJsp22w{t@Ahm#k)jAsrp&pk0*URo3`
zzZ*RGY~x67=lJ#bT?jpP4HgS_pL*TxI8%(Wh5lKfQ-?~YYCnZ`=8IhD3__k7&uUN0
z=#=eLYZdk}&Ng7~H+H4+7-LrVvKu;W274d-3Zd)RIZ=aJj
zHg>dn$)C^4m9o<&Vi!2=(2sn^GTTF%_d@5-puHr^6|^ou`vUzsCy0}qcYILoT_trV
zR_x&U@VihhJ_ma?xEJag%x6VnMfv3Zfdz~im%Q@esd(R>KZtkXY(%*T;%<{X;wqMX
z2Uw*y#_MI^4ZqR$Fz<~5ZH0%ireS{AL^=5Hn2+lMxt%}5IEEb$X?+1{u0zw?X!Ms&
zuRra>XWI^wG2^?zgL+`xH0H_i@5VE)_JPLx7IqD}<{X@Tm;EJmf#|@H1
zt$#mGhM26c(EoezBX91@(VkeJFLIIeThwPJTjIL+F!BoI7%%WYm*7#5%5l5K*GHhY;C)GO$Cg0&&umv=!PpqHbczB9@fBEjUm*PF(?v~w!IM?&n
zuC;PKiai9j->gT83)I}K-ec~2+I8J<=x#FZsjH4&;;25F?D$45{}y8bG98^P@lhYG
zBOl5opBMV`mI_?U`BeHW=zxByhQ1tg>fP{ZdvG5IW72!D7lw21vMm+7XAkc&s=#v+
zT=?F#OE@cFqi(FVefZvUp=f&uZ3y5!pu}aeEqJzo-4>UAr>40VwgNW44!&sSQ(rE>
z1!Ls00qYy~-Vq+0w4kCnfE_`$K6*wj&g5cUH{myvmbhV?~+$u
z5WQY{EVuWn$M)U%${yT@ayw)YA9jr;F0MoShe~@H>Ys?;*njl%+(HTa9eyN;=a}>S
zEOGFw&|M}u)*q)FGayIwppxSnOOAp}as*t?3@yixAqQncKkoB|-Og6<`2lE{cl<`@
z9a`U$k;nIevaMsSI*x$et*yZClXV^61V3JLmUYd(OxNJK<)igAcrK~DrUB)Ofe)AB
zo``vv>&-cFHUD|=EFRo1FYlXo`8f{~w=Cp)x-iBS-YdX<miH>+9Suw8q^J(=8v4*IU$Vk
z+SSz?dzaYjdPOYeeC`1b=<33|$$(e7y7+Fg{8sKicH!M*U3hnHS6BZQJfA+^^@?cg
z`kres=YHx+V9$eloqy^lxO
z$TJ%LttW-czW})q-x{?HeuMq=L!<@9KrzSK5P8mpzFn5RUYCtQ@8Q9n6AB>rao1p(@5jM!`OS=3GDI;?tdcAd=S^Vz;`nFs=X@zzaSgy_F@ev&xv54WF7b!Z0M5n
zcR_N`TkmTgS)%qxmRmeaAwS!FpO%q4d!b%AzIA)r`=++%LR~-G11vLPCv>=FJo~Gl
z^$z4kvFF6LyKs(k7}DX+uJJR$pZXbzZ`u*wDIwcUz0;TSS!2FXUuO!+bKd2i%x6*7
z%tIyk&AoH&SI}2{|E5QtpJjRYfKAlv7>{Vsi;Q6wtI>K|@sH4MwR)GlF^uhBn
za-DzbYpQS!?fxc|r`_7|A--d~QmiPi5Ux2(?-DJ)gl)*@8^DLj{bI}so!}F~nwslQ
z`ty)mjbl5u5ef6Wy#4SeE+Z^+9ichig6PL_ROzxj(Bo`+I*;-vdUhu-1R>mLto#
z_nXu50j@t--Ak_ZxIWl_J)eo{@jpjD1KkB9!T55}cYQri#-;LI4j=n$YVDSht}~N0tQf%!)Fs-KM~QUfNpYr_I1(5ebG|ACS`Sp0tU8Qy
z3h_C;TW$}Hb>d}scfgpE#X~XH*?u?HHe9FW!7ke6T5+hP4M6%~%o!&ESFPzSu8Uoz
z)~+2`KhHzmxM%O(cF+!?{t`qydp3u0Dl>A2PRQo7v_;j@4%$!hh5`r-Uo0!
zd0&lu^PU~d3FW7O&UHZSdx5qbuNWNZpF1HWyyM^aS)grZZ}^tOlgIDD`@~~k541gt
z`SH=b!0my$>PV@fEzq&Ryfj8Um
zCgg-Hma5Y%u)fFe+wdRv58Ss09-KoGb%aiGRvP)=>Du@?H
z_4@CSr}RSq%V&~U*Wwq)r*(XRcsOk@0aOwawe{HDr8Xp?`fPf58uanHCEZ)fpfgd
zr>M9W&-w;m4kA_vf1>xgN+BzK3g;Ep!}gVG9}&aeCH)5Fm+iKE1k=P3LA_TU8v{Fb
zcaD!u2(=im%H)*bjfcC~tUS-LM|`yz+CWLxca
zzJPje1y5rOWAGc8ls;F^n=Bg)o$f6BP{-Kt47N60vk9^uS@IKk4n|^H>Em4v6pwcm
z!N2LVYrsSCc3?Ue+8qTJll=zV{=xMwCw8M~BYwG3*N^9{FIa{(qB`RwV_N>(P!BOB
z@XLfEo(sb{TbFe%tR3YcBjTRw8Ll$+F`Vah)LC2DoBt#5NNp3|0UJHt37}0}!v}C?
zvP7P50bScB$JHIU&e$De{L=0?zoMXeC~jHV=EF&3_U$AL=DG;S5ci=fQ6Q
zYw@1DNJABP)S^z#dBW2@zZvDi%oSzy6
zdA-0F%Kp_DGI4X(W6sVRTGYe!#?M=prmnZ8|ENS;yD*k;Cd)qp?cI$vcMjU?#e2y<
ziGE&+^)}buOR?Ur#5(J4=xaB|$S(9nV$F@Qa}Q(=Va-h)h&uxP#T~981+cTF#O2Uk
zFRWkXb-RSaH8scB9$eSeixbCn_m(+p3^d|>DwW%!dTDCb51MZUugH#A8o-0`*{Gm;ac`&)JGd6J%aZH
zQ}0}-F>b;3A%5vMZS2r;ews4IvBm|@CHT$t{4(j+hWf{YZxFN+)WICk1r@wyq8$$c#w`_iY3!?i=8m!O1`mX*nLZJt5r1`h2h6Z<1?im)yf(to#|tljmrY
zXYIMC_x|hV+)DO#b>Ujmu64RyrLc>Ob=w5igqhm*uD;H0+h!?8THB&N=g^^}x6R;u
zvj@Qbqfh(*n27$0^8wz;0iTz|^qm;f?VcQ`17*y%VE71rqnv;Pek_N$21DZc6q65pvr
zzQDzPVB`g#!dc_ufLQQ-lnbE_;9dXj<;2pbwD$6r0izbc{)fnSDpo36Nvt_2Kk-h2
zuJHul#jyuGobjVx1|gBFumWWKm5rl`dMbG9n~Av`E|;`dl8jw4rwic9)a!TeLmIw
zPg%v7)1*FXPmI{6CQr1n{S}^5=FA1YKZuwN$50KfBQD^)OdRb-j7se_75N{4j2ug}
z;V5DYfj*rRwjplC7}x;f8}#39l)-ZxC9Z})+TcQLDd;V6_`Z~J$di4IaDxFyS39KZZ??P=Od0f#ZMW{K9Y$Sn&-xuF~qp!Nmv^z
z{#=)Uf7%$jrcjL`$WwmrP!)IMJV=}6`%PhggLobB%!tCdxrFHIScJf(lLhm;Cwx3F^n%WeqyU~TMoV1wB_Gi?Oo^oOhQyy!CVyCq%0
zSC(SGY989fd$$II=iO)<=kCa0(Z>5!c3}<3c0Pu-v7HBd3nzjxmeMDBU9r?tIA
z1fS_&h<&vD)^&peZL~-BC$Q@)I0ebKf!f%0KV4Xk0M;pJyCD}&he_RlGoV%q}ss!F!Xz(4Fz+)`VRcSOXF9G
zyg-}x!>$P%pi5$R`iWA^Rnx#ffl;|G|81b{zpOHxy9#o|pukIGR-vB3JFB4zhU9ajuFhy-^>}f%mufc5S=_JecO#cosCo{NX<&
z4%?TkQNm{94qt75Y)JDXh&kCZJDh
z9Q^Q22T@OK!nBk<@?|mxG{jH&GWj;+*Pa0H1h&FGOzpr8YHl9tUyMBV742F3g$3tg
zEfL1LWQ*)C;omLGpW;75_7(4~ERq<3`HZ)l@yqsv(Vhfd_ZR01wvjp+iu!(yH2aS-
zasF8PUDpDR5zI0ED?qn!DeMor2%qDu>Qwq7pB$8V>I3zRC#D6`jMcvYyCgrZ!%Qp%
zG1?6WhPa$9x}S)r)fsV|&j5~#VJ!y#wxesRTC;y1yo|24$Thq42mZ?CU)e!>p{}rI
zpB}+}7J0^|2HHB#N?pgg%sQ6)Qu5trYQ1!s=FN7VjNfc$GB#Bx=lx*p9A{c5^MG}*
ze(#VvE=<@1_jd2q?M$~zjaTLSyu0C5ZI=?S!Y(_2OU+)Gif!chPlru%4bL%0y|$vB
zW@(eD>uZTqVTHv^X=b+@oM60?|X9me8-ou3u4Cn
z=-5?J_zA{mUjq+g6FzMd#G0^8e+%nEy|fFCpBUn2?B7J(#(MW1?NjoBy&x&mnK{#r%nL
z>^v7~VjpaO7_pBU+4sCZR^407=ZkYrSiy6SIP(}rxpu6Nywg3nR^3Y$^)L?dOpD87
zzqf^BExcpvP8Bl^;qPeO|t|E(AH*N3H>ZbCzOX=~m3Oq2CN&RgF2Bakc8+c#bi!IOQ3d
z<2r}<(8Qk*f7wyG-M4BjWa$JC%+ni*i5QR0@gT+v|HS%egO6=4jjzD-kaMvAw_p26
zPoF>=V{XJMEQ5Bo$$pA{(rH8+pVw{N2Yk)^gx%=a5z$Q-o{P%-rbW>H*QkQ)l@$-;*mAFCQQlSIj!2(}nsd
zFL+<)$`NhmULdv=XAI@Ilx_8%C)@g!M1SVDvc0DNlW~QoTRL<**)Fyc3dHZw2<4UbVhrbI0q$H`D$wzc2oq+cl@L1ZQlvmfenLRZV}`
z?U}O<@j%MLHJCiRXvq>mZ2vLjaW9kiK*E2(cDPrTjFYd^?J#pQ*H~UWpC>^#SPIvV
zM89yJA|KQKpvJ#i2Fc%Gl1zHo4?g6<_EHwsW7pli-XZi+I|iZd>3EhU4oP4iNwt-{
zeB6u0Jr75qSISP_YJFVTnxp0Ywsj55lbdKqvaVt&MN`k&KTx$mZAR4^Nzy3`8*?Y
zFNit|&V0!uI@-AtGV>ZU4;Y_e$Ka1FX`_FXveIWXMiB=XgZJkmURQ~073Owhuew(Y
z*Fv24*A?NN<2CyJ{1P#4xrcS|-19JwqaAz(1p9|RU--mE%wOevU+Ax2)$yG|t}lz^
z`KOV(&+wefv3$4CGVrQA4f8v6L%SHUA;^8Aa`(-&onfuqN2Bie?3HGe<@-cqr{TOd
z+Q4T9xFA2v`5-5CN7<6+vWF$k>2SXI3*;#{jlQX%mHn%1Lp`5G?itOQBHx{4-n+RB
z<+&CKe}dQKw|*AaFg_UZig7C_3uJbq50_mDnIHq@Ujg~?jGgjej(XM?EqGh;U+W2fv|RA^5n
zT5N3;A%&0@NlKeamMra?5+(1s?wOI6@AvmS|L6I?@9Xm!_uSWd_H(XtUH3gDtY<-x
z{2@6+vEFCMHKOspf&BLY?a^GU{0O1}BUb@^nRW+_Plw!s0r)6e*b_;TMshQ|qy}uc
zR5ZPc1bCo+-f;d8)vbni7s)_*ADq9|2Aml3B2nVaVQ{|>&g^~zSkU}E>X7SzGYaX@
zw-<1Ij?P=Tn~XMV7?0Kitr71q+GqWIe<=?3^;zWzCRQBEpux-_C8(DKy)nSe3PLa?
zewc7E0l7!xI0Nli`hesK?J+=Z5u+=r_Xa&gI#mzvTdo72_6B?!gWuv9-B24=UIWn+
z(XE$5ToK{Ju=^4vt03p}Z?yE{m=#2X{3nCXD5oj`zjF*a$fPwY_X0bK&a)#OK=O}Z
z#bh7Or%!;~3o+&1duXBpwMS#Hfv^7k{R`;b?hIXG{gwm3&wLJqF+cKEQ}~R=0{O2O
zV8}npJ4u5Ma4>a%_#bqD0B|pZII0HVLw5A1%>(^O)W{Bja+WOQ0j<&aXg;Ka2p;d}
z6U$8C+#AB1agG6a0=OgH;D){{`_Z!#41M}NZ^+Uq7Cn%yLF2RD`>_CE!{%f5|EoR%
z3@mvvf%a%DY>X+q`3gA*)*Qdn9Oa_%FN`Y#xU=v^G(c?-
z{_mhpQk7xLk|1u9X38^benV)3%?-XA!PW-tk&bKw9MpjyZWuqrf8~eavTVL7{6Mmg
z=0pAKpg*!p==()9H|B>b;Y=mtJyFPaBRf6@Hj)(&n!>(0^GZ|p-rzj;~iND
z2Q*$D#AC=OAYU;1F1CJOmjrpwkw&&`39xCf`JDX~VlVW(0teWzPX>mg^y#GA4)NO9MW?)&~Y>$(rd=q{aFnC2Ky4fK8skSX*|nqwd*
zNLCr+&nf|%GO52KU_;$tjd29ppg5`ru2bzP9-ify19~^opqG6V8wcfM&~p_(pOZO=
z@C5oLLj5P0a~F(h2tM{alq26-4p)tdiHb_tOF{E6_C8?$4{$+zMRbGxXzV#9lqX{7
z4~);0DMlZ3E^rp+%UE^<`DP^JtI&A{bH+PxSor}W#IwkLBOPMp-N)muWBbT>pe5|(
z-sZq*X76H`%@zY*AQ~Zh4THa6oSR_an8G}xN&+0va~wGK#5==xknSVerLxTP~65E9~)cLa8w7{
zPaSL7T@(*9ekT*bISjN%ZIdCF#n3A@MI+Fo9(XnaVwO8dZ{|R3`UP@EVw`z*mIACK
z;EfOb9soK&%ak3(2{_NmjqruD2(Zp@LGN8cYfW$XeHnOv{uFTajE~u1-)54ZK*tQIo0$l*h~|O!Q^_G(;AXy>nYR<(Pc^f|8tS`2
z2O;S+nz#V*m$BAot(nn1vO%rTj>X?U_$dMT0;u9wWNlf@Bc@E=D_J4xXQbe8)i}@>S-4_)2IsA&>KU@hEIni8GwLB{@fq0{$Y&;k
z-_L{oNY8LH67S>yt-Pgi;Jd)D!gH)RICGBn-@WZ{)re+DE_<{RtEGlF+--rjsGT?D
zDxL#=2ya9OZ=jbCj6sGzi+~1@?99_qT()6{Id*Qd7iG$<4%4N|37UbuVUNhE~jI$^#zXof<>fxho*&JZgSkIdxoChEt>IYs*1MDDs
z?^E$w*@!*}Z$?fW))mb6$Re8Jf&Zuu*(x?@FV(Fz`3ydajlf4>`?NsUQg3L7o_*(l
zcBDk@U*2WS(q#%Z4|?90@y |