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