Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
13 changes: 10 additions & 3 deletions onnxruntime/core/framework/external_data_loader.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,21 @@ 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 {
ORT_NOT_IMPLEMENTED(__FUNCTION__, " is not implemented");
}
};
#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");
}
}; // namespace onnxruntime
Comment thread
apsonawane marked this conversation as resolved.
Resolved

#if defined(__wasm__)

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.
Resolved

size_t file_length = 0;
ORT_RETURN_IF_ERROR(external_data_file->GetLength(file_length));
Comment thread
tianleiwu marked this conversation as resolved.
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.
Resolved
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.
Resolved
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.
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{};
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{};
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.
Resolved
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.
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