Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions onnxruntime/core/framework/external_data_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,18 @@ class IExternalDataLoader {
virtual bool CanLoad(const OrtMemoryInfo& target_memory_info) const = 0;

// Tensor should be already allocated with the correct memory info and size.
#if defined(__wasm__)
virtual common::Status LoadTensor([[maybe_unused]] const Env& env,
[[maybe_unused]] const std::filesystem::path& data_file_path,
[[maybe_unused]] FileOffsetType data_offset,
[[maybe_unused]] SafeInt<size_t> data_length,
[[maybe_unused]] Tensor& tensor) const {
#else
virtual common::Status LoadTensor([[maybe_unused]] const RandomAccessFile& file,
[[maybe_unused]] FileOffsetType data_offset,
[[maybe_unused]] SafeInt<size_t> data_length,
[[maybe_unused]] Tensor& tensor) const {
#endif
ORT_NOT_IMPLEMENTED(__FUNCTION__, " is not implemented");
}
};
Comment thread
apsonawane marked this conversation as resolved.
Expand Down
82 changes: 62 additions & 20 deletions onnxruntime/core/framework/tensorprotoutils.cc
Original file line number Diff line number Diff line change
Expand Up @@ -568,6 +568,34 @@ Status ValidateExternalDataPath(const std::filesystem::path& model_path,
external_data_canonical, " ", "allowed directory: ", real_model_dir);
}

static Status ValidateOpenedExternalDataPath(const std::filesystem::path& model_path,
const std::filesystem::path& opened_path) {
const std::filesystem::path model_dir = model_path.empty() || model_path.parent_path().empty()
? std::filesystem::path{"."}
: model_path.parent_path();

std::filesystem::path model_dir_canonical;
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(model_dir, model_dir_canonical));
if (HasPathComponentPrefix(model_dir_canonical, opened_path)) {
return Status::OK();
}

std::error_code ec;
if (!model_path.empty() && std::filesystem::is_symlink(model_path, ec)) {
std::filesystem::path real_model_path;
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(model_path, real_model_path));
std::filesystem::path real_model_dir_canonical;
ORT_RETURN_IF_ERROR(WeaklyCanonicalPath(real_model_path.parent_path(), real_model_dir_canonical));
if (HasPathComponentPrefix(real_model_dir_canonical, opened_path)) {
return Status::OK();
}
}

return ORT_MAKE_STATUS(ONNXRUNTIME, FAIL,
"Opened external data file escapes model directory. Opened path: ",
opened_path, " allowed directory: ", model_dir_canonical);
}

Status GetExternalDataInfo(const ONNX_NAMESPACE::TensorProto& tensor_proto,
const std::filesystem::path& tensor_proto_dir,
std::basic_string<ORTCHAR_T>& external_file_path,
Expand Down Expand Up @@ -1624,18 +1652,17 @@ ORT_API(void, OrtUninitializeBuffer, _In_opt_ void* input, size_t input_len, enu
#endif

#if !defined(__wasm__)
static Status GetFileContent(const Env& env, const std::filesystem::path& file_path, FileOffsetType offset,
static Status GetFileContent(const RandomAccessFile& file, FileOffsetType offset,
size_t length, IAllocatorUniquePtr<void>& external_data) {
// query length if it is 0
if (length == 0) {
// The return type of std::filesystem::file_size is uintmax_t which could be bigger than size_t
length = narrow<size_t>(std::filesystem::file_size(file_path));
ORT_RETURN_IF_ERROR(file.GetLength(length));
}

// first, try to map into memory
{
Env::MappedMemoryPtr mapped_memory{};
auto status = env.MapFileIntoMemory(file_path.native().c_str(), offset, length, mapped_memory);
RandomAccessFile::MappedMemoryPtr mapped_memory{};
auto status = file.Map(offset, length, mapped_memory);
if (status.IsOK()) {
IAllocatorUniquePtr<void> raw_buffer(mapped_memory.release(),
mapped_memory.get_deleter());
Expand All @@ -1646,8 +1673,7 @@ static Status GetFileContent(const Env& env, const std::filesystem::path& file_p

// if that fails, try to copy
auto buffer = std::make_unique<char[]>(length);
ORT_RETURN_IF_ERROR(
env.ReadFileIntoBuffer(file_path.native().c_str(), offset, length, gsl::make_span(buffer.get(), length)));
ORT_RETURN_IF_ERROR(file.Read(offset, gsl::make_span(buffer.get(), length)));

IAllocatorUniquePtr<void> raw_buffer(buffer.release(), [](void* p) { delete[] reinterpret_cast<char*>(p); });
external_data.swap(raw_buffer);
Expand All @@ -1673,8 +1699,7 @@ static Status ValidateExternalFilePathForTensor(const ONNX_NAMESPACE::TensorProt
}

#if !defined(__wasm__)
static Status LoadPrepackedWeightsFromFile(const Env& env,
const std::filesystem::path& external_data_file_path,
static Status LoadPrepackedWeightsFromFile(const RandomAccessFile& file,
std::uintmax_t file_length,
const ExternalDataInfo::PrepackedInfos& prepacked_infos,
PrepackedWeightsForGraph& prepacked_info) {
Expand All @@ -1692,8 +1717,7 @@ static Status LoadPrepackedWeightsFromFile(const Env& env,
" is out of bounds and can not read in full");

IAllocatorUniquePtr<void> data_ptr;
ORT_RETURN_IF_ERROR(GetFileContent(env, external_data_file_path, blob_offset, blob_length,
data_ptr));
ORT_RETURN_IF_ERROR(GetFileContent(file, blob_offset, blob_length, data_ptr));
prepacked_weights.buffers_.push_back(std::move(data_ptr));
prepacked_weights.buffer_sizes_.push_back(blob_length);
}
Expand Down Expand Up @@ -1805,9 +1829,14 @@ Status GetExtDataFromTensorProto(const Env& env,
buffer.release();

#else
// The GetFileContent function doesn't report error if the requested data range is invalid. Therefore we need to
// manually check file size first.
std::uintmax_t file_length = std::filesystem::file_size(external_data_file_path);
std::unique_ptr<RandomAccessFile> external_data_file;
ORT_RETURN_IF_ERROR(env.OpenRandomAccessFile(external_data_file_path.c_str(), external_data_file));
PathString opened_path;
ORT_RETURN_IF_ERROR(external_data_file->GetCanonicalPath(opened_path));
ORT_RETURN_IF_ERROR(ValidateOpenedExternalDataPath(model_path, std::filesystem::path{opened_path}));
Comment thread
apsonawane marked this conversation as resolved.
Outdated

size_t file_length = 0;
ORT_RETURN_IF_ERROR(external_data_file->GetLength(file_length));
Comment thread
tianleiwu marked this conversation as resolved.

SafeInt<FileOffsetType> end_of_read(file_offset);
end_of_read += raw_data_safe_len;
Expand All @@ -1817,8 +1846,7 @@ Status GetExtDataFromTensorProto(const Env& env,
" are out of bounds or can not be read in full.");

IAllocatorUniquePtr<void> ext_data_buf;
ORT_RETURN_IF_ERROR(GetFileContent(env, external_data_file_path, file_offset, raw_data_safe_len,
ext_data_buf));
ORT_RETURN_IF_ERROR(GetFileContent(*external_data_file, file_offset, raw_data_safe_len, ext_data_buf));

// Data on disk is little endian
if constexpr (endian::native != endian::little) {
Expand Down Expand Up @@ -1847,7 +1875,7 @@ Status GetExtDataFromTensorProto(const Env& env,

if (prepacked_info != nullptr && !prepacked_infos->empty()) {
ORT_RETURN_IF_ERROR(LoadPrepackedWeightsFromFile(
env, external_data_file_path, file_length, *prepacked_infos, *prepacked_info));
*external_data_file, file_length, *prepacked_infos, *prepacked_info));
}
#endif
}
Expand Down Expand Up @@ -1885,9 +1913,14 @@ Status LoadPrepackedWeightsFromExternalData(const Env& env,
ORT_RETURN_IF(external_data_file_path == kTensorProtoNativeEndianMemoryAddressTag ||
external_data_file_path == kTensorProtoLittleEndianMemoryAddressTag,
"Pre-packed blobs cannot be restored from an in-memory external tensor.");
const std::uintmax_t file_length = std::filesystem::file_size(external_data_file_path);
return LoadPrepackedWeightsFromFile(
env, external_data_file_path, file_length, prepacked_infos, prepacked_info);
std::unique_ptr<RandomAccessFile> external_data_file;
ORT_RETURN_IF_ERROR(env.OpenRandomAccessFile(external_data_file_path.c_str(), external_data_file));
PathString opened_path;
ORT_RETURN_IF_ERROR(external_data_file->GetCanonicalPath(opened_path));
ORT_RETURN_IF_ERROR(ValidateOpenedExternalDataPath(model_path, std::filesystem::path{opened_path}));
Comment thread
apsonawane marked this conversation as resolved.
Outdated
size_t file_length = 0;
ORT_RETURN_IF_ERROR(external_data_file->GetLength(file_length));
return LoadPrepackedWeightsFromFile(*external_data_file, file_length, prepacked_infos, prepacked_info);
#endif
}

Expand Down Expand Up @@ -1916,7 +1949,16 @@ Status LoadExtDataToTensorFromTensorProto(const Env& env, const std::filesystem:
ORT_RETURN_IF(external_data_file_path == onnxruntime::utils::kTensorProtoLittleEndianMemoryAddressTag || external_data_file_path == onnxruntime::utils::kTensorProtoNativeEndianMemoryAddressTag,
"Memory address tag is not supported by custom external data loader.");

#if defined(__wasm__)
return ext_data_loader.LoadTensor(env, external_data_file_path, file_offset, raw_data_safe_len, tensor);
#else
std::unique_ptr<RandomAccessFile> external_data_file;
ORT_RETURN_IF_ERROR(env.OpenRandomAccessFile(external_data_file_path.c_str(), external_data_file));
PathString opened_path;
ORT_RETURN_IF_ERROR(external_data_file->GetCanonicalPath(opened_path));
ORT_RETURN_IF_ERROR(ValidateOpenedExternalDataPath(model_path, std::filesystem::path{opened_path}));
return ext_data_loader.LoadTensor(*external_data_file, file_offset, raw_data_safe_len, tensor);
#endif
}

#define CASE_PROTO(X, Y) \
Expand Down
10 changes: 9 additions & 1 deletion onnxruntime/core/platform/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -111,17 +111,25 @@ std::pair<int, std::string> GetErrnoInfo();
/**
* An owned open file supporting concurrent positional reads.
*
* Reads and length queries refer to the same file even if its pathname is replaced.
* Reads, mappings, length queries, and canonical-path queries refer to the same file even if its pathname is replaced.
* This is not a snapshot: callers must not modify the file in place while reading it.
* Keep the object alive until all callers have finished. Its destruction closes the file.
*/
class RandomAccessFile {
public:
using MappedMemoryPtr = std::unique_ptr<char[], std::function<void(void*)>>;

Comment thread
apsonawane marked this conversation as resolved.
Outdated
virtual ~RandomAccessFile() = default;

// Query the open file, leaving length unchanged on failure.
virtual common::Status GetLength(size_t& length) const = 0;

// Return the canonical path of this open file handle.
virtual common::Status GetCanonicalPath(PathString& path) const = 0;

// Map bytes from this open file without reopening its pathname.
virtual common::Status Map(FileOffsetType offset, size_t length, MappedMemoryPtr& mapped_memory) const = 0;

/**
* Fill buffer starting at offset without changing a shared file position.
* Concurrent calls must use disjoint buffers. Returns only after all I/O has completed.
Expand Down
76 changes: 76 additions & 0 deletions onnxruntime/core/platform/posix/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ limitations under the License.
#endif
#include <unistd.h>

#include <array>
#include <climits>
#include <filesystem>
#include <iostream>
#include <optional>
Expand All @@ -53,6 +55,9 @@ limitations under the License.
#if defined(__APPLE__) || defined(__FreeBSD__) || defined(__NetBSD__)
#include <sys/sysctl.h>
#endif
#if defined(__FreeBSD__)
#include <sys/user.h>
#endif

#include "core/common/common.h"
#include <gsl/gsl>
Expand Down Expand Up @@ -139,6 +144,77 @@ class PosixRandomAccessFile final : public RandomAccessFile {
return GetFileLength(descriptor_.Get(), length);
}

common::Status GetCanonicalPath(PathString& path) const override {
#if defined(F_GETPATH)
std::array<char, PATH_MAX> buffer{};
if (fcntl(descriptor_.Get(), F_GETPATH, buffer.data()) != 0) {
return ReportSystemError("fcntl(F_GETPATH)", path_);
}
path.assign(buffer.data());
return Status::OK();
#elif defined(__linux__) || defined(__ANDROID__)
const std::string fd_path = "/proc/self/fd/" + std::to_string(descriptor_.Get());
std::array<char, PATH_MAX> buffer{};
const auto length = readlink(fd_path.c_str(), buffer.data(), buffer.size() - 1);
if (length < 0) {
Comment thread
tianleiwu marked this conversation as resolved.
return ReportSystemError("readlink", fd_path);
}
path.assign(buffer.data(), static_cast<size_t>(length));
return Status::OK();
#elif defined(__FreeBSD__) && defined(F_KINFO)
struct kinfo_file file_info {};
Comment thread
apsonawane marked this conversation as resolved.
Outdated
if (fcntl(descriptor_.Get(), F_KINFO, &file_info) != 0) {
return ReportSystemError("fcntl(F_KINFO)", path_);
}
path.assign(file_info.kf_path);
return Status::OK();
#else
std::array<char, PATH_MAX> buffer{};
ORT_RETURN_IF_NOT(realpath(path_.c_str(), buffer.data()) != nullptr,
"realpath failed for ", path_, ": ", strerror(errno));

struct stat opened_stat {};
struct stat path_stat {};
Comment thread
apsonawane marked this conversation as resolved.
Outdated
ORT_RETURN_IF_NOT(fstat(descriptor_.Get(), &opened_stat) == 0,
"fstat failed for ", path_, ": ", strerror(errno));
ORT_RETURN_IF_NOT(stat(buffer.data(), &path_stat) == 0,
"stat failed for ", path_, ": ", strerror(errno));
ORT_RETURN_IF_NOT(opened_stat.st_dev == path_stat.st_dev && opened_stat.st_ino == path_stat.st_ino,
"The opened file no longer matches its canonical path: ", path_);
path.assign(buffer.data());
return Status::OK();
#endif
}

common::Status Map(FileOffsetType offset, size_t length, MappedMemoryPtr& mapped_memory) const override {
ORT_RETURN_IF_NOT(offset >= 0, "RandomAccessFile::Map: offset must be nonnegative.");
if (length == 0) {
mapped_memory = MappedMemoryPtr{};
return Status::OK();
}

size_t file_size = 0;
ORT_RETURN_IF_ERROR(GetLength(file_size));
const size_t requested_end = SafeInt<size_t>(offset) + length;
ORT_RETURN_IF(file_size < requested_end, "RandomAccessFile::Map: requested range exceeds file size.");

static const size_t page_size = narrow<size_t>(sysconf(_SC_PAGESIZE));
Comment thread
apsonawane marked this conversation as resolved.
Outdated
const FileOffsetType offset_to_page = offset % static_cast<FileOffsetType>(page_size);
const size_t mapped_length = SafeInt<size_t>(length) + static_cast<size_t>(offset_to_page);
const FileOffsetType mapped_offset = offset - offset_to_page;
void* const mapped_base =
mmap(nullptr, mapped_length, PROT_READ | PROT_WRITE, MAP_PRIVATE, descriptor_.Get(), mapped_offset);
Comment thread
Copilot marked this conversation as resolved.
if (mapped_base == MAP_FAILED) {
return ReportSystemError("mmap", path_);
}

mapped_memory = MappedMemoryPtr{reinterpret_cast<char*>(mapped_base) + offset_to_page,
[mapped_base, mapped_length](void*) {
UnmapFile(mapped_base, mapped_length);
}};
return Status::OK();
}

common::Status Read(FileOffsetType offset, gsl::span<char> buffer) const override {
if (offset < 0) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "RandomAccessFile::Read: offset must be nonnegative.");
Expand Down
76 changes: 76 additions & 0 deletions onnxruntime/core/platform/windows/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,82 @@ class WindowsRandomAccessFile final : public RandomAccessFile {
return Status::OK();
}

Status GetCanonicalPath(PathString& path) const override {
auto get_final_path = [&](DWORD flags, PathString& result) -> DWORD {
std::vector<PathChar> buffer(MAX_PATH);
const DWORD length = GetFinalPathNameByHandleW(
file_handle_.get(), buffer.data(), static_cast<DWORD>(buffer.size()), flags);
if (length == 0) {
return GetLastError();
}
if (length >= buffer.size()) {
buffer.resize(length);
const DWORD resized_length = GetFinalPathNameByHandleW(
file_handle_.get(), buffer.data(), static_cast<DWORD>(buffer.size()), flags);
if (resized_length == 0 || resized_length >= buffer.size()) {
return resized_length == 0 ? GetLastError() : ERROR_INSUFFICIENT_BUFFER;
}
result.assign(buffer.data(), resized_length);
} else {
result.assign(buffer.data(), length);
}
return ERROR_SUCCESS;
};

DWORD error = get_final_path(FILE_NAME_NORMALIZED | VOLUME_NAME_DOS, path);
if (error == ERROR_ACCESS_DENIED) {
error = get_final_path(FILE_NAME_NORMALIZED | VOLUME_NAME_NT, path);
if (error == ERROR_SUCCESS) {
path.insert(0, ORT_TSTR(R"(\\?\GLOBALROOT)"));
return Status::OK();
}
}
ORT_RETURN_IF_NOT(error == ERROR_SUCCESS, "GetFinalPathNameByHandleW failed: ", error);

if (path.find(ORT_TSTR(R"(\\?\)")) == 0) {
if (path.size() > 6 && path[5] == ORT_TSTR(':')) {
path.erase(0, 4);
} else if (path.find(ORT_TSTR(R"(UNC\)"), 4) == 4) {
path.erase(2, 6);
}
}
return Status::OK();
}

Status Map(FileOffsetType offset, size_t length, MappedMemoryPtr& mapped_memory) const override {
ORT_RETURN_IF_NOT(offset >= 0, "RandomAccessFile::Map: offset < 0");
if (length == 0) {
mapped_memory = MappedMemoryPtr{};
return Status::OK();
}

size_t file_size = 0;
ORT_RETURN_IF_ERROR(GetLength(file_size));
const size_t requested_end = SafeInt<size_t>(offset) + length;
ORT_RETURN_IF(file_size < requested_end, "RandomAccessFile::Map: requested range exceeds file size.");

wil::unique_handle mapping{CreateFileMappingW(file_handle_.get(), nullptr, PAGE_READONLY, 0, 0, nullptr)};
ORT_RETURN_IF(mapping.get() == nullptr,
"CreateFileMappingW failed: ", GetLastError());

SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
const FileOffsetType offset_to_granularity =
offset % static_cast<FileOffsetType>(sysinfo.dwAllocationGranularity);
const SIZE_T mapped_length = SafeInt<SIZE_T>(offset_to_granularity) + length;
const FileOffsetType mapped_offset = offset - offset_to_granularity;
const uint64_t mapped_offset_u64 = static_cast<uint64_t>(mapped_offset);
void* const mapped_base = MapViewOfFile(mapping.get(), FILE_MAP_READ,
static_cast<DWORD>(mapped_offset_u64 >> 32),
static_cast<DWORD>(mapped_offset_u64 & 0xFFFFFFFF),
mapped_length);
ORT_RETURN_IF(mapped_base == nullptr, "MapViewOfFile failed: ", GetLastError());

mapped_memory = MappedMemoryPtr{reinterpret_cast<char*>(mapped_base) + offset_to_granularity,
[mapped_base](void*) { UnmapViewOfFile(mapped_base); }};
return Status::OK();
}

Status Read(FileOffsetType offset, gsl::span<char> buffer) const override {
if (offset < 0) {
return ORT_MAKE_STATUS(ONNXRUNTIME, INVALID_ARGUMENT, "RandomAccessFile: offset < 0");
Expand Down
Loading