1 /* Copyright 2020 The TensorFlow Authors. All Rights Reserved.
2
3 Licensed under the Apache License, Version 2.0 (the "License");
4 you may not use this file except in compliance with the License.
5 You may obtain a copy of the License at
6
7 http://www.apache.org/licenses/LICENSE-2.0
8
9 Unless required by applicable law or agreed to in writing, software
10 distributed under the License is distributed on an "AS IS" BASIS,
11 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 See the License for the specific language governing permissions and
13 limitations under the License.
14 ==============================================================================*/
15
16 #include "tensorflow_lite_support/cc/task/core/external_file_handler.h"
17
18 #include <errno.h>
19 #include <fcntl.h>
20 #include <stddef.h>
21 #include <sys/mman.h>
22 #include <unistd.h>
23
24 #include <memory>
25 #include <string>
26
27 #include "absl/memory/memory.h"
28 #include "absl/strings/str_format.h"
29 #include "tensorflow_lite_support/cc/common.h"
30 #include "tensorflow_lite_support/cc/port/statusor.h"
31 #include "tensorflow_lite_support/cc/port/status_macros.h"
32
33 namespace tflite {
34 namespace task {
35 namespace core {
36 namespace {
37
38 using ::absl::StatusCode;
39 using ::tflite::support::CreateStatusWithPayload;
40 using ::tflite::support::StatusOr;
41 using ::tflite::support::TfLiteSupportStatus;
42
43 // Gets the offset aligned to page size for mapping given files into memory by
44 // file descriptor correctly, as according to mmap(2), the offset used in mmap
45 // must be a multiple of sysconf(_SC_PAGE_SIZE).
GetPageSizeAlignedOffset(int64 offset)46 int64 GetPageSizeAlignedOffset(int64 offset) {
47 int64 aligned_offset = offset;
48 int64 page_size = sysconf(_SC_PAGE_SIZE);
49 if (offset % page_size != 0) {
50 aligned_offset = offset / page_size * page_size;
51 }
52 return aligned_offset;
53 }
54
55 } // namespace
56
57 /* static */
58 StatusOr<std::unique_ptr<ExternalFileHandler>>
CreateFromExternalFile(const ExternalFile * external_file)59 ExternalFileHandler::CreateFromExternalFile(const ExternalFile* external_file) {
60 // Use absl::WrapUnique() to call private constructor:
61 // https://abseil.io/tips/126.
62 std::unique_ptr<ExternalFileHandler> handler =
63 absl::WrapUnique(new ExternalFileHandler(external_file));
64
65 RETURN_IF_ERROR(handler->MapExternalFile());
66
67 return handler;
68 }
69
MapExternalFile()70 absl::Status ExternalFileHandler::MapExternalFile() {
71 if (!external_file_.file_content().empty()) {
72 return absl::OkStatus();
73 }
74 if (external_file_.file_name().empty() &&
75 !external_file_.has_file_descriptor_meta()) {
76 return CreateStatusWithPayload(
77 StatusCode::kInvalidArgument,
78 "ExternalFile must specify at least one of 'file_content', file_name' "
79 "or 'file_descriptor_meta'.",
80 TfLiteSupportStatus::kInvalidArgumentError);
81 }
82 // Obtain file descriptor, offset and size.
83 int fd = -1;
84 if (!external_file_.file_name().empty()) {
85 owned_fd_ = open(external_file_.file_name().c_str(), O_RDONLY);
86 if (owned_fd_ < 0) {
87 const std::string error_message = absl::StrFormat(
88 "Unable to open file at %s", external_file_.file_name());
89 switch (errno) {
90 case ENOENT:
91 return CreateStatusWithPayload(
92 StatusCode::kNotFound, error_message,
93 TfLiteSupportStatus::kFileNotFoundError);
94 case EACCES:
95 case EPERM:
96 return CreateStatusWithPayload(
97 StatusCode::kPermissionDenied, error_message,
98 TfLiteSupportStatus::kFilePermissionDeniedError);
99 case EINTR:
100 return CreateStatusWithPayload(StatusCode::kUnavailable,
101 error_message,
102 TfLiteSupportStatus::kFileReadError);
103 case EBADF:
104 return CreateStatusWithPayload(StatusCode::kFailedPrecondition,
105 error_message,
106 TfLiteSupportStatus::kFileReadError);
107 default:
108 return CreateStatusWithPayload(
109 StatusCode::kUnknown,
110 absl::StrFormat("%s, errno=%d", error_message, errno),
111 TfLiteSupportStatus::kFileReadError);
112 }
113 }
114 fd = owned_fd_;
115 } else {
116 fd = external_file_.file_descriptor_meta().fd();
117 if (fd < 0) {
118 return CreateStatusWithPayload(
119 StatusCode::kInvalidArgument,
120 absl::StrFormat("Provided file descriptor is invalid: %d < 0", fd),
121 TfLiteSupportStatus::kInvalidArgumentError);
122 }
123 buffer_offset_ = external_file_.file_descriptor_meta().offset();
124 buffer_size_ = external_file_.file_descriptor_meta().length();
125 }
126 // Get actual file size. Always use 0 as offset to lseek(2) to get the actual
127 // file size, as SEEK_END returns the size of the file *plus* offset.
128 size_t file_size = lseek(fd, /*offset=*/0, SEEK_END);
129 if (file_size <= 0) {
130 return CreateStatusWithPayload(
131 StatusCode::kUnknown,
132 absl::StrFormat("Unable to get file size, errno=%d", errno),
133 TfLiteSupportStatus::kFileReadError);
134 }
135 // Deduce buffer size if not explicitly provided through file descriptor.
136 if (buffer_size_ <= 0) {
137 buffer_size_ = file_size - buffer_offset_;
138 }
139 // Check for out of range issues.
140 if (file_size <= buffer_offset_) {
141 return CreateStatusWithPayload(
142 StatusCode::kInvalidArgument,
143 absl::StrFormat("Provided file offset (%d) exceeds or matches actual "
144 "file length (%d)",
145 buffer_offset_, file_size),
146 TfLiteSupportStatus::kInvalidArgumentError);
147 }
148 if (file_size < buffer_size_ + buffer_offset_) {
149 return CreateStatusWithPayload(
150 StatusCode::kInvalidArgument,
151 absl::StrFormat("Provided file length + offset (%d) exceeds actual "
152 "file length (%d)",
153 buffer_size_ + buffer_offset_, file_size),
154 TfLiteSupportStatus::kInvalidArgumentError);
155 }
156 // If buffer_offset_ is not multiple of sysconf(_SC_PAGE_SIZE), align with
157 // extra leading bytes and adjust buffer_size_ to account for the extra
158 // leading bytes.
159 buffer_aligned_offset_ = GetPageSizeAlignedOffset(buffer_offset_);
160 buffer_aligned_size_ = buffer_size_ + buffer_offset_ - buffer_aligned_offset_;
161 // Map into memory.
162 buffer_ = mmap(/*addr=*/nullptr, buffer_aligned_size_, PROT_READ, MAP_SHARED,
163 fd, buffer_aligned_offset_);
164 if (buffer_ == MAP_FAILED) {
165 return CreateStatusWithPayload(
166 StatusCode::kUnknown,
167 absl::StrFormat("Unable to map file to memory buffer, errno=%d", errno),
168 TfLiteSupportStatus::kFileMmapError);
169 }
170 return absl::OkStatus();
171 }
172
GetFileContent()173 absl::string_view ExternalFileHandler::GetFileContent() {
174 if (!external_file_.file_content().empty()) {
175 return external_file_.file_content();
176 } else {
177 return absl::string_view(static_cast<const char*>(buffer_) +
178 buffer_offset_ - buffer_aligned_offset_,
179 buffer_size_);
180 }
181 }
182
~ExternalFileHandler()183 ExternalFileHandler::~ExternalFileHandler() {
184 if (buffer_ != MAP_FAILED) {
185 munmap(buffer_, buffer_aligned_size_);
186 }
187 if (owned_fd_ >= 0) {
188 close(owned_fd_);
189 }
190 }
191
192 } // namespace core
193 } // namespace task
194 } // namespace tflite
195