This is an automated email from the ASF dual-hosted git repository.

raulcd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 1369f7fa69 GH-45747: [C++] Remove deprecated ObjectType and 
FileStatistics, refactor hdfs code (#45998)
1369f7fa69 is described below

commit 1369f7fa69a7527c83c817ac0589d40bbc4f582f
Author: Alenka Frim <[email protected]>
AuthorDate: Tue Sep 29 08:44:29 2026 +0200

    GH-45747: [C++] Remove deprecated ObjectType and FileStatistics, refactor 
hdfs code (#45998)
    
    ### Rationale for this change
    `ObjectType` and `FileStatistics` in io/hdfs.h have been deprecated for a 
while and can be removed.
    
    ### What changes are included in this PR?
    `ObjectType` and `FileStatistics` structs are removed and instead 
FileSystem API in `arrow::fs` is used. Together with this change, the hdfs 
connected code is moved from `cpp/src/arrow/io` to `cpp/src/arrow/filesystem` 
merging `FileSystem` and `HadoopFileSystem` classes from `arrow::io` into the 
public `HadoopFileSystem` class.
    
    ### Are these changes tested?
    Existing tests should pass.
    
    ### Are there any user-facing changes?
    Deprecated structs are removed and all hdfs related code is now a part of 
the filesystem module.
    
    * GitHub Issue: #45747
    
    Also closes: https://github.com/apache/arrow/issues/22457 (not sure about 
`io/interfaces.h`?)
    
    Lead-authored-by: AlenkaF <[email protected]>
    Co-authored-by: Alenka Frim <[email protected]>
    Co-authored-by: Copilot Autofix powered by AI 
<[email protected]>
    Signed-off-by: Raúl Cumplido <[email protected]>
---
 ci/scripts/integration_hdfs.sh                    |   2 -
 cpp/src/arrow/CMakeLists.txt                      |  13 +-
 cpp/src/arrow/filesystem/hdfs.cc                  | 407 ++++++++++--
 cpp/src/arrow/filesystem/hdfs.h                   |  27 +-
 cpp/src/arrow/{io => filesystem}/hdfs_internal.cc | 318 +++++++++-
 cpp/src/arrow/{io => filesystem}/hdfs_internal.h  | 102 ++-
 cpp/src/arrow/filesystem/hdfs_test.cc             | 128 +++-
 cpp/src/arrow/io/CMakeLists.txt                   |  20 -
 cpp/src/arrow/io/api.h                            |   1 -
 cpp/src/arrow/io/hdfs.cc                          | 716 ----------------------
 cpp/src/arrow/io/hdfs.h                           | 284 ---------
 cpp/src/arrow/io/hdfs_test.cc                     | 466 --------------
 cpp/src/arrow/io/meson.build                      |  13 -
 cpp/src/arrow/meson.build                         |   7 +-
 python/pyarrow/__init__.py                        |  14 +-
 python/pyarrow/_hdfs.pyx                          |  24 +-
 python/pyarrow/includes/libarrow.pxd              |  83 ---
 python/pyarrow/includes/libarrow_fs.pxd           |   8 +-
 python/pyarrow/io.pxi                             |  14 +-
 19 files changed, 943 insertions(+), 1704 deletions(-)

diff --git a/ci/scripts/integration_hdfs.sh b/ci/scripts/integration_hdfs.sh
index bb3a9b5140..54d5a678d7 100755
--- a/ci/scripts/integration_hdfs.sh
+++ b/ci/scripts/integration_hdfs.sh
@@ -46,11 +46,9 @@ function use_libhdfs_dir() {
 export ARROW_HDFS_TEST_LIBHDFS_REQUIRE=ON
 pushd "${build_dir}"
 
-debug/arrow-io-hdfs-test
 debug/arrow-hdfs-test
 
 use_libhdfs_dir
-debug/arrow-io-hdfs-test
 debug/arrow-hdfs-test
 use_hadoop_home
 
diff --git a/cpp/src/arrow/CMakeLists.txt b/cpp/src/arrow/CMakeLists.txt
index d2b3eac779..9f9ef7f262 100644
--- a/cpp/src/arrow/CMakeLists.txt
+++ b/cpp/src/arrow/CMakeLists.txt
@@ -457,19 +457,11 @@ arrow_add_object_library(ARROW_IO
                          io/caching.cc
                          io/compressed.cc
                          io/file.cc
-                         io/hdfs.cc
-                         io/hdfs_internal.cc
                          io/interfaces.cc
                          io/memory.cc
                          io/slow.cc
                          io/stdio.cc
                          io/transform.cc)
-foreach(ARROW_IO_TARGET ${ARROW_IO_TARGETS})
-  target_link_libraries(${ARROW_IO_TARGET} PRIVATE arrow::hadoop)
-  if(NOT MSVC)
-    target_link_libraries(${ARROW_IO_TARGET} PRIVATE ${CMAKE_DL_LIBS})
-  endif()
-endforeach()
 
 set(ARROW_MEMORY_POOL_SRCS memory_pool.cc)
 if(ARROW_JEMALLOC)
@@ -1007,7 +999,7 @@ if(ARROW_FILESYSTEM)
     endif()
   endif()
   if(ARROW_HDFS)
-    list(APPEND ARROW_FILESYSTEM_SRCS filesystem/hdfs.cc)
+    list(APPEND ARROW_FILESYSTEM_SRCS filesystem/hdfs.cc 
filesystem/hdfs_internal.cc)
   endif()
 
   arrow_add_object_library(ARROW_FILESYSTEM ${ARROW_FILESYSTEM_SRCS})
@@ -1025,6 +1017,9 @@ if(ARROW_FILESYSTEM)
   if(ARROW_HDFS)
     foreach(ARROW_FILESYSTEM_TARGET ${ARROW_FILESYSTEM_TARGETS})
       target_link_libraries(${ARROW_FILESYSTEM_TARGET} PRIVATE arrow::hadoop)
+      if(NOT MSVC)
+        target_link_libraries(${ARROW_FILESYSTEM_TARGET} PRIVATE 
${CMAKE_DL_LIBS})
+      endif()
     endforeach()
   endif()
   if(ARROW_S3)
diff --git a/cpp/src/arrow/filesystem/hdfs.cc b/cpp/src/arrow/filesystem/hdfs.cc
index adb8b0d50d..da0fbd5ec0 100644
--- a/cpp/src/arrow/filesystem/hdfs.cc
+++ b/cpp/src/arrow/filesystem/hdfs.cc
@@ -21,12 +21,11 @@
 #include <utility>
 
 #include "arrow/filesystem/hdfs.h"
+#include "arrow/filesystem/hdfs_internal.h"
 #include "arrow/filesystem/path_util.h"
 #include "arrow/filesystem/util_internal.h"
-#include "arrow/io/hdfs.h"
-#include "arrow/io/hdfs_internal.h"
+#include "arrow/result.h"
 #include "arrow/util/checked_cast.h"
-#include "arrow/util/io_util.h"
 #include "arrow/util/logging.h"
 #include "arrow/util/value_parsing.h"
 #include "arrow/util/windows_fixup.h"
@@ -39,28 +38,114 @@ using util::Uri;
 
 namespace fs {
 
+#define CHECK_FAILURE(RETURN_VALUE, WHAT)                       \
+  do {                                                          \
+    if (RETURN_VALUE == -1) {                                   \
+      return IOErrorFromErrno(errno, "HDFS ", WHAT, " failed"); \
+    }                                                           \
+  } while (0)
+
+static constexpr int kDefaultHdfsBufferSize = 1 << 16;
+
 using internal::GetAbstractPathParent;
 using internal::MakeAbstractPathRelative;
 using internal::RemoveLeadingSlash;
 
+// ----------------------------------------------------------------------
+// HDFS client
+
+// TODO(wesm): this could throw std::bad_alloc in the course of copying strings
+// into the path info object
+static void SetPathInfo(const hdfsFileInfo* input, HdfsPathInfo* out) {
+  out->kind = input->mKind == kObjectKindFile ? arrow::fs::FileType::File
+                                              : arrow::fs::FileType::Directory;
+  out->name = std::string(input->mName);
+  out->owner = std::string(input->mOwner);
+  out->group = std::string(input->mGroup);
+
+  out->last_access_time = static_cast<int32_t>(input->mLastAccess);
+  out->last_modified_time = static_cast<int32_t>(input->mLastMod);
+  out->size = static_cast<int64_t>(input->mSize);
+
+  out->replication = input->mReplication;
+  out->block_size = input->mBlockSize;
+
+  out->permissions = input->mPermissions;
+}
+
+namespace {
+
+Status GetPathInfoFailed(const std::string& path) {
+  return IOErrorFromErrno(errno, "Calling GetPathInfo for '", path, "' 
failed");
+}
+
+}  // namespace
+
 class HadoopFileSystem::Impl {
  public:
   Impl(HdfsOptions options, const io::IOContext& io_context)
-      : options_(std::move(options)), io_context_(io_context) {}
+      : options_(std::move(options)),
+        io_context_(io_context),
+        driver_(NULLPTR),
+        port_(0),
+        client_(NULLPTR) {}
 
   ~Impl() { ARROW_WARN_NOT_OK(Close(), "Failed to disconnect hdfs client"); }
 
   Status Init() {
-    io::internal::LibHdfsShim* driver_shim;
-    RETURN_NOT_OK(ConnectLibHdfs(&driver_shim));
-    RETURN_NOT_OK(io::HadoopFileSystem::Connect(&options_.connection_config, 
&client_));
+    RETURN_NOT_OK(internal::ConnectLibHdfs(&driver_));
+
+    if (!driver_) {
+      return Status::Invalid("Failed to initialize HDFS driver");
+    }
+
+    // connect to HDFS with the builder object
+    hdfsBuilder* builder = driver_->NewBuilder();
+    if (!options_.host().empty()) {
+      driver_->BuilderSetNameNode(builder, options_.host().c_str());
+    }
+    driver_->BuilderSetNameNodePort(builder, 
static_cast<tPort>(options_.port()));
+    if (!options_.user().empty()) {
+      driver_->BuilderSetUserName(builder, options_.user().c_str());
+    }
+    if (!options_.kerb_ticket().empty()) {
+      driver_->BuilderSetKerbTicketCachePath(builder, 
options_.kerb_ticket().c_str());
+    }
+
+    for (const auto& kv : options_.extra_conf()) {
+      int ret = driver_->BuilderConfSetStr(builder, kv.first.c_str(), 
kv.second.c_str());
+      CHECK_FAILURE(ret, "confsetstr");
+    }
+
+    driver_->BuilderSetForceNewInstance(builder);
+    client_ = driver_->BuilderConnect(builder);
+
+    if (client_ == nullptr) {
+      return Status::IOError("HDFS connection failed");
+    }
+
+    namenode_host_ = options_.host();
+    port_ = options_.port();
+    user_ = options_.user();
+    kerb_ticket_ = options_.kerb_ticket();
+
     return Status::OK();
   }
 
   Status Close() {
-    if (client_) {
-      RETURN_NOT_OK(client_->Disconnect());
+    if (client_ == nullptr) {
+      return Status::OK();  // already closed
     }
+
+    if (!driver_) {
+      return Status::Invalid("driver_ is null in Close()");
+    }
+
+    int ret = driver_->Disconnect(client_);
+    CHECK_FAILURE(ret, "hdfsFS::Disconnect");
+
+    client_ = nullptr;  // prevent double-disconnect
+
     return Status::OK();
   }
 
@@ -76,8 +161,8 @@ class HadoopFileSystem::Impl {
       return Status::Invalid("GetFileInfo must not be passed a URI, got: ", 
path);
     }
     FileInfo info;
-    io::HdfsPathInfo path_info;
-    auto status = client_->GetPathInfo(path, &path_info);
+    HdfsPathInfo path_info;
+    auto status = GetPathInfoStatus(path, &path_info);
     info.set_path(path);
     if (status.IsIOError()) {
       info.set_type(FileType::NotFound);
@@ -88,11 +173,18 @@ class HadoopFileSystem::Impl {
     return info;
   }
 
+  bool Exists(const std::string& path) {
+    // hdfsExists does not distinguish between RPC failure and the file not
+    // existing
+    int ret = driver_->Exists(client_, path.c_str());
+    return ret == 0;
+  }
+
   Status StatSelector(const std::string& wd, const std::string& path,
                       const FileSelector& select, int nesting_depth,
                       std::vector<FileInfo>* out) {
-    std::vector<io::HdfsPathInfo> children;
-    Status st = client_->ListDirectory(path, &children);
+    std::vector<HdfsPathInfo> children;
+    Status st = ListDirectory(path, &children);
     if (!st.ok()) {
       if (select.allow_not_found) {
         ARROW_ASSIGN_OR_RAISE(auto info, GetFileInfo(path));
@@ -129,6 +221,15 @@ class HadoopFileSystem::Impl {
     return Status::OK();
   }
 
+  Status GetWorkingDirectory(std::string* out) {
+    char buffer[2048];
+    if (driver_->GetWorkingDirectory(client_, buffer, sizeof(buffer) - 1) == 
nullptr) {
+      return IOErrorFromErrno(errno, "HDFS GetWorkingDirectory failed");
+    }
+    *out = buffer;
+    return Status::OK();
+  }
+
   Result<std::vector<FileInfo>> GetFileInfo(const FileSelector& select) {
     // See GetFileInfo(const std::string&) above.
     if (select.base_dir.substr(0, 5) == "hdfs:") {
@@ -143,7 +244,7 @@ class HadoopFileSystem::Impl {
     // If select.base_dir is absolute, we need to trim the "URI authority"
     // portion of the working directory.
     std::string wd;
-    RETURN_NOT_OK(client_->GetWorkingDirectory(&wd));
+    RETURN_NOT_OK(GetWorkingDirectory(&wd));
 
     if (!select.base_dir.empty() && select.base_dir.front() == '/') {
       // base_dir is absolute, only keep the URI authority portion.
@@ -186,24 +287,100 @@ class HadoopFileSystem::Impl {
                                "': parent is not a directory");
       }
     }
-    RETURN_NOT_OK(client_->MakeDirectory(path));
+    RETURN_NOT_OK(MakeDirectory(path));
+    return Status::OK();
+  }
+
+  Status MakeDirectory(const std::string& path) {
+    int ret = driver_->MakeDirectory(client_, path.c_str());
+    CHECK_FAILURE(ret, "create directory");
+    return Status::OK();
+  }
+
+  Status GetPathInfoStatus(const std::string& path, HdfsPathInfo* info) {
+    hdfsFileInfo* entry = driver_->GetPathInfo(client_, path.c_str());
+
+    if (entry == nullptr) {
+      return GetPathInfoFailed(path);
+    }
+
+    SetPathInfo(entry, info);
+    driver_->FreeFileInfo(entry, 1);
+
+    return Status::OK();
+  }
+
+  Status Stat(const std::string& path, arrow::fs::FileInfo* file_info) {
+    HdfsPathInfo info;
+    RETURN_NOT_OK(GetPathInfoStatus(path, &info));
+
+    file_info->set_size(info.size);
+    file_info->set_type(info.kind);
     return Status::OK();
   }
 
   Status CheckForDirectory(const std::string& path, const char* action) {
     // Check existence of path, and that it's a directory
-    io::HdfsPathInfo info;
-    RETURN_NOT_OK(client_->GetPathInfo(path, &info));
-    if (info.kind != io::ObjectType::DIRECTORY) {
+    HdfsPathInfo info;
+    RETURN_NOT_OK(GetPathInfoStatus(path, &info));
+    if (info.kind != FileType::Directory) {
       return Status::IOError("Cannot ", action, " directory '", path,
                              "': not a directory");
     }
     return Status::OK();
   }
 
+  Status GetChildren(const std::string& path, std::vector<std::string>* 
listing) {
+    std::vector<HdfsPathInfo> detailed_listing;
+    RETURN_NOT_OK(ListDirectory(path, &detailed_listing));
+    for (const auto& info : detailed_listing) {
+      listing->push_back(info.name);
+    }
+    return Status::OK();
+  }
+
+  Status ListDirectory(const std::string& path, std::vector<HdfsPathInfo>* 
listing) {
+    int num_entries = 0;
+    errno = 0;
+    hdfsFileInfo* entries = driver_->ListDirectory(client_, path.c_str(), 
&num_entries);
+
+    if (entries == nullptr) {
+      // If the directory is empty, entries is NULL but errno is 0. Non-zero
+      // errno indicates error
+      //
+      // Note: errno is thread-local
+      //
+      // XXX(wesm): ARROW-2300; we found with Hadoop 2.6 that libhdfs would set
+      // errno 2/ENOENT for empty directories. To be more robust to this we
+      // double check this case
+      if ((errno == 0) || (errno == ENOENT && Exists(path))) {
+        num_entries = 0;
+      } else {
+        return IOErrorFromErrno(errno, "HDFS list directory failed");
+      }
+    }
+
+    // Allocate additional space for elements
+    int vec_offset = static_cast<int>(listing->size());
+    listing->resize(vec_offset + num_entries);
+
+    for (int i = 0; i < num_entries; ++i) {
+      SetPathInfo(entries + i, &(*listing)[vec_offset + i]);
+    }
+
+    // Free libhdfs file info
+    driver_->FreeFileInfo(entries, num_entries);
+
+    return Status::OK();
+  }
+
+  Status DeleteDirectory(const std::string& path) {
+    return Delete(path, /*recursive=*/true);
+  }
+
   Status DeleteDir(const std::string& path) {
     RETURN_NOT_OK(CheckForDirectory(path, "delete"));
-    return client_->DeleteDirectory(path);
+    return DeleteDirectory(path);
   }
 
   Status DeleteDirContents(const std::string& path, bool missing_dir_ok) {
@@ -216,9 +393,9 @@ class HadoopFileSystem::Impl {
     }
 
     std::vector<std::string> file_list;
-    RETURN_NOT_OK(client_->GetChildren(path, &file_list));
+    RETURN_NOT_OK(GetChildren(path, &file_list));
     for (const auto& file : file_list) {
-      RETURN_NOT_OK(client_->Delete(file, /*recursive=*/true));
+      RETURN_NOT_OK(Delete(file, /*recursive=*/true));
     }
     return Status::OK();
   }
@@ -227,35 +404,126 @@ class HadoopFileSystem::Impl {
     if (IsDirectory(path)) {
       return Status::IOError("path is a directory");
     }
-    RETURN_NOT_OK(client_->Delete(path));
+    RETURN_NOT_OK(Delete(path, /*recursive=*/false));
+    return Status::OK();
+  }
+
+  Status Delete(const std::string& path, bool recursive) {
+    int ret = driver_->Delete(client_, path.c_str(), 
static_cast<int>(recursive));
+    CHECK_FAILURE(ret, "delete");
+    return Status::OK();
+  }
+
+  Status Rename(const std::string& src, const std::string& dst) {
+    int ret = driver_->Rename(client_, src.c_str(), dst.c_str());
+    CHECK_FAILURE(ret, "Rename");
     return Status::OK();
   }
 
   Status Move(const std::string& src, const std::string& dest) {
-    auto st = client_->Rename(src, dest);
+    auto st = Rename(src, dest);
     if (st.IsIOError() && IsFile(src) && IsFile(dest)) {
       // Allow file -> file clobber
-      RETURN_NOT_OK(client_->Delete(dest));
-      st = client_->Rename(src, dest);
+      RETURN_NOT_OK(Delete(dest, /*recursive=*/false));
+      st = Rename(src, dest);
     }
     return st;
   }
 
+  Status Copy(const std::string& src, const std::string& dst) {
+    int ret = driver_->Copy(client_, src.c_str(), client_, dst.c_str());
+    CHECK_FAILURE(ret, "Copy");
+    return Status::OK();
+  }
+
   Status CopyFile(const std::string& src, const std::string& dest) {
-    return client_->Copy(src, dest);
+    return Copy(src, dest);
+  }
+
+  Status Chmod(const std::string& path, int mode) {
+    int ret = driver_->Chmod(client_, path.c_str(), static_cast<short>(mode)); 
 // NOLINT
+    CHECK_FAILURE(ret, "Chmod");
+    return Status::OK();
+  }
+
+  Status Chown(const std::string& path, const char* owner, const char* group) {
+    int ret = driver_->Chown(client_, path.c_str(), owner, group);
+    CHECK_FAILURE(ret, "Chown");
+    return Status::OK();
+  }
+
+  Status OpenReadable(const std::string& path, int32_t buffer_size,
+                      const io::IOContext& io_context,
+                      std::shared_ptr<HdfsReadableFile>* file) {
+    errno = 0;
+    hdfsFile handle =
+        driver_->OpenFile(client_, path.c_str(), O_RDONLY, buffer_size, 0, 0);
+
+    if (handle == nullptr) {
+      return IOErrorFromErrno(errno, "Opening HDFS file '", path, "' failed");
+    }
+
+    // std::make_shared does not work with private ctors
+    std::shared_ptr<HdfsReadableFile> file_tmp;
+    ARROW_ASSIGN_OR_RAISE(file_tmp, HdfsReadableFile::Make(path, buffer_size, 
io_context,
+                                                           driver_, client_, 
handle));
+    *file = std::move(file_tmp);
+    return Status::OK();
+  }
+
+  Status OpenReadable(const std::string& path, int32_t buffer_size,
+                      std::shared_ptr<HdfsReadableFile>* file) {
+    return OpenReadable(path, buffer_size, io::default_io_context(), file);
+  }
+
+  Status OpenReadable(const std::string& path, 
std::shared_ptr<HdfsReadableFile>* file) {
+    return OpenReadable(path, kDefaultHdfsBufferSize, 
io::default_io_context(), file);
+  }
+
+  Status OpenReadable(const std::string& path, const io::IOContext& io_context,
+                      std::shared_ptr<HdfsReadableFile>* file) {
+    return OpenReadable(path, kDefaultHdfsBufferSize, io_context, file);
+  }
+
+  Status OpenWritable(const std::string& path, bool append, int32_t 
buffer_size,
+                      int16_t replication, int64_t default_block_size,
+                      std::shared_ptr<HdfsOutputStream>* file) {
+    int flags = O_WRONLY;
+    if (append) flags |= O_APPEND;
+
+    errno = 0;
+    hdfsFile handle =
+        driver_->OpenFile(client_, path.c_str(), flags, buffer_size, 
replication,
+                          static_cast<tSize>(default_block_size));
+
+    if (handle == nullptr) {
+      return IOErrorFromErrno(errno, "Opening HDFS file '", path, "' failed");
+    }
+
+    // std::make_shared does not work with private ctors
+    std::shared_ptr<HdfsOutputStream> file_tmp;
+    ARROW_ASSIGN_OR_RAISE(
+        file_tmp, HdfsOutputStream::Make(path, buffer_size, driver_, client_, 
handle));
+    *file = std::move(file_tmp);
+    return Status::OK();
+  }
+
+  Status OpenWritable(const std::string& path, bool append,
+                      std::shared_ptr<HdfsOutputStream>* file) {
+    return OpenWritable(path, append, 0, 0, 0, file);
   }
 
   Result<std::shared_ptr<io::InputStream>> OpenInputStream(const std::string& 
path) {
     ARROW_RETURN_NOT_OK(internal::AssertNoTrailingSlash(path));
-    std::shared_ptr<io::HdfsReadableFile> file;
-    RETURN_NOT_OK(client_->OpenReadable(path, io_context_, &file));
+    std::shared_ptr<HdfsReadableFile> file;
+    RETURN_NOT_OK(OpenReadable(path, io_context_, &file));
     return file;
   }
 
   Result<std::shared_ptr<io::RandomAccessFile>> OpenInputFile(const 
std::string& path) {
     ARROW_RETURN_NOT_OK(internal::AssertNoTrailingSlash(path));
-    std::shared_ptr<io::HdfsReadableFile> file;
-    RETURN_NOT_OK(client_->OpenReadable(path, io_context_, &file));
+    std::shared_ptr<HdfsReadableFile> file;
+    RETURN_NOT_OK(OpenReadable(path, io_context_, &file));
     return file;
   }
 
@@ -269,16 +537,37 @@ class HadoopFileSystem::Impl {
     return OpenOutputStreamGeneric(path, append);
   }
 
+  Status GetCapacity(int64_t* nbytes) {
+    tOffset ret = driver_->GetCapacity(client_);
+    CHECK_FAILURE(ret, "GetCapacity");
+    *nbytes = ret;
+    return Status::OK();
+  }
+
+  Status GetUsed(int64_t* nbytes) {
+    tOffset ret = driver_->GetUsed(client_);
+    CHECK_FAILURE(ret, "GetUsed");
+    *nbytes = ret;
+    return Status::OK();
+  }
+
  protected:
   const HdfsOptions options_;
   const io::IOContext io_context_;
-  std::shared_ptr<::arrow::io::HadoopFileSystem> client_;
+  internal::LibHdfsShim* driver_;
 
-  void PathInfoToFileInfo(const io::HdfsPathInfo& info, FileInfo* out) {
-    if (info.kind == io::ObjectType::DIRECTORY) {
+  std::string namenode_host_;
+  std::string user_;
+  int port_;
+  std::string kerb_ticket_;
+
+  hdfsFS client_;
+
+  void PathInfoToFileInfo(const HdfsPathInfo& info, FileInfo* out) {
+    if (info.kind == FileType::Directory) {
       out->set_type(FileType::Directory);
       out->set_size(kNoSize);
-    } else if (info.kind == io::ObjectType::FILE) {
+    } else if (info.kind == FileType::File) {
       out->set_type(FileType::File);
       out->set_size(info.size);
     }
@@ -288,25 +577,24 @@ class HadoopFileSystem::Impl {
   Result<std::shared_ptr<io::OutputStream>> OpenOutputStreamGeneric(
       const std::string& path, bool append) {
     ARROW_RETURN_NOT_OK(internal::AssertNoTrailingSlash(path));
-    std::shared_ptr<io::HdfsOutputStream> stream;
-    RETURN_NOT_OK(client_->OpenWritable(path, append, options_.buffer_size,
-                                        options_.replication, 
options_.default_block_size,
-                                        &stream));
+    std::shared_ptr<HdfsOutputStream> stream;
+    RETURN_NOT_OK(OpenWritable(path, append, options_.buffer_size, 
options_.replication,
+                               options_.default_block_size, &stream));
     return stream;
   }
 
   bool IsDirectory(const std::string& path) {
-    io::HdfsPathInfo info;
-    return GetPathInfo(path, &info) && info.kind == io::ObjectType::DIRECTORY;
+    HdfsPathInfo info;
+    return GetPathInfo(path, &info) && info.kind == FileType::Directory;
   }
 
   bool IsFile(const std::string& path) {
-    io::HdfsPathInfo info;
-    return GetPathInfo(path, &info) && info.kind == io::ObjectType::FILE;
+    HdfsPathInfo info;
+    return GetPathInfo(path, &info) && info.kind == FileType::File;
   }
 
-  bool GetPathInfo(const std::string& path, io::HdfsPathInfo* info) {
-    return client_->GetPathInfo(path, info).ok();
+  bool GetPathInfo(const std::string& path, HdfsPathInfo* info) {
+    return GetPathInfoStatus(path, info).ok();
   }
 
   TimePoint ToTimePoint(int secs) {
@@ -316,16 +604,14 @@ class HadoopFileSystem::Impl {
 };
 
 void HdfsOptions::ConfigureEndPoint(std::string host, int port) {
-  connection_config.host = std::move(host);
-  connection_config.port = port;
+  host_ = std::move(host);
+  port_ = port;
 }
 
-void HdfsOptions::ConfigureUser(std::string user_name) {
-  connection_config.user = std::move(user_name);
-}
+void HdfsOptions::ConfigureUser(std::string user_name) { user_ = 
std::move(user_name); }
 
 void HdfsOptions::ConfigureKerberosTicketCachePath(std::string path) {
-  connection_config.kerb_ticket = std::move(path);
+  kerb_ticket_ = std::move(path);
 }
 
 void HdfsOptions::ConfigureReplication(int16_t replication) {
@@ -341,17 +627,14 @@ void HdfsOptions::ConfigureBlockSize(int64_t 
default_block_size) {
 }
 
 void HdfsOptions::ConfigureExtraConf(std::string key, std::string val) {
-  connection_config.extra_conf.emplace(std::move(key), std::move(val));
+  extra_conf_.emplace(std::move(key), std::move(val));
 }
 
 bool HdfsOptions::Equals(const HdfsOptions& other) const {
   return (buffer_size == other.buffer_size && replication == other.replication 
&&
-          default_block_size == other.default_block_size &&
-          connection_config.host == other.connection_config.host &&
-          connection_config.port == other.connection_config.port &&
-          connection_config.user == other.connection_config.user &&
-          connection_config.kerb_ticket == other.connection_config.kerb_ticket 
&&
-          connection_config.extra_conf == other.connection_config.extra_conf);
+          default_block_size == other.default_block_size && host_ == 
other.host_ &&
+          port_ == other.port_ && user_ == other.user_ &&
+          kerb_ticket_ == other.kerb_ticket_ && extra_conf_ == 
other.extra_conf_);
 }
 
 Result<HdfsOptions> HdfsOptions::FromUri(const Uri& uri) {
@@ -540,5 +823,15 @@ Result<std::shared_ptr<io::OutputStream>> 
HadoopFileSystem::OpenAppendStream(
   return impl_->OpenAppendStream(path);
 }
 
+// ----------------------------------------------------------------------
+// Allow public API users to check whether we are set up correctly
+
+Status HaveLibHdfs() {
+  internal::LibHdfsShim* driver;
+  return internal::ConnectLibHdfs(&driver);
+}
+
 }  // namespace fs
 }  // namespace arrow
+
+#undef CHECK_FAILURE
diff --git a/cpp/src/arrow/filesystem/hdfs.h b/cpp/src/arrow/filesystem/hdfs.h
index 25604a39e3..b27f282419 100644
--- a/cpp/src/arrow/filesystem/hdfs.h
+++ b/cpp/src/arrow/filesystem/hdfs.h
@@ -19,10 +19,10 @@
 
 #include <memory>
 #include <string>
+#include <unordered_map>
 #include <vector>
 
 #include "arrow/filesystem/filesystem.h"
-#include "arrow/io/hdfs.h"
 #include "arrow/util/uri.h"
 
 namespace arrow::fs {
@@ -32,10 +32,7 @@ struct ARROW_EXPORT HdfsOptions {
   HdfsOptions() = default;
   ~HdfsOptions() = default;
 
-  /// Hdfs configuration options, contains host, port, driver
-  io::HdfsConnectionConfig connection_config;
-
-  /// Used by Hdfs OpenWritable Interface.
+  /// Used by OpenOutputStream and OpenAppendStream
   int32_t buffer_size = 0;
   int16_t replication = 3;
   int64_t default_block_size = 0;
@@ -52,12 +49,24 @@ struct ARROW_EXPORT HdfsOptions {
 
   static Result<HdfsOptions> FromUri(const ::arrow::util::Uri& uri);
   static Result<HdfsOptions> FromUri(const std::string& uri);
+
+  const std::string& host() const { return host_; }
+  int port() const { return port_; }
+  const std::string& user() const { return user_; }
+  const std::string& kerb_ticket() const { return kerb_ticket_; }
+  const std::unordered_map<std::string, std::string>& extra_conf() const {
+    return extra_conf_;
+  }
+
+ private:
+  std::string host_;
+  int port_ = 0;
+  std::string user_;
+  std::string kerb_ticket_;
+  std::unordered_map<std::string, std::string> extra_conf_;
 };
 
 /// HDFS-backed FileSystem implementation.
-///
-/// implementation notes:
-/// - This is a wrapper of arrow/io/hdfs, so we can use FileSystem API to 
handle hdfs.
 class ARROW_EXPORT HadoopFileSystem : public FileSystem {
  public:
   ~HadoopFileSystem() override;
@@ -114,4 +123,6 @@ class ARROW_EXPORT HadoopFileSystem : public FileSystem {
   std::unique_ptr<Impl> impl_;
 };
 
+ARROW_EXPORT Status HaveLibHdfs();
+
 }  // namespace arrow::fs
diff --git a/cpp/src/arrow/io/hdfs_internal.cc 
b/cpp/src/arrow/filesystem/hdfs_internal.cc
similarity index 63%
rename from cpp/src/arrow/io/hdfs_internal.cc
rename to cpp/src/arrow/filesystem/hdfs_internal.cc
index ab9f8e3502..89ae079b1c 100644
--- a/cpp/src/arrow/io/hdfs_internal.cc
+++ b/cpp/src/arrow/filesystem/hdfs_internal.cc
@@ -28,7 +28,7 @@
 // This software may be modified and distributed under the terms
 // of the BSD license. See the LICENSE file for details.
 
-#include "arrow/io/hdfs_internal.h"
+#include "arrow/filesystem/hdfs_internal.h"
 
 #include <cstdint>
 #include <cstdlib>
@@ -43,6 +43,7 @@
 #  include <dlfcn.h>
 #endif
 
+#include "arrow/buffer.h"
 #include "arrow/result.h"
 #include "arrow/status.h"
 #include "arrow/util/io_util.h"
@@ -51,12 +52,20 @@
 namespace arrow {
 
 using internal::GetEnvVarNative;
+using internal::IOErrorFromErrno;
 using internal::PlatformFilename;
 
-namespace io::internal {
+namespace fs::internal {
 
 namespace {
 
+#define CHECK_FAILURE(RETURN_VALUE, WHAT)                       \
+  do {                                                          \
+    if (RETURN_VALUE == -1) {                                   \
+      return IOErrorFromErrno(errno, "HDFS ", WHAT, " failed"); \
+    }                                                           \
+  } while (0)
+
 template <bool Required, typename T>
 Status SetSymbol(void* handle, const char* name, T** symbol) {
   if (*symbol != nullptr) return Status::OK();
@@ -502,5 +511,308 @@ int LibHdfsShim::Utime(hdfsFS fs, const char* path, tTime 
mtime, tTime atime) {
   }
 }
 
-}  // namespace io::internal
+// ----------------------------------------------------------------------
+// File reading
+
+class HdfsAnyFileImpl {
+ public:
+  void set_members(const std::string& path, internal::LibHdfsShim* driver, 
hdfsFS fs,
+                   hdfsFile handle) {
+    path_ = path;
+    driver_ = driver;
+    fs_ = fs;
+    file_ = handle;
+    is_open_ = true;
+  }
+
+  Status Seek(int64_t position) {
+    RETURN_NOT_OK(CheckClosed());
+    int ret = driver_->Seek(fs_, file_, position);
+    CHECK_FAILURE(ret, "seek");
+    return Status::OK();
+  }
+
+  Result<int64_t> Tell() {
+    RETURN_NOT_OK(CheckClosed());
+    int64_t ret = driver_->Tell(fs_, file_);
+    CHECK_FAILURE(ret, "tell");
+    return ret;
+  }
+
+  bool is_open() const { return is_open_; }
+
+ protected:
+  Status CheckClosed() {
+    if (!is_open_) {
+      return Status::Invalid("Operation on closed HDFS file");
+    }
+    return Status::OK();
+  }
+
+  std::string path_;
+
+  internal::LibHdfsShim* driver_;
+
+  // For threadsafety
+  std::mutex lock_;
+
+  // These are pointers in libhdfs, so OK to copy
+  hdfsFS fs_;
+  hdfsFile file_;
+
+  bool is_open_;
+};
+
+namespace {
+
+Status GetPathInfoFailed(const std::string& path) {
+  return IOErrorFromErrno(errno, "Calling GetPathInfo for '", path, "' 
failed");
+}
+
+}  // namespace
+
+}  // namespace fs::internal
+
+namespace fs {
+
+// Private implementation for read-only files
+class HdfsReadableFile::HdfsReadableFileImpl : public 
internal::HdfsAnyFileImpl {
+ public:
+  explicit HdfsReadableFileImpl(MemoryPool* pool) : pool_(pool) {}
+
+  Status Close() {
+    if (is_open_) {
+      // is_open_ must be set to false in the beginning, because the destructor
+      // attempts to close the stream again, and if the first close fails, then
+      // the error doesn't get propagated properly and the second close
+      // initiated by the destructor raises a segfault
+      is_open_ = false;
+      int ret = driver_->CloseFile(fs_, file_);
+      CHECK_FAILURE(ret, "CloseFile");
+    }
+    return Status::OK();
+  }
+
+  bool closed() const { return !is_open_; }
+
+  Result<int64_t> ReadAt(int64_t position, int64_t nbytes, uint8_t* buffer) {
+    RETURN_NOT_OK(CheckClosed());
+    if (!driver_->HasPread()) {
+      std::lock_guard<std::mutex> guard(lock_);
+      RETURN_NOT_OK(Seek(position));
+      return Read(nbytes, buffer);
+    }
+
+    constexpr int64_t kMaxBlockSize = std::numeric_limits<int32_t>::max();
+    int64_t total_bytes = 0;
+    while (nbytes > 0) {
+      const auto block_size = static_cast<tSize>(std::min(kMaxBlockSize, 
nbytes));
+      tSize ret =
+          driver_->Pread(fs_, file_, static_cast<tOffset>(position), buffer, 
block_size);
+      CHECK_FAILURE(ret, "read");
+      DCHECK_LE(ret, block_size);
+      if (ret == 0) {
+        break;  // EOF
+      }
+      buffer += ret;
+      total_bytes += ret;
+      position += ret;
+      nbytes -= ret;
+    }
+    return total_bytes;
+  }
+
+  Result<std::shared_ptr<Buffer>> ReadAt(int64_t position, int64_t nbytes) {
+    RETURN_NOT_OK(CheckClosed());
+
+    ARROW_ASSIGN_OR_RAISE(auto buffer, AllocateResizableBuffer(nbytes, pool_));
+    ARROW_ASSIGN_OR_RAISE(int64_t bytes_read,
+                          ReadAt(position, nbytes, buffer->mutable_data()));
+    if (bytes_read < nbytes) {
+      RETURN_NOT_OK(buffer->Resize(bytes_read));
+      buffer->ZeroPadding();
+    }
+    // R build with openSUSE155 requires an explicit shared_ptr construction
+    return std::shared_ptr<Buffer>(std::move(buffer));
+  }
+
+  Result<int64_t> Read(int64_t nbytes, void* buffer) {
+    RETURN_NOT_OK(CheckClosed());
+
+    int64_t total_bytes = 0;
+    while (total_bytes < nbytes) {
+      tSize ret = driver_->Read(
+          fs_, file_, reinterpret_cast<uint8_t*>(buffer) + total_bytes,
+          static_cast<tSize>(std::min<int64_t>(buffer_size_, nbytes - 
total_bytes)));
+      CHECK_FAILURE(ret, "read");
+      total_bytes += ret;
+      if (ret == 0) {
+        break;
+      }
+    }
+    return total_bytes;
+  }
+
+  Result<std::shared_ptr<Buffer>> Read(int64_t nbytes) {
+    RETURN_NOT_OK(CheckClosed());
+
+    ARROW_ASSIGN_OR_RAISE(auto buffer, AllocateResizableBuffer(nbytes, pool_));
+    ARROW_ASSIGN_OR_RAISE(int64_t bytes_read, Read(nbytes, 
buffer->mutable_data()));
+    if (bytes_read < nbytes) {
+      RETURN_NOT_OK(buffer->Resize(bytes_read));
+    }
+    // R build with openSUSE155 requires an explicit shared_ptr construction
+    return std::shared_ptr<Buffer>(std::move(buffer));
+  }
+
+  Result<int64_t> GetSize() {
+    RETURN_NOT_OK(CheckClosed());
+
+    hdfsFileInfo* entry = driver_->GetPathInfo(fs_, path_.c_str());
+    if (entry == nullptr) {
+      return internal::GetPathInfoFailed(path_);
+    }
+    int64_t size = entry->mSize;
+    driver_->FreeFileInfo(entry, 1);
+    return size;
+  }
+
+  void set_memory_pool(MemoryPool* pool) { pool_ = pool; }
+
+  void set_buffer_size(int32_t buffer_size) { buffer_size_ = buffer_size; }
+
+ private:
+  MemoryPool* pool_;
+  int32_t buffer_size_;
+};
+
+HdfsReadableFile::HdfsReadableFile(const io::IOContext& io_context) {
+  impl_.reset(new HdfsReadableFileImpl(io_context.pool()));
+}
+
+HdfsReadableFile::~HdfsReadableFile() {
+  ARROW_WARN_NOT_OK(impl_->Close(), "Failed to close HdfsReadableFile");
+}
+
+Status HdfsReadableFile::Close() { return impl_->Close(); }
+
+bool HdfsReadableFile::closed() const { return impl_->closed(); }
+
+Result<int64_t> HdfsReadableFile::ReadAt(int64_t position, int64_t nbytes, 
void* buffer) {
+  return impl_->ReadAt(position, nbytes, reinterpret_cast<uint8_t*>(buffer));
+}
+
+Result<std::shared_ptr<Buffer>> HdfsReadableFile::ReadAt(int64_t position,
+                                                         int64_t nbytes) {
+  return impl_->ReadAt(position, nbytes);
+}
+
+Result<int64_t> HdfsReadableFile::Read(int64_t nbytes, void* buffer) {
+  return impl_->Read(nbytes, buffer);
+}
+
+Result<std::shared_ptr<Buffer>> HdfsReadableFile::Read(int64_t nbytes) {
+  return impl_->Read(nbytes);
+}
+
+Result<int64_t> HdfsReadableFile::GetSize() { return impl_->GetSize(); }
+
+Status HdfsReadableFile::Seek(int64_t position) { return 
impl_->Seek(position); }
+
+Result<int64_t> HdfsReadableFile::Tell() const { return impl_->Tell(); }
+
+Result<std::shared_ptr<HdfsReadableFile>> HdfsReadableFile::Make(
+    const std::string& path, int32_t buffer_size, const io::IOContext& 
io_context,
+    internal::LibHdfsShim* driver, hdfsFS fs, hdfsFile handle) {
+  // Must use `new` due to private constructor
+  std::shared_ptr<HdfsReadableFile> file(new HdfsReadableFile(io_context));
+  file->impl_->set_members(path, driver, fs, handle);
+  file->impl_->set_buffer_size(buffer_size);
+  return file;
+}
+
+// ----------------------------------------------------------------------
+// File writing
+
+// Private implementation for writable-only files
+class HdfsOutputStream::HdfsOutputStreamImpl : public 
internal::HdfsAnyFileImpl {
+ public:
+  HdfsOutputStreamImpl() {}
+
+  Status Close() {
+    if (is_open_) {
+      // is_open_ must be set to false in the beginning, because the destructor
+      // attempts to close the stream again, and if the first close fails, then
+      // the error doesn't get propagated properly and the second close
+      // initiated by the destructor raises a segfault
+      is_open_ = false;
+      RETURN_NOT_OK(FlushInternal());
+      int ret = driver_->CloseFile(fs_, file_);
+      CHECK_FAILURE(ret, "CloseFile");
+    }
+    return Status::OK();
+  }
+
+  bool closed() const { return !is_open_; }
+
+  Status Flush() {
+    RETURN_NOT_OK(CheckClosed());
+
+    return FlushInternal();
+  }
+
+  Status Write(const uint8_t* buffer, int64_t nbytes) {
+    RETURN_NOT_OK(CheckClosed());
+
+    constexpr int64_t kMaxBlockSize = std::numeric_limits<int32_t>::max();
+
+    std::lock_guard<std::mutex> guard(lock_);
+    while (nbytes > 0) {
+      const auto block_size = static_cast<tSize>(std::min(kMaxBlockSize, 
nbytes));
+      tSize ret = driver_->Write(fs_, file_, buffer, block_size);
+      CHECK_FAILURE(ret, "Write");
+      DCHECK_LE(ret, block_size);
+      buffer += ret;
+      nbytes -= ret;
+    }
+    return Status::OK();
+  }
+
+ protected:
+  Status FlushInternal() {
+    int ret = driver_->Flush(fs_, file_);
+    CHECK_FAILURE(ret, "Flush");
+    return Status::OK();
+  }
+};
+
+HdfsOutputStream::HdfsOutputStream() { impl_.reset(new 
HdfsOutputStreamImpl()); }
+
+HdfsOutputStream::~HdfsOutputStream() {
+  ARROW_WARN_NOT_OK(impl_->Close(), "Failed to close HdfsOutputStream");
+}
+
+Status HdfsOutputStream::Close() { return impl_->Close(); }
+
+bool HdfsOutputStream::closed() const { return impl_->closed(); }
+
+Status HdfsOutputStream::Write(const void* buffer, int64_t nbytes) {
+  return impl_->Write(reinterpret_cast<const uint8_t*>(buffer), nbytes);
+}
+
+Status HdfsOutputStream::Flush() { return impl_->Flush(); }
+
+Result<int64_t> HdfsOutputStream::Tell() const { return impl_->Tell(); }
+
+Result<std::shared_ptr<HdfsOutputStream>> HdfsOutputStream::Make(
+    const std::string& path, int32_t buffer_size, internal::LibHdfsShim* 
driver,
+    hdfsFS fs, hdfsFile handle) {
+  std::shared_ptr<HdfsOutputStream> file(new HdfsOutputStream());
+  file->impl_->set_members(path, driver, fs, handle);
+  return file;
+}
+
+}  // namespace fs
 }  // namespace arrow
+
+#undef CHECK_FAILURE
diff --git a/cpp/src/arrow/io/hdfs_internal.h 
b/cpp/src/arrow/filesystem/hdfs_internal.h
similarity index 72%
rename from cpp/src/arrow/io/hdfs_internal.h
rename to cpp/src/arrow/filesystem/hdfs_internal.h
index 4b6b4884c0..8606436f25 100644
--- a/cpp/src/arrow/io/hdfs_internal.h
+++ b/cpp/src/arrow/filesystem/hdfs_internal.h
@@ -22,6 +22,11 @@
 
 #include <hdfs.h>
 
+#include "arrow/filesystem/filesystem.h"
+#include "arrow/filesystem/hdfs.h"
+#include "arrow/filesystem/type_fwd.h"
+#include "arrow/io/interfaces.h"
+#include "arrow/util/io_util.h"
 #include "arrow/util/visibility.h"
 #include "arrow/util/windows_compatibility.h"  // IWYU pragma: keep
 
@@ -33,7 +38,9 @@ namespace arrow {
 
 class Status;
 
-namespace io::internal {
+using internal::IOErrorFromErrno;
+
+namespace fs::internal {
 
 // NOTE(wesm): cpplint does not like use of short and other imprecise C types
 struct LibHdfsShim {
@@ -210,5 +217,96 @@ struct LibHdfsShim {
 // TODO(wesm): Remove these exports when we are linking statically
 ARROW_EXPORT Status ConnectLibHdfs(LibHdfsShim** driver);
 
-}  // namespace io::internal
+}  // namespace fs::internal
+
+namespace fs {
+
+struct HdfsPathInfo {
+  arrow::fs::FileType kind;
+
+  std::string name;
+  std::string owner;
+  std::string group;
+
+  // Access times in UNIX timestamps (seconds)
+  int64_t size;
+  int64_t block_size;
+
+  int32_t last_modified_time;
+  int32_t last_access_time;
+
+  int16_t replication;
+  int16_t permissions;
+};
+
+class ARROW_EXPORT HdfsReadableFile : public io::RandomAccessFile {
+ public:
+  ~HdfsReadableFile() override;
+
+  static Result<std::shared_ptr<HdfsReadableFile>> Make(const std::string& 
path,
+                                                        int32_t buffer_size,
+                                                        const io::IOContext& 
io_context,
+                                                        internal::LibHdfsShim* 
driver,
+                                                        hdfsFS fs, hdfsFile 
handle);
+
+  Status Close() override;
+
+  bool closed() const override;
+
+  // NOTE: If you wish to read a particular range of a file in a multithreaded
+  // context, you may prefer to use ReadAt to avoid locking issues
+  Result<int64_t> Read(int64_t nbytes, void* out) override;
+  Result<std::shared_ptr<Buffer>> Read(int64_t nbytes) override;
+  Result<int64_t> ReadAt(int64_t position, int64_t nbytes, void* out) override;
+  Result<std::shared_ptr<Buffer>> ReadAt(int64_t position, int64_t nbytes) 
override;
+
+  Status Seek(int64_t position) override;
+  Result<int64_t> Tell() const override;
+  Result<int64_t> GetSize() override;
+
+ private:
+  explicit HdfsReadableFile(const io::IOContext&);
+
+  class ARROW_NO_EXPORT HdfsReadableFileImpl;
+  std::unique_ptr<HdfsReadableFileImpl> impl_;
+
+  friend class arrow::fs::HadoopFileSystem;
+
+  ARROW_DISALLOW_COPY_AND_ASSIGN(HdfsReadableFile);
+};
+
+// Naming this file OutputStream because it does not support seeking (like the
+// WritableFile interface)
+class ARROW_EXPORT HdfsOutputStream : public io::OutputStream {
+ public:
+  ~HdfsOutputStream() override;
+
+  static Result<std::shared_ptr<HdfsOutputStream>> Make(const std::string& 
path,
+                                                        int32_t buffer_size,
+                                                        internal::LibHdfsShim* 
driver,
+                                                        hdfsFS fs, hdfsFile 
handle);
+
+  Status Close() override;
+
+  bool closed() const override;
+
+  using OutputStream::Write;
+  Status Write(const void* buffer, int64_t nbytes) override;
+
+  Status Flush() override;
+
+  Result<int64_t> Tell() const override;
+
+ private:
+  class ARROW_NO_EXPORT HdfsOutputStreamImpl;
+  std::unique_ptr<HdfsOutputStreamImpl> impl_;
+
+  friend class arrow::fs::HadoopFileSystem;
+
+  HdfsOutputStream();
+
+  ARROW_DISALLOW_COPY_AND_ASSIGN(HdfsOutputStream);
+};
+
+}  // namespace fs
 }  // namespace arrow
diff --git a/cpp/src/arrow/filesystem/hdfs_test.cc 
b/cpp/src/arrow/filesystem/hdfs_test.cc
index db5cefef37..e3f0fa966c 100644
--- a/cpp/src/arrow/filesystem/hdfs_test.cc
+++ b/cpp/src/arrow/filesystem/hdfs_test.cc
@@ -19,14 +19,19 @@
 
 #include <gtest/gtest.h>
 
+#include <atomic>
 #include <cerrno>
 #include <chrono>
+#include <cstring>
 #include <memory>
 #include <sstream>
 #include <string>
+#include <thread>
 
+#include "arrow/buffer.h"
 #include "arrow/filesystem/test_util.h"
 #include "arrow/testing/gtest_util.h"
+#include "arrow/testing/util.h"
 #include "arrow/util/io_util.h"
 #include "arrow/util/logging.h"
 #include "arrow/util/uri.h"
@@ -45,29 +50,29 @@ TEST(TestHdfsOptions, FromUri) {
   ASSERT_OK(uri.Parse("hdfs://localhost"));
   ASSERT_OK_AND_ASSIGN(options, HdfsOptions::FromUri(uri));
   ASSERT_EQ(options.replication, 3);
-  ASSERT_EQ(options.connection_config.host, "hdfs://localhost");
-  ASSERT_EQ(options.connection_config.port, 0);
-  ASSERT_EQ(options.connection_config.user, "");
+  ASSERT_EQ(options.host(), "hdfs://localhost");
+  ASSERT_EQ(options.port(), 0);
+  ASSERT_EQ(options.user(), "");
 
   
ASSERT_OK(uri.Parse("hdfs://otherhost:9999/?replication=2&kerb_ticket=kerb.ticket"));
   ASSERT_OK_AND_ASSIGN(options, HdfsOptions::FromUri(uri));
   ASSERT_EQ(options.replication, 2);
-  ASSERT_EQ(options.connection_config.kerb_ticket, "kerb.ticket");
-  ASSERT_EQ(options.connection_config.host, "hdfs://otherhost");
-  ASSERT_EQ(options.connection_config.port, 9999);
-  ASSERT_EQ(options.connection_config.user, "");
+  ASSERT_EQ(options.kerb_ticket(), "kerb.ticket");
+  ASSERT_EQ(options.host(), "hdfs://otherhost");
+  ASSERT_EQ(options.port(), 9999);
+  ASSERT_EQ(options.user(), "");
 
   ASSERT_OK(uri.Parse("hdfs://otherhost:9999/?hdfs_token=hdfs_token_ticket"));
   ASSERT_OK_AND_ASSIGN(options, HdfsOptions::FromUri(uri));
-  ASSERT_EQ(options.connection_config.host, "hdfs://otherhost");
-  ASSERT_EQ(options.connection_config.port, 9999);
-  ASSERT_EQ(options.connection_config.extra_conf["hdfs_token"], 
"hdfs_token_ticket");
+  ASSERT_EQ(options.host(), "hdfs://otherhost");
+  ASSERT_EQ(options.port(), 9999);
+  ASSERT_EQ(options.extra_conf().at("hdfs_token"), "hdfs_token_ticket");
 
   ASSERT_OK(uri.Parse("viewfs://other-nn/mypath/myfile"));
   ASSERT_OK_AND_ASSIGN(options, HdfsOptions::FromUri(uri));
-  ASSERT_EQ(options.connection_config.host, "viewfs://other-nn");
-  ASSERT_EQ(options.connection_config.port, 0);
-  ASSERT_EQ(options.connection_config.user, "");
+  ASSERT_EQ(options.host(), "viewfs://other-nn");
+  ASSERT_EQ(options.port(), 0);
+  ASSERT_EQ(options.user(), "");
 }
 
 class HadoopFileSystemTestMixin {
@@ -110,9 +115,8 @@ class TestHadoopFileSystem : public ::testing::Test, public 
HadoopFileSystemTest
 
   void TestFileSystemFromUri() {
     std::stringstream ss;
-    ss << "hdfs://" << options_.connection_config.host << ":"
-       << options_.connection_config.port << "/"
-       << "?replication=0&user=" << options_.connection_config.user;
+    ss << "hdfs://" << options_.host() << ":" << options_.port() << "/"
+       << "?replication=0&user=" << options_.user();
 
     std::shared_ptr<FileSystem> uri_fs;
     std::string path;
@@ -201,6 +205,26 @@ class TestHadoopFileSystem : public ::testing::Test, 
public HadoopFileSystemTest
     GTEST_SKIP() << "Driver not loaded, skipping"; \
   }
 
+TEST_F(TestHadoopFileSystem, ConnectsAgain) {
+  SKIP_IF_NO_DRIVER();
+
+  std::shared_ptr<FileSystem> fs;
+  ASSERT_OK_AND_ASSIGN(fs, HadoopFileSystem::Make(this->options_));
+}
+
+TEST_F(TestHadoopFileSystem, MultipleClients) {
+  SKIP_IF_NO_DRIVER();
+
+  std::shared_ptr<FileSystem> fs1, fs2;
+  ASSERT_OK_AND_ASSIGN(fs1, HadoopFileSystem::Make(this->options_));
+  ASSERT_OK_AND_ASSIGN(fs2, HadoopFileSystem::Make(this->options_));
+
+  // fs2 continues to function independently of fs1
+  ASSERT_OK(fs2->CreateDir("multiple-clients"));
+  AssertFileInfo(fs2.get(), "multiple-clients", FileType::Directory);
+  ASSERT_OK(fs2->DeleteDir("multiple-clients"));
+}
+
 TEST_F(TestHadoopFileSystem, CreateDirDeleteDir) {
   SKIP_IF_NO_DRIVER();
 
@@ -274,6 +298,78 @@ TEST_F(TestHadoopFileSystem, WriteReadFile) {
   ASSERT_OK(this->fs_->DeleteDir("CD"));
 }
 
+TEST_F(TestHadoopFileSystem, LargeFile) {
+  SKIP_IF_NO_DRIVER();
+
+  ASSERT_OK(this->fs_->CreateDir("EF"));
+  std::string file_name = "EF/large-file";
+  constexpr int64_t kDataSize = 1000000;
+
+  std::vector<uint8_t> data(kDataSize);
+  random_bytes(kDataSize, /*seed=*/0, data.data());
+
+  std::shared_ptr<io::OutputStream> stream;
+  ASSERT_OK_AND_ASSIGN(stream, this->fs_->OpenOutputStream(file_name));
+  ASSERT_OK(stream->Write(data.data(), kDataSize));
+  ASSERT_OK(stream->Close());
+
+  std::shared_ptr<io::RandomAccessFile> file;
+  ASSERT_OK_AND_ASSIGN(file, this->fs_->OpenInputFile(file_name));
+  ASSERT_OK_AND_EQ(kDataSize, file->GetSize());
+
+  ASSERT_OK_AND_ASSIGN(auto buffer, AllocateBuffer(kDataSize));
+  ASSERT_OK_AND_EQ(kDataSize, file->Read(kDataSize, buffer->mutable_data()));
+  ASSERT_EQ(0, std::memcmp(buffer->data(), data.data(), kDataSize));
+
+  ASSERT_OK(this->fs_->DeleteDir("EF"));
+}
+
+TEST_F(TestHadoopFileSystem, ThreadSafety) {
+  SKIP_IF_NO_DRIVER();
+
+  ASSERT_OK(this->fs_->CreateDir("GH"));
+  std::string file_name = "GH/threadsafety";
+  std::string data = "foobar";
+  CreateFile(this->fs_.get(), file_name, data);
+
+  std::shared_ptr<io::RandomAccessFile> file;
+  ASSERT_OK_AND_ASSIGN(file, this->fs_->OpenInputFile(file_name));
+
+  std::atomic<int> correct_count(0);
+  constexpr int kNumIterations = 1000;
+
+  auto ReadData = [&]() {
+    for (int i = 0; i < kNumIterations; ++i) {
+      std::shared_ptr<Buffer> buffer;
+      if (i % 2 == 0) {
+        ASSERT_OK_AND_ASSIGN(buffer, file->ReadAt(3, 3));
+        if (0 == std::memcmp(data.c_str() + 3, buffer->data(), 3)) {
+          correct_count += 1;
+        }
+      } else {
+        ASSERT_OK_AND_ASSIGN(buffer, file->ReadAt(0, 4));
+        if (0 == std::memcmp(data.c_str() + 0, buffer->data(), 4)) {
+          correct_count += 1;
+        }
+      }
+    }
+  };
+
+  std::thread thread1(ReadData);
+  std::thread thread2(ReadData);
+  std::thread thread3(ReadData);
+  std::thread thread4(ReadData);
+
+  thread1.join();
+  thread2.join();
+  thread3.join();
+  thread4.join();
+
+  ASSERT_EQ(kNumIterations * 4, correct_count);
+
+  ASSERT_OK(this->fs_->DeleteDir("GH"));
+}
+
 TEST_F(TestHadoopFileSystem, GetFileInfoRelative) {
   // Test GetFileInfo() with relative paths
   SKIP_IF_NO_DRIVER();
diff --git a/cpp/src/arrow/io/CMakeLists.txt b/cpp/src/arrow/io/CMakeLists.txt
index f37844026d..4dfee22a56 100644
--- a/cpp/src/arrow/io/CMakeLists.txt
+++ b/cpp/src/arrow/io/CMakeLists.txt
@@ -21,26 +21,6 @@
 add_arrow_test(buffered_test PREFIX "arrow-io")
 add_arrow_test(compressed_test PREFIX "arrow-io")
 add_arrow_test(file_test PREFIX "arrow-io")
-
-if(ARROW_HDFS)
-  set(HDFS_TEST_EXTRA_LINK_LIBS arrow::hadoop)
-  if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
-    if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "9")
-      list(APPEND HDFS_TEST_EXTRA_LINK_LIBS stdc++fs)
-    endif()
-  elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
-    if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8")
-      list(APPEND HDFS_TEST_EXTRA_LINK_LIBS c++fs)
-    endif()
-  endif()
-  add_arrow_test(hdfs_test
-                 NO_VALGRIND
-                 PREFIX
-                 "arrow-io"
-                 EXTRA_LINK_LIBS
-                 ${HDFS_TEST_EXTRA_LINK_LIBS})
-endif()
-
 add_arrow_test(memory_test PREFIX "arrow-io")
 
 add_arrow_benchmark(file_benchmark PREFIX "arrow-io")
diff --git a/cpp/src/arrow/io/api.h b/cpp/src/arrow/io/api.h
index d55b2c2d55..3bfde6de45 100644
--- a/cpp/src/arrow/io/api.h
+++ b/cpp/src/arrow/io/api.h
@@ -20,6 +20,5 @@
 #include "arrow/io/buffered.h"
 #include "arrow/io/compressed.h"
 #include "arrow/io/file.h"
-#include "arrow/io/hdfs.h"
 #include "arrow/io/interfaces.h"
 #include "arrow/io/memory.h"
diff --git a/cpp/src/arrow/io/hdfs.cc b/cpp/src/arrow/io/hdfs.cc
deleted file mode 100644
index 73d8e15757..0000000000
--- a/cpp/src/arrow/io/hdfs.cc
+++ /dev/null
@@ -1,716 +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.
-
-#include <algorithm>
-#include <cerrno>
-#include <cstdint>
-#include <cstring>
-#include <limits>
-#include <memory>
-#include <mutex>
-#include <sstream>
-#include <string>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-
-#include "arrow/buffer.h"
-#include "arrow/io/hdfs.h"
-#include "arrow/io/hdfs_internal.h"
-#include "arrow/io/interfaces.h"
-#include "arrow/memory_pool.h"
-#include "arrow/result.h"
-#include "arrow/status.h"
-#include "arrow/util/io_util.h"
-#include "arrow/util/logging_internal.h"
-
-using std::size_t;
-
-namespace arrow {
-
-using internal::IOErrorFromErrno;
-
-namespace io {
-
-#define CHECK_FAILURE(RETURN_VALUE, WHAT)                       \
-  do {                                                          \
-    if (RETURN_VALUE == -1) {                                   \
-      return IOErrorFromErrno(errno, "HDFS ", WHAT, " failed"); \
-    }                                                           \
-  } while (0)
-
-static constexpr int kDefaultHdfsBufferSize = 1 << 16;
-
-// ----------------------------------------------------------------------
-// File reading
-
-class HdfsAnyFileImpl {
- public:
-  void set_members(const std::string& path, internal::LibHdfsShim* driver, 
hdfsFS fs,
-                   hdfsFile handle) {
-    path_ = path;
-    driver_ = driver;
-    fs_ = fs;
-    file_ = handle;
-    is_open_ = true;
-  }
-
-  Status Seek(int64_t position) {
-    RETURN_NOT_OK(CheckClosed());
-    int ret = driver_->Seek(fs_, file_, position);
-    CHECK_FAILURE(ret, "seek");
-    return Status::OK();
-  }
-
-  Result<int64_t> Tell() {
-    RETURN_NOT_OK(CheckClosed());
-    int64_t ret = driver_->Tell(fs_, file_);
-    CHECK_FAILURE(ret, "tell");
-    return ret;
-  }
-
-  bool is_open() const { return is_open_; }
-
- protected:
-  Status CheckClosed() {
-    if (!is_open_) {
-      return Status::Invalid("Operation on closed HDFS file");
-    }
-    return Status::OK();
-  }
-
-  std::string path_;
-
-  internal::LibHdfsShim* driver_;
-
-  // For threadsafety
-  std::mutex lock_;
-
-  // These are pointers in libhdfs, so OK to copy
-  hdfsFS fs_;
-  hdfsFile file_;
-
-  bool is_open_;
-};
-
-namespace {
-
-Status GetPathInfoFailed(const std::string& path) {
-  return IOErrorFromErrno(errno, "Calling GetPathInfo for '", path, "' 
failed");
-}
-
-}  // namespace
-
-// Private implementation for read-only files
-class HdfsReadableFile::HdfsReadableFileImpl : public HdfsAnyFileImpl {
- public:
-  explicit HdfsReadableFileImpl(MemoryPool* pool) : pool_(pool) {}
-
-  Status Close() {
-    if (is_open_) {
-      // is_open_ must be set to false in the beginning, because the destructor
-      // attempts to close the stream again, and if the first close fails, then
-      // the error doesn't get propagated properly and the second close
-      // initiated by the destructor raises a segfault
-      is_open_ = false;
-      int ret = driver_->CloseFile(fs_, file_);
-      CHECK_FAILURE(ret, "CloseFile");
-    }
-    return Status::OK();
-  }
-
-  bool closed() const { return !is_open_; }
-
-  Result<int64_t> ReadAt(int64_t position, int64_t nbytes, uint8_t* buffer) {
-    RETURN_NOT_OK(CheckClosed());
-    if (!driver_->HasPread()) {
-      std::lock_guard<std::mutex> guard(lock_);
-      RETURN_NOT_OK(Seek(position));
-      return Read(nbytes, buffer);
-    }
-
-    constexpr int64_t kMaxBlockSize = std::numeric_limits<int32_t>::max();
-    int64_t total_bytes = 0;
-    while (nbytes > 0) {
-      const auto block_size = static_cast<tSize>(std::min(kMaxBlockSize, 
nbytes));
-      tSize ret =
-          driver_->Pread(fs_, file_, static_cast<tOffset>(position), buffer, 
block_size);
-      CHECK_FAILURE(ret, "read");
-      DCHECK_LE(ret, block_size);
-      if (ret == 0) {
-        break;  // EOF
-      }
-      buffer += ret;
-      total_bytes += ret;
-      position += ret;
-      nbytes -= ret;
-    }
-    return total_bytes;
-  }
-
-  Result<std::shared_ptr<Buffer>> ReadAt(int64_t position, int64_t nbytes) {
-    RETURN_NOT_OK(CheckClosed());
-
-    ARROW_ASSIGN_OR_RAISE(auto buffer, AllocateResizableBuffer(nbytes, pool_));
-    ARROW_ASSIGN_OR_RAISE(int64_t bytes_read,
-                          ReadAt(position, nbytes, buffer->mutable_data()));
-    if (bytes_read < nbytes) {
-      RETURN_NOT_OK(buffer->Resize(bytes_read));
-      buffer->ZeroPadding();
-    }
-    return buffer;
-  }
-
-  Result<int64_t> Read(int64_t nbytes, void* buffer) {
-    RETURN_NOT_OK(CheckClosed());
-
-    int64_t total_bytes = 0;
-    while (total_bytes < nbytes) {
-      tSize ret = driver_->Read(
-          fs_, file_, reinterpret_cast<uint8_t*>(buffer) + total_bytes,
-          static_cast<tSize>(std::min<int64_t>(buffer_size_, nbytes - 
total_bytes)));
-      CHECK_FAILURE(ret, "read");
-      total_bytes += ret;
-      if (ret == 0) {
-        break;
-      }
-    }
-    return total_bytes;
-  }
-
-  Result<std::shared_ptr<Buffer>> Read(int64_t nbytes) {
-    RETURN_NOT_OK(CheckClosed());
-
-    ARROW_ASSIGN_OR_RAISE(auto buffer, AllocateResizableBuffer(nbytes, pool_));
-    ARROW_ASSIGN_OR_RAISE(int64_t bytes_read, Read(nbytes, 
buffer->mutable_data()));
-    if (bytes_read < nbytes) {
-      RETURN_NOT_OK(buffer->Resize(bytes_read));
-    }
-    return buffer;
-  }
-
-  Result<int64_t> GetSize() {
-    RETURN_NOT_OK(CheckClosed());
-
-    hdfsFileInfo* entry = driver_->GetPathInfo(fs_, path_.c_str());
-    if (entry == nullptr) {
-      return GetPathInfoFailed(path_);
-    }
-    int64_t size = entry->mSize;
-    driver_->FreeFileInfo(entry, 1);
-    return size;
-  }
-
-  void set_memory_pool(MemoryPool* pool) { pool_ = pool; }
-
-  void set_buffer_size(int32_t buffer_size) { buffer_size_ = buffer_size; }
-
- private:
-  MemoryPool* pool_;
-  int32_t buffer_size_;
-};
-
-HdfsReadableFile::HdfsReadableFile(const io::IOContext& io_context) {
-  impl_.reset(new HdfsReadableFileImpl(io_context.pool()));
-}
-
-HdfsReadableFile::~HdfsReadableFile() {
-  ARROW_WARN_NOT_OK(impl_->Close(), "Failed to close HdfsReadableFile");
-}
-
-Status HdfsReadableFile::Close() { return impl_->Close(); }
-
-bool HdfsReadableFile::closed() const { return impl_->closed(); }
-
-Result<int64_t> HdfsReadableFile::ReadAt(int64_t position, int64_t nbytes, 
void* buffer) {
-  return impl_->ReadAt(position, nbytes, reinterpret_cast<uint8_t*>(buffer));
-}
-
-Result<std::shared_ptr<Buffer>> HdfsReadableFile::ReadAt(int64_t position,
-                                                         int64_t nbytes) {
-  return impl_->ReadAt(position, nbytes);
-}
-
-Result<int64_t> HdfsReadableFile::Read(int64_t nbytes, void* buffer) {
-  return impl_->Read(nbytes, buffer);
-}
-
-Result<std::shared_ptr<Buffer>> HdfsReadableFile::Read(int64_t nbytes) {
-  return impl_->Read(nbytes);
-}
-
-Result<int64_t> HdfsReadableFile::GetSize() { return impl_->GetSize(); }
-
-Status HdfsReadableFile::Seek(int64_t position) { return 
impl_->Seek(position); }
-
-Result<int64_t> HdfsReadableFile::Tell() const { return impl_->Tell(); }
-
-// ----------------------------------------------------------------------
-// File writing
-
-// Private implementation for writable-only files
-class HdfsOutputStream::HdfsOutputStreamImpl : public HdfsAnyFileImpl {
- public:
-  HdfsOutputStreamImpl() {}
-
-  Status Close() {
-    if (is_open_) {
-      // is_open_ must be set to false in the beginning, because the destructor
-      // attempts to close the stream again, and if the first close fails, then
-      // the error doesn't get propagated properly and the second close
-      // initiated by the destructor raises a segfault
-      is_open_ = false;
-      RETURN_NOT_OK(FlushInternal());
-      int ret = driver_->CloseFile(fs_, file_);
-      CHECK_FAILURE(ret, "CloseFile");
-    }
-    return Status::OK();
-  }
-
-  bool closed() const { return !is_open_; }
-
-  Status Flush() {
-    RETURN_NOT_OK(CheckClosed());
-
-    return FlushInternal();
-  }
-
-  Status Write(const uint8_t* buffer, int64_t nbytes) {
-    RETURN_NOT_OK(CheckClosed());
-
-    constexpr int64_t kMaxBlockSize = std::numeric_limits<int32_t>::max();
-
-    std::lock_guard<std::mutex> guard(lock_);
-    while (nbytes > 0) {
-      const auto block_size = static_cast<tSize>(std::min(kMaxBlockSize, 
nbytes));
-      tSize ret = driver_->Write(fs_, file_, buffer, block_size);
-      CHECK_FAILURE(ret, "Write");
-      DCHECK_LE(ret, block_size);
-      buffer += ret;
-      nbytes -= ret;
-    }
-    return Status::OK();
-  }
-
- protected:
-  Status FlushInternal() {
-    int ret = driver_->Flush(fs_, file_);
-    CHECK_FAILURE(ret, "Flush");
-    return Status::OK();
-  }
-};
-
-HdfsOutputStream::HdfsOutputStream() { impl_.reset(new 
HdfsOutputStreamImpl()); }
-
-HdfsOutputStream::~HdfsOutputStream() {
-  ARROW_WARN_NOT_OK(impl_->Close(), "Failed to close HdfsOutputStream");
-}
-
-Status HdfsOutputStream::Close() { return impl_->Close(); }
-
-bool HdfsOutputStream::closed() const { return impl_->closed(); }
-
-Status HdfsOutputStream::Write(const void* buffer, int64_t nbytes) {
-  return impl_->Write(reinterpret_cast<const uint8_t*>(buffer), nbytes);
-}
-
-Status HdfsOutputStream::Flush() { return impl_->Flush(); }
-
-Result<int64_t> HdfsOutputStream::Tell() const { return impl_->Tell(); }
-
-// ----------------------------------------------------------------------
-// HDFS client
-
-// TODO(wesm): this could throw std::bad_alloc in the course of copying strings
-// into the path info object
-static void SetPathInfo(const hdfsFileInfo* input, HdfsPathInfo* out) {
-  out->kind = input->mKind == kObjectKindFile ? ObjectType::FILE : 
ObjectType::DIRECTORY;
-  out->name = std::string(input->mName);
-  out->owner = std::string(input->mOwner);
-  out->group = std::string(input->mGroup);
-
-  out->last_access_time = static_cast<int32_t>(input->mLastAccess);
-  out->last_modified_time = static_cast<int32_t>(input->mLastMod);
-  out->size = static_cast<int64_t>(input->mSize);
-
-  out->replication = input->mReplication;
-  out->block_size = input->mBlockSize;
-
-  out->permissions = input->mPermissions;
-}
-
-// Private implementation
-class HadoopFileSystem::HadoopFileSystemImpl {
- public:
-  HadoopFileSystemImpl() : driver_(NULLPTR), port_(0), fs_(NULLPTR) {}
-
-  Status Connect(const HdfsConnectionConfig* config) {
-    RETURN_NOT_OK(ConnectLibHdfs(&driver_));
-
-    // connect to HDFS with the builder object
-    hdfsBuilder* builder = driver_->NewBuilder();
-    if (!config->host.empty()) {
-      driver_->BuilderSetNameNode(builder, config->host.c_str());
-    }
-    driver_->BuilderSetNameNodePort(builder, static_cast<tPort>(config->port));
-    if (!config->user.empty()) {
-      driver_->BuilderSetUserName(builder, config->user.c_str());
-    }
-    if (!config->kerb_ticket.empty()) {
-      driver_->BuilderSetKerbTicketCachePath(builder, 
config->kerb_ticket.c_str());
-    }
-
-    for (const auto& kv : config->extra_conf) {
-      int ret = driver_->BuilderConfSetStr(builder, kv.first.c_str(), 
kv.second.c_str());
-      CHECK_FAILURE(ret, "confsetstr");
-    }
-
-    driver_->BuilderSetForceNewInstance(builder);
-    fs_ = driver_->BuilderConnect(builder);
-
-    if (fs_ == nullptr) {
-      return Status::IOError("HDFS connection failed");
-    }
-    namenode_host_ = config->host;
-    port_ = config->port;
-    user_ = config->user;
-    kerb_ticket_ = config->kerb_ticket;
-
-    return Status::OK();
-  }
-
-  Status MakeDirectory(const std::string& path) {
-    int ret = driver_->MakeDirectory(fs_, path.c_str());
-    CHECK_FAILURE(ret, "create directory");
-    return Status::OK();
-  }
-
-  Status Delete(const std::string& path, bool recursive) {
-    int ret = driver_->Delete(fs_, path.c_str(), static_cast<int>(recursive));
-    CHECK_FAILURE(ret, "delete");
-    return Status::OK();
-  }
-
-  Status Disconnect() {
-    int ret = driver_->Disconnect(fs_);
-    CHECK_FAILURE(ret, "hdfsFS::Disconnect");
-    return Status::OK();
-  }
-
-  bool Exists(const std::string& path) {
-    // hdfsExists does not distinguish between RPC failure and the file not
-    // existing
-    int ret = driver_->Exists(fs_, path.c_str());
-    return ret == 0;
-  }
-
-  Status GetCapacity(int64_t* nbytes) {
-    tOffset ret = driver_->GetCapacity(fs_);
-    CHECK_FAILURE(ret, "GetCapacity");
-    *nbytes = ret;
-    return Status::OK();
-  }
-
-  Status GetUsed(int64_t* nbytes) {
-    tOffset ret = driver_->GetUsed(fs_);
-    CHECK_FAILURE(ret, "GetUsed");
-    *nbytes = ret;
-    return Status::OK();
-  }
-
-  Status GetWorkingDirectory(std::string* out) {
-    char buffer[2048];
-    if (driver_->GetWorkingDirectory(fs_, buffer, sizeof(buffer) - 1) == 
nullptr) {
-      return IOErrorFromErrno(errno, "HDFS GetWorkingDirectory failed");
-    }
-    *out = buffer;
-    return Status::OK();
-  }
-
-  Status GetPathInfo(const std::string& path, HdfsPathInfo* info) {
-    hdfsFileInfo* entry = driver_->GetPathInfo(fs_, path.c_str());
-
-    if (entry == nullptr) {
-      return GetPathInfoFailed(path);
-    }
-
-    SetPathInfo(entry, info);
-    driver_->FreeFileInfo(entry, 1);
-
-    return Status::OK();
-  }
-
-  Status Stat(const std::string& path, FileStatistics* stat) {
-    HdfsPathInfo info;
-    RETURN_NOT_OK(GetPathInfo(path, &info));
-
-    stat->size = info.size;
-    stat->kind = info.kind;
-    return Status::OK();
-  }
-
-  Status GetChildren(const std::string& path, std::vector<std::string>* 
listing) {
-    std::vector<HdfsPathInfo> detailed_listing;
-    RETURN_NOT_OK(ListDirectory(path, &detailed_listing));
-    for (const auto& info : detailed_listing) {
-      listing->push_back(info.name);
-    }
-    return Status::OK();
-  }
-
-  Status ListDirectory(const std::string& path, std::vector<HdfsPathInfo>* 
listing) {
-    int num_entries = 0;
-    errno = 0;
-    hdfsFileInfo* entries = driver_->ListDirectory(fs_, path.c_str(), 
&num_entries);
-
-    if (entries == nullptr) {
-      // If the directory is empty, entries is NULL but errno is 0. Non-zero
-      // errno indicates error
-      //
-      // Note: errno is thread-local
-      //
-      // XXX(wesm): ARROW-2300; we found with Hadoop 2.6 that libhdfs would set
-      // errno 2/ENOENT for empty directories. To be more robust to this we
-      // double check this case
-      if ((errno == 0) || (errno == ENOENT && Exists(path))) {
-        num_entries = 0;
-      } else {
-        return IOErrorFromErrno(errno, "HDFS list directory failed");
-      }
-    }
-
-    // Allocate additional space for elements
-    int vec_offset = static_cast<int>(listing->size());
-    listing->resize(vec_offset + num_entries);
-
-    for (int i = 0; i < num_entries; ++i) {
-      SetPathInfo(entries + i, &(*listing)[vec_offset + i]);
-    }
-
-    // Free libhdfs file info
-    driver_->FreeFileInfo(entries, num_entries);
-
-    return Status::OK();
-  }
-
-  Status OpenReadable(const std::string& path, int32_t buffer_size,
-                      const io::IOContext& io_context,
-                      std::shared_ptr<HdfsReadableFile>* file) {
-    errno = 0;
-    hdfsFile handle = driver_->OpenFile(fs_, path.c_str(), O_RDONLY, 
buffer_size, 0, 0);
-
-    if (handle == nullptr) {
-      return IOErrorFromErrno(errno, "Opening HDFS file '", path, "' failed");
-    }
-
-    // std::make_shared does not work with private ctors
-    *file = std::shared_ptr<HdfsReadableFile>(new 
HdfsReadableFile(io_context));
-    (*file)->impl_->set_members(path, driver_, fs_, handle);
-    (*file)->impl_->set_buffer_size(buffer_size);
-
-    return Status::OK();
-  }
-
-  Status OpenWritable(const std::string& path, bool append, int32_t 
buffer_size,
-                      int16_t replication, int64_t default_block_size,
-                      std::shared_ptr<HdfsOutputStream>* file) {
-    int flags = O_WRONLY;
-    if (append) flags |= O_APPEND;
-
-    errno = 0;
-    hdfsFile handle =
-        driver_->OpenFile(fs_, path.c_str(), flags, buffer_size, replication,
-                          static_cast<tSize>(default_block_size));
-
-    if (handle == nullptr) {
-      return IOErrorFromErrno(errno, "Opening HDFS file '", path, "' failed");
-    }
-
-    // std::make_shared does not work with private ctors
-    *file = std::shared_ptr<HdfsOutputStream>(new HdfsOutputStream());
-    (*file)->impl_->set_members(path, driver_, fs_, handle);
-
-    return Status::OK();
-  }
-
-  Status Rename(const std::string& src, const std::string& dst) {
-    int ret = driver_->Rename(fs_, src.c_str(), dst.c_str());
-    CHECK_FAILURE(ret, "Rename");
-    return Status::OK();
-  }
-
-  Status Copy(const std::string& src, const std::string& dst) {
-    int ret = driver_->Copy(fs_, src.c_str(), fs_, dst.c_str());
-    CHECK_FAILURE(ret, "Rename");
-    return Status::OK();
-  }
-
-  Status Move(const std::string& src, const std::string& dst) {
-    int ret = driver_->Move(fs_, src.c_str(), fs_, dst.c_str());
-    CHECK_FAILURE(ret, "Rename");
-    return Status::OK();
-  }
-
-  Status Chmod(const std::string& path, int mode) {
-    int ret = driver_->Chmod(fs_, path.c_str(), static_cast<short>(mode));  // 
NOLINT
-    CHECK_FAILURE(ret, "Chmod");
-    return Status::OK();
-  }
-
-  Status Chown(const std::string& path, const char* owner, const char* group) {
-    int ret = driver_->Chown(fs_, path.c_str(), owner, group);
-    CHECK_FAILURE(ret, "Chown");
-    return Status::OK();
-  }
-
- private:
-  internal::LibHdfsShim* driver_;
-
-  std::string namenode_host_;
-  std::string user_;
-  int port_;
-  std::string kerb_ticket_;
-
-  hdfsFS fs_;
-};
-
-// ----------------------------------------------------------------------
-// Public API for HDFSClient
-
-HadoopFileSystem::HadoopFileSystem() { impl_.reset(new 
HadoopFileSystemImpl()); }
-
-HadoopFileSystem::~HadoopFileSystem() {}
-
-Status HadoopFileSystem::Connect(const HdfsConnectionConfig* config,
-                                 std::shared_ptr<HadoopFileSystem>* fs) {
-  // ctor is private, make_shared will not work
-  *fs = std::shared_ptr<HadoopFileSystem>(new HadoopFileSystem());
-
-  RETURN_NOT_OK((*fs)->impl_->Connect(config));
-  return Status::OK();
-}
-
-Status HadoopFileSystem::MakeDirectory(const std::string& path) {
-  return impl_->MakeDirectory(path);
-}
-
-Status HadoopFileSystem::Delete(const std::string& path, bool recursive) {
-  return impl_->Delete(path, recursive);
-}
-
-Status HadoopFileSystem::DeleteDirectory(const std::string& path) {
-  return Delete(path, true);
-}
-
-Status HadoopFileSystem::Disconnect() { return impl_->Disconnect(); }
-
-bool HadoopFileSystem::Exists(const std::string& path) { return 
impl_->Exists(path); }
-
-Status HadoopFileSystem::GetPathInfo(const std::string& path, HdfsPathInfo* 
info) {
-  return impl_->GetPathInfo(path, info);
-}
-
-Status HadoopFileSystem::Stat(const std::string& path, FileStatistics* stat) {
-  return impl_->Stat(path, stat);
-}
-
-Status HadoopFileSystem::GetCapacity(int64_t* nbytes) {
-  return impl_->GetCapacity(nbytes);
-}
-
-Status HadoopFileSystem::GetUsed(int64_t* nbytes) { return 
impl_->GetUsed(nbytes); }
-
-Status HadoopFileSystem::GetWorkingDirectory(std::string* out) {
-  return impl_->GetWorkingDirectory(out);
-}
-
-Status HadoopFileSystem::GetChildren(const std::string& path,
-                                     std::vector<std::string>* listing) {
-  return impl_->GetChildren(path, listing);
-}
-
-Status HadoopFileSystem::ListDirectory(const std::string& path,
-                                       std::vector<HdfsPathInfo>* listing) {
-  return impl_->ListDirectory(path, listing);
-}
-
-Status HadoopFileSystem::OpenReadable(const std::string& path, int32_t 
buffer_size,
-                                      std::shared_ptr<HdfsReadableFile>* file) 
{
-  return impl_->OpenReadable(path, buffer_size, io::default_io_context(), 
file);
-}
-
-Status HadoopFileSystem::OpenReadable(const std::string& path,
-                                      std::shared_ptr<HdfsReadableFile>* file) 
{
-  return OpenReadable(path, kDefaultHdfsBufferSize, io::default_io_context(), 
file);
-}
-
-Status HadoopFileSystem::OpenReadable(const std::string& path, int32_t 
buffer_size,
-                                      const io::IOContext& io_context,
-                                      std::shared_ptr<HdfsReadableFile>* file) 
{
-  return impl_->OpenReadable(path, buffer_size, io_context, file);
-}
-
-Status HadoopFileSystem::OpenReadable(const std::string& path,
-                                      const io::IOContext& io_context,
-                                      std::shared_ptr<HdfsReadableFile>* file) 
{
-  return OpenReadable(path, kDefaultHdfsBufferSize, io_context, file);
-}
-
-Status HadoopFileSystem::OpenWritable(const std::string& path, bool append,
-                                      int32_t buffer_size, int16_t replication,
-                                      int64_t default_block_size,
-                                      std::shared_ptr<HdfsOutputStream>* file) 
{
-  return impl_->OpenWritable(path, append, buffer_size, replication, 
default_block_size,
-                             file);
-}
-
-Status HadoopFileSystem::OpenWritable(const std::string& path, bool append,
-                                      std::shared_ptr<HdfsOutputStream>* file) 
{
-  return OpenWritable(path, append, 0, 0, 0, file);
-}
-
-Status HadoopFileSystem::Chmod(const std::string& path, int mode) {
-  return impl_->Chmod(path, mode);
-}
-
-Status HadoopFileSystem::Chown(const std::string& path, const char* owner,
-                               const char* group) {
-  return impl_->Chown(path, owner, group);
-}
-
-Status HadoopFileSystem::Rename(const std::string& src, const std::string& 
dst) {
-  return impl_->Rename(src, dst);
-}
-
-Status HadoopFileSystem::Copy(const std::string& src, const std::string& dst) {
-  return impl_->Copy(src, dst);
-}
-
-Status HadoopFileSystem::Move(const std::string& src, const std::string& dst) {
-  return impl_->Move(src, dst);
-}
-
-// ----------------------------------------------------------------------
-// Allow public API users to check whether we are set up correctly
-
-Status HaveLibHdfs() {
-  internal::LibHdfsShim* driver;
-  return internal::ConnectLibHdfs(&driver);
-}
-
-}  // namespace io
-}  // namespace arrow
diff --git a/cpp/src/arrow/io/hdfs.h b/cpp/src/arrow/io/hdfs.h
deleted file mode 100644
index 46038070ae..0000000000
--- a/cpp/src/arrow/io/hdfs.h
+++ /dev/null
@@ -1,284 +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.
-
-#pragma once
-
-#include <cstdint>
-#include <memory>
-#include <string>
-#include <unordered_map>
-#include <vector>
-
-#include "arrow/io/interfaces.h"
-#include "arrow/util/macros.h"
-#include "arrow/util/visibility.h"
-
-namespace arrow {
-
-class Buffer;
-class MemoryPool;
-class Status;
-
-namespace io {
-
-class HdfsReadableFile;
-class HdfsOutputStream;
-
-/// DEPRECATED.  Use the FileSystem API in arrow::fs instead.
-struct ObjectType {
-  enum type { FILE, DIRECTORY };
-};
-
-/// DEPRECATED.  Use the FileSystem API in arrow::fs instead.
-struct ARROW_EXPORT FileStatistics {
-  /// Size of file, -1 if finding length is unsupported
-  int64_t size;
-  ObjectType::type kind;
-};
-
-class ARROW_EXPORT FileSystem {
- public:
-  virtual ~FileSystem() = default;
-
-  virtual Status MakeDirectory(const std::string& path) = 0;
-
-  virtual Status DeleteDirectory(const std::string& path) = 0;
-
-  virtual Status GetChildren(const std::string& path,
-                             std::vector<std::string>* listing) = 0;
-
-  virtual Status Rename(const std::string& src, const std::string& dst) = 0;
-
-  virtual Status Stat(const std::string& path, FileStatistics* stat) = 0;
-};
-
-struct HdfsPathInfo {
-  ObjectType::type kind;
-
-  std::string name;
-  std::string owner;
-  std::string group;
-
-  // Access times in UNIX timestamps (seconds)
-  int64_t size;
-  int64_t block_size;
-
-  int32_t last_modified_time;
-  int32_t last_access_time;
-
-  int16_t replication;
-  int16_t permissions;
-};
-
-struct HdfsConnectionConfig {
-  std::string host;
-  int port;
-  std::string user;
-  std::string kerb_ticket;
-  std::unordered_map<std::string, std::string> extra_conf;
-};
-
-class ARROW_EXPORT HadoopFileSystem : public FileSystem {
- public:
-  ~HadoopFileSystem() override;
-
-  // Connect to an HDFS cluster given a configuration
-  //
-  // @param config (in): configuration for connecting
-  // @param fs (out): the created client
-  // @returns Status
-  static Status Connect(const HdfsConnectionConfig* config,
-                        std::shared_ptr<HadoopFileSystem>* fs);
-
-  // Create directory and all parents
-  //
-  // @param path (in): absolute HDFS path
-  // @returns Status
-  Status MakeDirectory(const std::string& path) override;
-
-  // Delete file or directory
-  // @param path absolute path to data
-  // @param recursive if path is a directory, delete contents as well
-  // @returns error status on failure
-  Status Delete(const std::string& path, bool recursive = false);
-
-  Status DeleteDirectory(const std::string& path) override;
-
-  // Disconnect from cluster
-  //
-  // @returns Status
-  Status Disconnect();
-
-  // @param path (in): absolute HDFS path
-  // @returns bool, true if the path exists, false if not (or on error)
-  bool Exists(const std::string& path);
-
-  // @param path (in): absolute HDFS path
-  // @param info (out)
-  // @returns Status
-  Status GetPathInfo(const std::string& path, HdfsPathInfo* info);
-
-  // @param nbytes (out): total capacity of the filesystem
-  // @returns Status
-  Status GetCapacity(int64_t* nbytes);
-
-  // @param nbytes (out): total bytes used of the filesystem
-  // @returns Status
-  Status GetUsed(int64_t* nbytes);
-
-  Status GetChildren(const std::string& path, std::vector<std::string>* 
listing) override;
-
-  /// List directory contents
-  ///
-  /// If path is a relative path, returned values will be absolute paths or 
URIs
-  /// starting from the current working directory.
-  Status ListDirectory(const std::string& path, std::vector<HdfsPathInfo>* 
listing);
-
-  /// Return the filesystem's current working directory.
-  ///
-  /// The working directory is the base path for all relative paths given to
-  /// other APIs.
-  /// NOTE: this actually returns a URI.
-  Status GetWorkingDirectory(std::string* out);
-
-  /// Change
-  ///
-  /// @param path file path to change
-  /// @param owner pass null for no change
-  /// @param group pass null for no change
-  Status Chown(const std::string& path, const char* owner, const char* group);
-
-  /// Change path permissions
-  ///
-  /// \param path Absolute path in file system
-  /// \param mode Mode bitset
-  /// \return Status
-  Status Chmod(const std::string& path, int mode);
-
-  // Move file or directory from source path to destination path within the
-  // current filesystem
-  Status Rename(const std::string& src, const std::string& dst) override;
-
-  Status Copy(const std::string& src, const std::string& dst);
-
-  Status Move(const std::string& src, const std::string& dst);
-
-  Status Stat(const std::string& path, FileStatistics* stat) override;
-
-  // TODO(wesm): GetWorkingDirectory, SetWorkingDirectory
-
-  // Open an HDFS file in READ mode. Returns error
-  // status if the file is not found.
-  //
-  // @param path complete file path
-  Status OpenReadable(const std::string& path, int32_t buffer_size,
-                      std::shared_ptr<HdfsReadableFile>* file);
-
-  Status OpenReadable(const std::string& path, int32_t buffer_size,
-                      const io::IOContext& io_context,
-                      std::shared_ptr<HdfsReadableFile>* file);
-
-  Status OpenReadable(const std::string& path, 
std::shared_ptr<HdfsReadableFile>* file);
-
-  Status OpenReadable(const std::string& path, const io::IOContext& io_context,
-                      std::shared_ptr<HdfsReadableFile>* file);
-
-  // FileMode::WRITE options
-  // @param path complete file path
-  // @param buffer_size 0 by default
-  // @param replication 0 by default
-  // @param default_block_size 0 by default
-  Status OpenWritable(const std::string& path, bool append, int32_t 
buffer_size,
-                      int16_t replication, int64_t default_block_size,
-                      std::shared_ptr<HdfsOutputStream>* file);
-
-  Status OpenWritable(const std::string& path, bool append,
-                      std::shared_ptr<HdfsOutputStream>* file);
-
- private:
-  friend class HdfsReadableFile;
-  friend class HdfsOutputStream;
-
-  class ARROW_NO_EXPORT HadoopFileSystemImpl;
-  std::unique_ptr<HadoopFileSystemImpl> impl_;
-
-  HadoopFileSystem();
-  ARROW_DISALLOW_COPY_AND_ASSIGN(HadoopFileSystem);
-};
-
-class ARROW_EXPORT HdfsReadableFile : public RandomAccessFile {
- public:
-  ~HdfsReadableFile() override;
-
-  Status Close() override;
-
-  bool closed() const override;
-
-  // NOTE: If you wish to read a particular range of a file in a multithreaded
-  // context, you may prefer to use ReadAt to avoid locking issues
-  Result<int64_t> Read(int64_t nbytes, void* out) override;
-  Result<std::shared_ptr<Buffer>> Read(int64_t nbytes) override;
-  Result<int64_t> ReadAt(int64_t position, int64_t nbytes, void* out) override;
-  Result<std::shared_ptr<Buffer>> ReadAt(int64_t position, int64_t nbytes) 
override;
-
-  Status Seek(int64_t position) override;
-  Result<int64_t> Tell() const override;
-  Result<int64_t> GetSize() override;
-
- private:
-  explicit HdfsReadableFile(const io::IOContext&);
-
-  class ARROW_NO_EXPORT HdfsReadableFileImpl;
-  std::unique_ptr<HdfsReadableFileImpl> impl_;
-
-  friend class HadoopFileSystem::HadoopFileSystemImpl;
-
-  ARROW_DISALLOW_COPY_AND_ASSIGN(HdfsReadableFile);
-};
-
-// Naming this file OutputStream because it does not support seeking (like the
-// WritableFile interface)
-class ARROW_EXPORT HdfsOutputStream : public OutputStream {
- public:
-  ~HdfsOutputStream() override;
-
-  Status Close() override;
-
-  bool closed() const override;
-
-  using OutputStream::Write;
-  Status Write(const void* buffer, int64_t nbytes) override;
-
-  Status Flush() override;
-
-  Result<int64_t> Tell() const override;
-
- private:
-  class ARROW_NO_EXPORT HdfsOutputStreamImpl;
-  std::unique_ptr<HdfsOutputStreamImpl> impl_;
-
-  friend class HadoopFileSystem::HadoopFileSystemImpl;
-
-  HdfsOutputStream();
-
-  ARROW_DISALLOW_COPY_AND_ASSIGN(HdfsOutputStream);
-};
-
-ARROW_EXPORT Status HaveLibHdfs();
-
-}  // namespace io
-}  // namespace arrow
diff --git a/cpp/src/arrow/io/hdfs_test.cc b/cpp/src/arrow/io/hdfs_test.cc
deleted file mode 100644
index 4f0a280b76..0000000000
--- a/cpp/src/arrow/io/hdfs_test.cc
+++ /dev/null
@@ -1,466 +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.
-
-#include <atomic>
-#include <cstdint>
-#include <cstdlib>
-#include <cstring>
-#include <filesystem>
-#include <iostream>
-#include <memory>
-#include <sstream>  // IWYU pragma: keep
-#include <string>
-#include <thread>
-#include <vector>
-
-#include <gtest/gtest.h>
-
-#include "arrow/buffer.h"
-#include "arrow/io/hdfs.h"
-#include "arrow/io/hdfs_internal.h"
-#include "arrow/io/interfaces.h"
-#include "arrow/status.h"
-#include "arrow/testing/gtest_util.h"
-#include "arrow/testing/util.h"
-
-namespace arrow {
-namespace io {
-
-std::vector<uint8_t> RandomData(int64_t size) {
-  std::vector<uint8_t> buffer(size);
-  random_bytes(size, 0, buffer.data());
-  return buffer;
-}
-
-class TestHadoopFileSystem : public ::testing::Test {
- public:
-  Status MakeScratchDir() {
-    if (client_->Exists(scratch_dir_)) {
-      RETURN_NOT_OK((client_->Delete(scratch_dir_, true)));
-    }
-    return client_->MakeDirectory(scratch_dir_);
-  }
-
-  Status WriteDummyFile(const std::string& path, const uint8_t* buffer, 
int64_t size,
-                        bool append = false, int buffer_size = 0, int16_t 
replication = 0,
-                        int default_block_size = 0) {
-    std::shared_ptr<HdfsOutputStream> file;
-    RETURN_NOT_OK(client_->OpenWritable(path, append, buffer_size, replication,
-                                        default_block_size, &file));
-
-    RETURN_NOT_OK(file->Write(buffer, size));
-    RETURN_NOT_OK(file->Close());
-
-    return Status::OK();
-  }
-
-  std::string ScratchPath(const std::string& name) {
-    std::stringstream ss;
-    ss << scratch_dir_ << "/" << name;
-    return ss.str();
-  }
-
-  std::string HdfsAbsPath(const std::string& relpath) {
-    std::stringstream ss;
-    ss << "hdfs://" << conf_.host << ":" << conf_.port << relpath;
-    return ss.str();
-  }
-
-  // Set up shared state between unit tests
-  void SetUp() {
-    internal::LibHdfsShim* driver_shim;
-
-    client_ = nullptr;
-    scratch_dir_ =
-        (std::filesystem::temp_directory_path() / 
"arrow-hdfs/scratch-").string();
-    int random_size = 4;
-    scratch_dir_.resize(scratch_dir_.size() + random_size, '%');
-    random_alnum(
-        random_size, 0,
-        reinterpret_cast<uint8_t*>(&scratch_dir_[scratch_dir_.size() - 
random_size]));
-
-    loaded_driver_ = false;
-
-    Status msg = ConnectLibHdfs(&driver_shim);
-    if (!msg.ok()) {
-      if (std::getenv("ARROW_HDFS_TEST_LIBHDFS_REQUIRE")) {
-        FAIL() << "Loading libhdfs failed: " << msg.ToString();
-      } else {
-        std::cout << "Loading libhdfs failed, skipping tests gracefully: "
-                  << msg.ToString() << std::endl;
-      }
-      return;
-    }
-
-    loaded_driver_ = true;
-
-    const char* host = std::getenv("ARROW_HDFS_TEST_HOST");
-    const char* port = std::getenv("ARROW_HDFS_TEST_PORT");
-    const char* user = std::getenv("ARROW_HDFS_TEST_USER");
-
-    ASSERT_TRUE(user != nullptr) << "Set ARROW_HDFS_TEST_USER";
-
-    conf_.host = host == nullptr ? "localhost" : host;
-    conf_.user = user;
-    conf_.port = port == nullptr ? 20500 : atoi(port);
-
-    ASSERT_OK(HadoopFileSystem::Connect(&conf_, &client_));
-  }
-
-  void TearDown() {
-    if (client_) {
-      if (client_->Exists(scratch_dir_)) {
-        ARROW_EXPECT_OK(client_->Delete(scratch_dir_, true));
-      }
-      ARROW_EXPECT_OK(client_->Disconnect());
-    }
-  }
-
-  HdfsConnectionConfig conf_;
-  bool loaded_driver_;
-
-  // Resources shared amongst unit tests
-  std::string scratch_dir_;
-  std::shared_ptr<HadoopFileSystem> client_;
-};
-
-#define SKIP_IF_NO_DRIVER()                        \
-  if (!this->loaded_driver_) {                     \
-    GTEST_SKIP() << "Driver not loaded, skipping"; \
-  }
-
-TEST_F(TestHadoopFileSystem, ConnectsAgain) {
-  SKIP_IF_NO_DRIVER();
-
-  std::shared_ptr<HadoopFileSystem> client;
-  ASSERT_OK(HadoopFileSystem::Connect(&this->conf_, &client));
-  ASSERT_OK(client->Disconnect());
-}
-
-TEST_F(TestHadoopFileSystem, MultipleClients) {
-  SKIP_IF_NO_DRIVER();
-
-  ASSERT_OK(this->MakeScratchDir());
-
-  std::shared_ptr<HadoopFileSystem> client1;
-  std::shared_ptr<HadoopFileSystem> client2;
-  ASSERT_OK(HadoopFileSystem::Connect(&this->conf_, &client1));
-  ASSERT_OK(HadoopFileSystem::Connect(&this->conf_, &client2));
-  ASSERT_OK(client1->Disconnect());
-
-  // client2 continues to function after equivalent client1 has shutdown
-  std::vector<HdfsPathInfo> listing;
-  ASSERT_OK(client2->ListDirectory(this->scratch_dir_, &listing));
-  ASSERT_OK(client2->Disconnect());
-}
-
-TEST_F(TestHadoopFileSystem, MakeDirectory) {
-  SKIP_IF_NO_DRIVER();
-
-  std::string path = this->ScratchPath("create-directory");
-
-  if (this->client_->Exists(path)) {
-    ASSERT_OK(this->client_->Delete(path, true));
-  }
-
-  ASSERT_OK(this->client_->MakeDirectory(path));
-  ASSERT_TRUE(this->client_->Exists(path));
-  std::vector<HdfsPathInfo> listing;
-  ARROW_EXPECT_OK(this->client_->ListDirectory(path, &listing));
-  ASSERT_EQ(0, listing.size());
-  ARROW_EXPECT_OK(this->client_->Delete(path, true));
-  ASSERT_FALSE(this->client_->Exists(path));
-  ASSERT_RAISES(IOError, this->client_->ListDirectory(path, &listing));
-}
-
-TEST_F(TestHadoopFileSystem, GetCapacityUsed) {
-  SKIP_IF_NO_DRIVER();
-
-  // Who knows what is actually in your DFS cluster, but expect it to have
-  // positive used bytes and capacity
-  int64_t nbytes = 0;
-  ASSERT_OK(this->client_->GetCapacity(&nbytes));
-  ASSERT_LT(0, nbytes);
-
-  ASSERT_OK(this->client_->GetUsed(&nbytes));
-  ASSERT_LT(0, nbytes);
-}
-
-TEST_F(TestHadoopFileSystem, GetPathInfo) {
-  SKIP_IF_NO_DRIVER();
-
-  HdfsPathInfo info;
-
-  ASSERT_OK(this->MakeScratchDir());
-
-  // Directory info
-  ASSERT_OK(this->client_->GetPathInfo(this->scratch_dir_, &info));
-  ASSERT_EQ(ObjectType::DIRECTORY, info.kind);
-  ASSERT_EQ(this->HdfsAbsPath(this->scratch_dir_), info.name);
-  ASSERT_EQ(this->conf_.user, info.owner);
-
-  // TODO(wesm): test group, other attrs
-
-  auto path = this->ScratchPath("test-file");
-
-  const int size = 100;
-
-  std::vector<uint8_t> buffer = RandomData(size);
-
-  ASSERT_OK(this->WriteDummyFile(path, buffer.data(), size));
-  ASSERT_OK(this->client_->GetPathInfo(path, &info));
-
-  ASSERT_EQ(ObjectType::FILE, info.kind);
-  ASSERT_EQ(this->HdfsAbsPath(path), info.name);
-  ASSERT_EQ(this->conf_.user, info.owner);
-  ASSERT_EQ(size, info.size);
-}
-
-TEST_F(TestHadoopFileSystem, GetPathInfoNotExist) {
-  // ARROW-2919: Test that the error message is reasonable
-  SKIP_IF_NO_DRIVER();
-
-  ASSERT_OK(this->MakeScratchDir());
-  auto path = this->ScratchPath("path-does-not-exist");
-
-  HdfsPathInfo info;
-  Status s = this->client_->GetPathInfo(path, &info);
-  ASSERT_TRUE(s.IsIOError());
-
-  const std::string error_message = s.ToString();
-
-  // Check that the file path is found in the error message
-  ASSERT_LT(error_message.find(path), std::string::npos);
-}
-
-TEST_F(TestHadoopFileSystem, AppendToFile) {
-  SKIP_IF_NO_DRIVER();
-
-  ASSERT_OK(this->MakeScratchDir());
-
-  auto path = this->ScratchPath("test-file");
-  const int size = 100;
-
-  std::vector<uint8_t> buffer = RandomData(size);
-  ASSERT_OK(this->WriteDummyFile(path, buffer.data(), size));
-
-  // now append
-  ASSERT_OK(this->WriteDummyFile(path, buffer.data(), size, true));
-
-  HdfsPathInfo info;
-  ASSERT_OK(this->client_->GetPathInfo(path, &info));
-  ASSERT_EQ(size * 2, info.size);
-}
-
-TEST_F(TestHadoopFileSystem, ListDirectory) {
-  SKIP_IF_NO_DRIVER();
-
-  const int size = 100;
-  std::vector<uint8_t> data = RandomData(size);
-
-  auto p1 = this->ScratchPath("test-file-1");
-  auto p2 = this->ScratchPath("test-file-2");
-  auto d1 = this->ScratchPath("test-dir-1");
-
-  ASSERT_OK(this->MakeScratchDir());
-  ASSERT_OK(this->WriteDummyFile(p1, data.data(), size));
-  ASSERT_OK(this->WriteDummyFile(p2, data.data(), size / 2));
-  ASSERT_OK(this->client_->MakeDirectory(d1));
-
-  std::vector<HdfsPathInfo> listing;
-  ASSERT_OK(this->client_->ListDirectory(this->scratch_dir_, &listing));
-
-  // Do it again, appends!
-  ASSERT_OK(this->client_->ListDirectory(this->scratch_dir_, &listing));
-
-  ASSERT_EQ(6, static_cast<int>(listing.size()));
-
-  // Argh, well, shouldn't expect the listing to be in any particular order
-  for (size_t i = 0; i < listing.size(); ++i) {
-    const HdfsPathInfo& info = listing[i];
-    if (info.name == this->HdfsAbsPath(p1)) {
-      ASSERT_EQ(ObjectType::FILE, info.kind);
-      ASSERT_EQ(size, info.size);
-    } else if (info.name == this->HdfsAbsPath(p2)) {
-      ASSERT_EQ(ObjectType::FILE, info.kind);
-      ASSERT_EQ(size / 2, info.size);
-    } else if (info.name == this->HdfsAbsPath(d1)) {
-      ASSERT_EQ(ObjectType::DIRECTORY, info.kind);
-    } else {
-      FAIL() << "Unexpected path: " << info.name;
-    }
-  }
-}
-
-TEST_F(TestHadoopFileSystem, ReadableMethods) {
-  SKIP_IF_NO_DRIVER();
-
-  ASSERT_OK(this->MakeScratchDir());
-
-  auto path = this->ScratchPath("test-file");
-  const int size = 100;
-
-  std::vector<uint8_t> data = RandomData(size);
-  ASSERT_OK(this->WriteDummyFile(path, data.data(), size));
-
-  std::shared_ptr<HdfsReadableFile> file;
-  ASSERT_OK(this->client_->OpenReadable(path, &file));
-
-  // Test GetSize -- move this into its own unit test if ever needed
-  ASSERT_OK_AND_EQ(size, file->GetSize());
-
-  uint8_t buffer[50];
-
-  ASSERT_OK_AND_EQ(50, file->Read(50, buffer));
-  ASSERT_EQ(0, std::memcmp(buffer, data.data(), 50));
-
-  ASSERT_OK_AND_EQ(50, file->Read(50, buffer));
-  ASSERT_EQ(0, std::memcmp(buffer, data.data() + 50, 50));
-
-  // EOF
-  ASSERT_OK_AND_EQ(0, file->Read(1, buffer));
-
-  // ReadAt to EOF
-  ASSERT_OK_AND_EQ(40, file->ReadAt(60, 100, buffer));
-  ASSERT_EQ(0, std::memcmp(buffer, data.data() + 60, 40));
-
-  // Seek, Tell
-  ASSERT_OK(file->Seek(60));
-  ASSERT_OK_AND_EQ(60, file->Tell());
-}
-
-TEST_F(TestHadoopFileSystem, LargeFile) {
-  SKIP_IF_NO_DRIVER();
-
-  ASSERT_OK(this->MakeScratchDir());
-
-  auto path = this->ScratchPath("test-large-file");
-  const int size = 1000000;
-
-  std::vector<uint8_t> data = RandomData(size);
-  ASSERT_OK(this->WriteDummyFile(path, data.data(), size));
-
-  std::shared_ptr<HdfsReadableFile> file;
-  ASSERT_OK(this->client_->OpenReadable(path, &file));
-
-  ASSERT_FALSE(file->closed());
-
-  ASSERT_OK_AND_ASSIGN(auto buffer, AllocateBuffer(size));
-
-  ASSERT_OK_AND_EQ(size, file->Read(size, buffer->mutable_data()));
-  ASSERT_EQ(0, std::memcmp(buffer->data(), data.data(), size));
-
-  // explicit buffer size
-  std::shared_ptr<HdfsReadableFile> file2;
-  ASSERT_OK(this->client_->OpenReadable(path, 1 << 18, &file2));
-
-  ASSERT_OK_AND_ASSIGN(auto buffer2, AllocateBuffer(size));
-
-  ASSERT_OK_AND_EQ(size, file2->Read(size, buffer2->mutable_data()));
-  ASSERT_EQ(0, std::memcmp(buffer2->data(), data.data(), size));
-}
-
-TEST_F(TestHadoopFileSystem, RenameFile) {
-  SKIP_IF_NO_DRIVER();
-  ASSERT_OK(this->MakeScratchDir());
-
-  auto src_path = this->ScratchPath("src-file");
-  auto dst_path = this->ScratchPath("dst-file");
-  const int size = 100;
-
-  std::vector<uint8_t> data = RandomData(size);
-  ASSERT_OK(this->WriteDummyFile(src_path, data.data(), size));
-
-  ASSERT_OK(this->client_->Rename(src_path, dst_path));
-
-  ASSERT_FALSE(this->client_->Exists(src_path));
-  ASSERT_TRUE(this->client_->Exists(dst_path));
-}
-
-TEST_F(TestHadoopFileSystem, ChmodChown) {
-  SKIP_IF_NO_DRIVER();
-  ASSERT_OK(this->MakeScratchDir());
-
-  auto path = this->ScratchPath("path-to-chmod");
-
-  int16_t mode = 0755;
-  const int size = 100;
-
-  std::vector<uint8_t> data = RandomData(size);
-  ASSERT_OK(this->WriteDummyFile(path, data.data(), size));
-
-  HdfsPathInfo info;
-  ASSERT_OK(this->client_->Chmod(path, mode));
-  ASSERT_OK(this->client_->GetPathInfo(path, &info));
-  ASSERT_EQ(mode, info.permissions);
-
-  std::string owner = "hadoop";
-  std::string group = "hadoop";
-  ASSERT_OK(this->client_->Chown(path, owner.c_str(), group.c_str()));
-  ASSERT_OK(this->client_->GetPathInfo(path, &info));
-  ASSERT_EQ("hadoop", info.owner);
-  ASSERT_EQ("hadoop", info.group);
-}
-
-TEST_F(TestHadoopFileSystem, ThreadSafety) {
-  SKIP_IF_NO_DRIVER();
-  ASSERT_OK(this->MakeScratchDir());
-
-  auto src_path = this->ScratchPath("threadsafety");
-
-  std::string data = "foobar";
-  ASSERT_OK(this->WriteDummyFile(src_path, reinterpret_cast<const 
uint8_t*>(data.c_str()),
-                                 static_cast<int64_t>(data.size())));
-
-  std::shared_ptr<HdfsReadableFile> file;
-  ASSERT_OK(this->client_->OpenReadable(src_path, &file));
-
-  std::atomic<int> correct_count(0);
-  int niter = 1000;
-
-  auto ReadData = [&file, &correct_count, &data, &niter]() {
-    for (int i = 0; i < niter; ++i) {
-      std::shared_ptr<Buffer> buffer;
-      if (i % 2 == 0) {
-        ASSERT_OK_AND_ASSIGN(buffer, file->ReadAt(3, 3));
-        if (0 == memcmp(data.c_str() + 3, buffer->data(), 3)) {
-          correct_count += 1;
-        }
-      } else {
-        ASSERT_OK_AND_ASSIGN(buffer, file->ReadAt(0, 4));
-        if (0 == memcmp(data.c_str() + 0, buffer->data(), 4)) {
-          correct_count += 1;
-        }
-      }
-    }
-  };
-
-  std::thread thread1(ReadData);
-  std::thread thread2(ReadData);
-  std::thread thread3(ReadData);
-  std::thread thread4(ReadData);
-
-  thread1.join();
-  thread2.join();
-  thread3.join();
-  thread4.join();
-
-  ASSERT_EQ(niter * 4, correct_count);
-}
-
-}  // namespace io
-}  // namespace arrow
diff --git a/cpp/src/arrow/io/meson.build b/cpp/src/arrow/io/meson.build
index 93d024fbea..4f12f96b5b 100644
--- a/cpp/src/arrow/io/meson.build
+++ b/cpp/src/arrow/io/meson.build
@@ -28,18 +28,6 @@ foreach io_test : io_tests
     test(test_name, exc)
 endforeach
 
-if needs_hdfs
-    hdfs_incdir = '../../../thirdparty/hadoop/include'
-    exc = executable(
-        'arrow-io-hdfs-test',
-        sources: 'hdfs_test.cc',
-        dependencies: [arrow_test_dep, filesystem_dep],
-        implicit_include_directories: false,
-        include_directories: [hdfs_incdir],
-    )
-    test('arrow-io-hdfs-test', exc)
-endif
-
 io_benchmarks = ['file_benchmark', 'compressed_benchmark']
 
 foreach io_benchmark : io_benchmarks
@@ -63,7 +51,6 @@ install_headers(
         'compressed.h',
         'concurrency.h',
         'file.h',
-        'hdfs.h',
         'interfaces.h',
         'memory.h',
         'mman.h',
diff --git a/cpp/src/arrow/meson.build b/cpp/src/arrow/meson.build
index 418165e1ee..61939d9642 100644
--- a/cpp/src/arrow/meson.build
+++ b/cpp/src/arrow/meson.build
@@ -101,8 +101,6 @@ arrow_components = {
             'io/caching.cc',
             'io/compressed.cc',
             'io/file.cc',
-            'io/hdfs.cc',
-            'io/hdfs_internal.cc',
             'io/interfaces.cc',
             'io/memory.cc',
             'io/slow.cc',
@@ -484,7 +482,10 @@ disable Arrow gcs support with -Dgcs=disabled.
     endif
 
     if needs_hdfs
-        arrow_filesystem_srcs += ['filesystem/hdfs.cc']
+        arrow_filesystem_srcs += [
+            'filesystem/hdfs.cc',
+            'filesystem/hdfs_internal.cc',
+        ]
     endif
 
     if needs_s3
diff --git a/python/pyarrow/__init__.py b/python/pyarrow/__init__.py
index adfc50d573..f2a874f56f 100644
--- a/python/pyarrow/__init__.py
+++ b/python/pyarrow/__init__.py
@@ -108,6 +108,17 @@ def _filesystem_is_available(fs):
         return True
 
 
+def have_libhdfs():
+    """
+    Return true if HDFS (HadoopFileSystem) library is set up correctly.
+    """
+    try:
+        from pyarrow._hdfs import _have_libhdfs  # noqa
+        return _have_libhdfs()
+    except ImportError:
+        return False
+
+
 def show_info():
     """
     Print detailed version and platform information, for error reporting
@@ -258,8 +269,7 @@ from pyarrow.lib import (NativeFile, PythonFile,
                          BufferReader, BufferOutputStream,
                          OSFile, MemoryMappedFile, memory_map,
                          create_memory_map, MockOutputStream,
-                         input_stream, output_stream,
-                         have_libhdfs)
+                         input_stream, output_stream)
 
 from pyarrow.lib import (ChunkedArray, RecordBatch, Table, table,
                          concat_arrays, concat_tables, TableGroupBy,
diff --git a/python/pyarrow/_hdfs.pyx b/python/pyarrow/_hdfs.pyx
index 8a9fddee3d..f59862a108 100644
--- a/python/pyarrow/_hdfs.pyx
+++ b/python/pyarrow/_hdfs.pyx
@@ -23,9 +23,22 @@ from pyarrow.includes.libarrow_fs cimport *
 from pyarrow._fs cimport FileSystem
 
 from pyarrow.lib import frombytes, tobytes
+from pyarrow.lib cimport check_status
 from pyarrow.util import _stringify_path
 
 
+def _have_libhdfs():
+    """
+    Return true if HDFS (HadoopFileSystem) library is set up correctly.
+    """
+    try:
+        with nogil:
+            check_status(HaveLibHdfs())
+        return True
+    except Exception:
+        return False
+
+
 cdef class HadoopFileSystem(FileSystem):
     """
     HDFS backed FileSystem implementation
@@ -142,16 +155,17 @@ replication=1)``
 
     def __reduce__(self):
         cdef CHdfsOptions opts = self.hdfs.options()
+        cdef unordered_map[c_string, c_string] c_extra_conf = opts.extra_conf()
         return (
             HadoopFileSystem._reconstruct, (dict(
-                host=frombytes(opts.connection_config.host),
-                port=opts.connection_config.port,
-                user=frombytes(opts.connection_config.user),
+                host=frombytes(opts.host()),
+                port=opts.port(),
+                user=frombytes(opts.user()),
                 replication=opts.replication,
                 buffer_size=opts.buffer_size,
                 default_block_size=opts.default_block_size,
-                kerb_ticket=frombytes(opts.connection_config.kerb_ticket),
+                kerb_ticket=frombytes(opts.kerb_ticket()),
                 extra_conf={frombytes(k): frombytes(v)
-                            for k, v in opts.connection_config.extra_conf},
+                            for k, v in c_extra_conf},
             ),)
         )
diff --git a/python/pyarrow/includes/libarrow.pxd 
b/python/pyarrow/includes/libarrow.pxd
index 455d11d39e..17dcb87a80 100644
--- a/python/pyarrow/includes/libarrow.pxd
+++ b/python/pyarrow/includes/libarrow.pxd
@@ -1621,10 +1621,6 @@ cdef extern from "arrow/io/api.h" namespace "arrow::io" 
nogil:
         FileMode_WRITE" arrow::io::FileMode::WRITE"
         FileMode_READWRITE" arrow::io::FileMode::READWRITE"
 
-    cdef enum ObjectType" arrow::io::ObjectType::type":
-        ObjectType_FILE" arrow::io::ObjectType::FILE"
-        ObjectType_DIRECTORY" arrow::io::ObjectType::DIRECTORY"
-
     cdef cppclass CIOContext" arrow::io::IOContext":
         CIOContext()
         CIOContext(CStopToken)
@@ -1635,10 +1631,6 @@ cdef extern from "arrow/io/api.h" namespace "arrow::io" 
nogil:
     int GetIOThreadPoolCapacity()
     CStatus SetIOThreadPoolCapacity(int threads)
 
-    cdef cppclass FileStatistics:
-        int64_t size
-        ObjectType kind
-
     cdef cppclass FileInterface:
         CStatus Close()
         CResult[int64_t] Tell()
@@ -1706,9 +1698,6 @@ cdef extern from "arrow/io/api.h" namespace "arrow::io" 
nogil:
                                          WritableFile):
         pass
 
-    cdef cppclass CIOFileSystem" arrow::io::FileSystem":
-        CStatus Stat(const c_string& path, FileStatistics* stat)
-
     cdef cppclass FileOutputStream(COutputStream):
         @staticmethod
         CResult[shared_ptr[COutputStream]] Open(const c_string& path)
@@ -1800,78 +1789,6 @@ cdef extern from "arrow/io/api.h" namespace "arrow::io" 
nogil:
         CTransformInputStreamVTable vtable,
         object method_arg)
 
-    # ----------------------------------------------------------------------
-    # HDFS
-
-    CStatus HaveLibHdfs()
-    CStatus HaveLibHdfs3()
-
-    cdef enum HdfsDriver" arrow::io::HdfsDriver":
-        HdfsDriver_LIBHDFS" arrow::io::HdfsDriver::LIBHDFS"
-        HdfsDriver_LIBHDFS3" arrow::io::HdfsDriver::LIBHDFS3"
-
-    cdef cppclass HdfsConnectionConfig:
-        c_string host
-        int port
-        c_string user
-        c_string kerb_ticket
-        unordered_map[c_string, c_string] extra_conf
-        HdfsDriver driver
-
-    cdef cppclass HdfsPathInfo:
-        ObjectType kind
-        c_string name
-        c_string owner
-        c_string group
-        int32_t last_modified_time
-        int32_t last_access_time
-        int64_t size
-        int16_t replication
-        int64_t block_size
-        int16_t permissions
-
-    cdef cppclass HdfsReadableFile(CRandomAccessFile):
-        pass
-
-    cdef cppclass HdfsOutputStream(COutputStream):
-        pass
-
-    cdef cppclass CIOHadoopFileSystem \
-            "arrow::io::HadoopFileSystem"(CIOFileSystem):
-        @staticmethod
-        CStatus Connect(const HdfsConnectionConfig* config,
-                        shared_ptr[CIOHadoopFileSystem]* client)
-
-        CStatus MakeDirectory(const c_string& path)
-
-        CStatus Delete(const c_string& path, c_bool recursive)
-
-        CStatus Disconnect()
-
-        c_bool Exists(const c_string& path)
-
-        CStatus Chmod(const c_string& path, int mode)
-        CStatus Chown(const c_string& path, const char* owner,
-                      const char* group)
-
-        CStatus GetCapacity(int64_t* nbytes)
-        CStatus GetUsed(int64_t* nbytes)
-
-        CStatus ListDirectory(const c_string& path,
-                              vector[HdfsPathInfo]* listing)
-
-        CStatus GetPathInfo(const c_string& path, HdfsPathInfo* info)
-
-        CStatus Rename(const c_string& src, const c_string& dst)
-
-        CStatus OpenReadable(const c_string& path,
-                             shared_ptr[HdfsReadableFile]* handle)
-
-        CStatus OpenWritable(const c_string& path, c_bool append,
-                             int32_t buffer_size, int16_t replication,
-                             int64_t default_block_size,
-                             shared_ptr[HdfsOutputStream]* handle)
-
     cdef cppclass CBufferReader \
             " arrow::io::BufferReader"(CRandomAccessFile):
         CBufferReader(const shared_ptr[CBuffer]& buffer)
diff --git a/python/pyarrow/includes/libarrow_fs.pxd 
b/python/pyarrow/includes/libarrow_fs.pxd
index d18dc2d2bd..19b663c863 100644
--- a/python/pyarrow/includes/libarrow_fs.pxd
+++ b/python/pyarrow/includes/libarrow_fs.pxd
@@ -271,7 +271,6 @@ cdef extern from "arrow/filesystem/api.h" namespace 
"arrow::fs" nogil:
         CAzureOptions options()
 
     cdef cppclass CHdfsOptions "arrow::fs::HdfsOptions":
-        HdfsConnectionConfig connection_config
         int32_t buffer_size
         int16_t replication
         int64_t default_block_size
@@ -288,6 +287,12 @@ cdef extern from "arrow/filesystem/api.h" namespace 
"arrow::fs" nogil:
         void ConfigureKerberosTicketCachePath(c_string path)
         void ConfigureExtraConf(c_string key, c_string value)
 
+        const c_string& host() const
+        int port() const
+        const c_string& user() const
+        const c_string& kerb_ticket() const
+        const unordered_map[c_string, c_string]& extra_conf() const
+
     cdef cppclass CHadoopFileSystem "arrow::fs::HadoopFileSystem"(CFileSystem):
         @staticmethod
         CResult[shared_ptr[CHadoopFileSystem]] Make(
@@ -311,6 +316,7 @@ cdef extern from "arrow/filesystem/api.h" namespace 
"arrow::fs" nogil:
         const CIOContext& io_context,
         int64_t chunk_size, c_bool use_threads)
 
+    CStatus HaveLibHdfs()
 
 # Callbacks for implementing Python filesystems
 # Use typedef to emulate syntax for std::function<void(..)>
diff --git a/python/pyarrow/io.pxi b/python/pyarrow/io.pxi
index 14cf0deae4..dbecd80909 100644
--- a/python/pyarrow/io.pxi
+++ b/python/pyarrow/io.pxi
@@ -32,7 +32,7 @@ import warnings
 from io import BufferedIOBase, IOBase, TextIOBase, UnsupportedOperation
 from queue import Queue, Empty as QueueEmpty
 
-from pyarrow.lib cimport check_status, HaveLibHdfs
+from pyarrow.lib cimport check_status
 from pyarrow.util import _is_path_like, _stringify_path
 
 
@@ -46,18 +46,6 @@ cdef extern from "Python.h":
         char *v, Py_ssize_t len) except NULL
 
 
-def have_libhdfs():
-    """
-    Return true if HDFS (HadoopFileSystem) library is set up correctly.
-    """
-    try:
-        with nogil:
-            check_status(HaveLibHdfs())
-        return True
-    except Exception:
-        return False
-
-
 def io_thread_count():
     """
     Return the number of threads to use for I/O operations.

Reply via email to