1 /* Copyright 2016 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/tools/android/inference_interface/asset_manager_filesystem.h"
17
18 #include <unistd.h>
19
20 #include "tensorflow/core/lib/strings/str_util.h"
21 #include "tensorflow/core/platform/env.h"
22 #include "tensorflow/core/platform/file_system_helper.h"
23
24 namespace tensorflow {
25 namespace {
26
RemoveSuffix(const string & name,const string & suffix)27 string RemoveSuffix(const string& name, const string& suffix) {
28 string output(name);
29 StringPiece piece(output);
30 absl::ConsumeSuffix(&piece, suffix);
31 return string(piece);
32 }
33
34 // Closes the given AAsset when variable is destructed.
35 class ScopedAsset {
36 public:
ScopedAsset(AAsset * asset)37 ScopedAsset(AAsset* asset) : asset_(asset) {}
~ScopedAsset()38 ~ScopedAsset() {
39 if (asset_ != nullptr) {
40 AAsset_close(asset_);
41 }
42 }
43
get() const44 AAsset* get() const { return asset_; }
45
46 private:
47 AAsset* asset_;
48 };
49
50 // Closes the given AAssetDir when variable is destructed.
51 class ScopedAssetDir {
52 public:
ScopedAssetDir(AAssetDir * asset_dir)53 ScopedAssetDir(AAssetDir* asset_dir) : asset_dir_(asset_dir) {}
~ScopedAssetDir()54 ~ScopedAssetDir() {
55 if (asset_dir_ != nullptr) {
56 AAssetDir_close(asset_dir_);
57 }
58 }
59
get() const60 AAssetDir* get() const { return asset_dir_; }
61
62 private:
63 AAssetDir* asset_dir_;
64 };
65
66 class ReadOnlyMemoryRegionFromAsset : public ReadOnlyMemoryRegion {
67 public:
ReadOnlyMemoryRegionFromAsset(std::unique_ptr<char[]> data,uint64 length)68 ReadOnlyMemoryRegionFromAsset(std::unique_ptr<char[]> data, uint64 length)
69 : data_(std::move(data)), length_(length) {}
70 ~ReadOnlyMemoryRegionFromAsset() override = default;
71
data()72 const void* data() override { return reinterpret_cast<void*>(data_.get()); }
length()73 uint64 length() override { return length_; }
74
75 private:
76 std::unique_ptr<char[]> data_;
77 uint64 length_;
78 };
79
80 // Note that AAssets are not thread-safe and cannot be used across threads.
81 // However, AAssetManager is. Because RandomAccessFile must be thread-safe and
82 // used across threads, new AAssets must be created for every access.
83 // TODO(tylerrhodes): is there a more efficient way to do this?
84 class RandomAccessFileFromAsset : public RandomAccessFile {
85 public:
RandomAccessFileFromAsset(AAssetManager * asset_manager,const string & name)86 RandomAccessFileFromAsset(AAssetManager* asset_manager, const string& name)
87 : asset_manager_(asset_manager), file_name_(name) {}
88 ~RandomAccessFileFromAsset() override = default;
89
Read(uint64 offset,size_t to_read,StringPiece * result,char * scratch) const90 Status Read(uint64 offset, size_t to_read, StringPiece* result,
91 char* scratch) const override {
92 auto asset = ScopedAsset(AAssetManager_open(
93 asset_manager_, file_name_.c_str(), AASSET_MODE_RANDOM));
94 if (asset.get() == nullptr) {
95 return errors::NotFound("File ", file_name_, " not found.");
96 }
97
98 off64_t new_offset = AAsset_seek64(asset.get(), offset, SEEK_SET);
99 off64_t length = AAsset_getLength64(asset.get());
100 if (new_offset < 0) {
101 *result = StringPiece(scratch, 0);
102 return errors::OutOfRange("Read after file end.");
103 }
104 const off64_t region_left =
105 std::min(length - new_offset, static_cast<off64_t>(to_read));
106 int read = AAsset_read(asset.get(), scratch, region_left);
107 if (read < 0) {
108 return errors::Internal("Error reading from asset.");
109 }
110 *result = StringPiece(scratch, region_left);
111 return (region_left == to_read)
112 ? Status::OK()
113 : errors::OutOfRange("Read less bytes than requested.");
114 }
115
116 private:
117 AAssetManager* asset_manager_;
118 string file_name_;
119 };
120
121 } // namespace
122
AssetManagerFileSystem(AAssetManager * asset_manager,const string & prefix)123 AssetManagerFileSystem::AssetManagerFileSystem(AAssetManager* asset_manager,
124 const string& prefix)
125 : asset_manager_(asset_manager), prefix_(prefix) {}
126
FileExists(const string & fname,TransactionToken * token)127 Status AssetManagerFileSystem::FileExists(const string& fname,
128 TransactionToken* token) {
129 string path = RemoveAssetPrefix(fname);
130 auto asset = ScopedAsset(
131 AAssetManager_open(asset_manager_, path.c_str(), AASSET_MODE_RANDOM));
132 if (asset.get() == nullptr) {
133 return errors::NotFound("File ", fname, " not found.");
134 }
135 return Status::OK();
136 }
137
NewRandomAccessFile(const string & fname,TransactionToken * token,std::unique_ptr<RandomAccessFile> * result)138 Status AssetManagerFileSystem::NewRandomAccessFile(
139 const string& fname, TransactionToken* token,
140 std::unique_ptr<RandomAccessFile>* result) {
141 string path = RemoveAssetPrefix(fname);
142 auto asset = ScopedAsset(
143 AAssetManager_open(asset_manager_, path.c_str(), AASSET_MODE_RANDOM));
144 if (asset.get() == nullptr) {
145 return errors::NotFound("File ", fname, " not found.");
146 }
147 result->reset(new RandomAccessFileFromAsset(asset_manager_, path));
148 return Status::OK();
149 }
150
NewReadOnlyMemoryRegionFromFile(const string & fname,TransactionToken * token,std::unique_ptr<ReadOnlyMemoryRegion> * result)151 Status AssetManagerFileSystem::NewReadOnlyMemoryRegionFromFile(
152 const string& fname, TransactionToken* token,
153 std::unique_ptr<ReadOnlyMemoryRegion>* result) {
154 string path = RemoveAssetPrefix(fname);
155 auto asset = ScopedAsset(
156 AAssetManager_open(asset_manager_, path.c_str(), AASSET_MODE_STREAMING));
157 if (asset.get() == nullptr) {
158 return errors::NotFound("File ", fname, " not found.");
159 }
160
161 off64_t start, length;
162 int fd = AAsset_openFileDescriptor64(asset.get(), &start, &length);
163 std::unique_ptr<char[]> data;
164 if (fd >= 0) {
165 data.reset(new char[length]);
166 ssize_t result = pread(fd, data.get(), length, start);
167 if (result < 0) {
168 return errors::Internal("Error reading from file ", fname,
169 " using 'read': ", result);
170 }
171 if (result != length) {
172 return errors::Internal("Expected size does not match size read: ",
173 "Expected ", length, " vs. read ", result);
174 }
175 close(fd);
176 } else {
177 length = AAsset_getLength64(asset.get());
178 data.reset(new char[length]);
179 const void* asset_buffer = AAsset_getBuffer(asset.get());
180 if (asset_buffer == nullptr) {
181 return errors::Internal("Error reading ", fname, " from asset manager.");
182 }
183 memcpy(data.get(), asset_buffer, length);
184 }
185 result->reset(new ReadOnlyMemoryRegionFromAsset(std::move(data), length));
186 return Status::OK();
187 }
188
GetChildren(const string & prefixed_dir,TransactionToken * token,std::vector<string> * r)189 Status AssetManagerFileSystem::GetChildren(const string& prefixed_dir,
190 TransactionToken* token,
191 std::vector<string>* r) {
192 std::string path = NormalizeDirectoryPath(prefixed_dir);
193 auto dir =
194 ScopedAssetDir(AAssetManager_openDir(asset_manager_, path.c_str()));
195 if (dir.get() == nullptr) {
196 return errors::NotFound("Directory ", prefixed_dir, " not found.");
197 }
198 const char* next_file = AAssetDir_getNextFileName(dir.get());
199 while (next_file != nullptr) {
200 r->push_back(next_file);
201 next_file = AAssetDir_getNextFileName(dir.get());
202 }
203 return Status::OK();
204 }
205
GetFileSize(const string & fname,TransactionToken * token,uint64 * s)206 Status AssetManagerFileSystem::GetFileSize(const string& fname,
207 TransactionToken* token, uint64* s) {
208 // If fname corresponds to a directory, return early. It doesn't map to an
209 // AAsset, and would otherwise return NotFound.
210 if (DirectoryExists(fname)) {
211 *s = 0;
212 return Status::OK();
213 }
214 string path = RemoveAssetPrefix(fname);
215 auto asset = ScopedAsset(
216 AAssetManager_open(asset_manager_, path.c_str(), AASSET_MODE_RANDOM));
217 if (asset.get() == nullptr) {
218 return errors::NotFound("File ", fname, " not found.");
219 }
220 *s = AAsset_getLength64(asset.get());
221 return Status::OK();
222 }
223
Stat(const string & fname,TransactionToken * token,FileStatistics * stat)224 Status AssetManagerFileSystem::Stat(const string& fname,
225 TransactionToken* token,
226 FileStatistics* stat) {
227 uint64 size;
228 stat->is_directory = DirectoryExists(fname);
229 TF_RETURN_IF_ERROR(GetFileSize(fname, &size));
230 stat->length = size;
231 return Status::OK();
232 }
233
NormalizeDirectoryPath(const string & fname)234 string AssetManagerFileSystem::NormalizeDirectoryPath(const string& fname) {
235 return RemoveSuffix(RemoveAssetPrefix(fname), "/");
236 }
237
RemoveAssetPrefix(const string & name)238 string AssetManagerFileSystem::RemoveAssetPrefix(const string& name) {
239 StringPiece piece(name);
240 absl::ConsumePrefix(&piece, prefix_);
241 return string(piece);
242 }
243
DirectoryExists(const std::string & fname)244 bool AssetManagerFileSystem::DirectoryExists(const std::string& fname) {
245 std::string path = NormalizeDirectoryPath(fname);
246 auto dir =
247 ScopedAssetDir(AAssetManager_openDir(asset_manager_, path.c_str()));
248 // Note that openDir will return something even if the directory doesn't
249 // exist. Therefore, we need to ensure one file exists in the folder.
250 return AAssetDir_getNextFileName(dir.get()) != NULL;
251 }
252
GetMatchingPaths(const string & pattern,TransactionToken * token,std::vector<string> * results)253 Status AssetManagerFileSystem::GetMatchingPaths(const string& pattern,
254 TransactionToken* token,
255 std::vector<string>* results) {
256 return internal::GetMatchingPaths(this, Env::Default(), pattern, results);
257 }
258
NewWritableFile(const string & fname,TransactionToken * token,std::unique_ptr<WritableFile> * result)259 Status AssetManagerFileSystem::NewWritableFile(
260 const string& fname, TransactionToken* token,
261 std::unique_ptr<WritableFile>* result) {
262 return errors::Unimplemented("Asset storage is read only.");
263 }
NewAppendableFile(const string & fname,TransactionToken * token,std::unique_ptr<WritableFile> * result)264 Status AssetManagerFileSystem::NewAppendableFile(
265 const string& fname, TransactionToken* token,
266 std::unique_ptr<WritableFile>* result) {
267 return errors::Unimplemented("Asset storage is read only.");
268 }
DeleteFile(const string & f,TransactionToken * token)269 Status AssetManagerFileSystem::DeleteFile(const string& f,
270 TransactionToken* token) {
271 return errors::Unimplemented("Asset storage is read only.");
272 }
CreateDir(const string & d,TransactionToken * token)273 Status AssetManagerFileSystem::CreateDir(const string& d,
274 TransactionToken* token) {
275 return errors::Unimplemented("Asset storage is read only.");
276 }
DeleteDir(const string & d,TransactionToken * token)277 Status AssetManagerFileSystem::DeleteDir(const string& d,
278 TransactionToken* token) {
279 return errors::Unimplemented("Asset storage is read only.");
280 }
RenameFile(const string & s,const string & t,TransactionToken * token)281 Status AssetManagerFileSystem::RenameFile(const string& s, const string& t,
282 TransactionToken* token) {
283 return errors::Unimplemented("Asset storage is read only.");
284 }
285
286 } // namespace tensorflow
287