1 /*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #include <libfiemap/fiemap_writer.h>
18
19 #include <dirent.h>
20 #include <fcntl.h>
21 #include <linux/fs.h>
22 #include <stdio.h>
23 #include <sys/ioctl.h>
24 #include <sys/stat.h>
25 #include <sys/sysmacros.h>
26 #include <sys/types.h>
27 #include <sys/vfs.h>
28 #include <unistd.h>
29
30 #include <limits>
31 #include <string>
32 #include <utility>
33 #include <vector>
34
35 #include <android-base/file.h>
36 #include <android-base/logging.h>
37 #include <android-base/stringprintf.h>
38 #include <android-base/strings.h>
39 #include <android-base/unique_fd.h>
40 #include <libdm/dm.h>
41 #include "utility.h"
42
43 namespace android {
44 namespace fiemap {
45
46 using namespace android::dm;
47
48 // We cap the maximum number of extents as a robustness measure.
49 static constexpr uint32_t kMaxExtents = 50000;
50
51 // TODO: Fallback to using fibmap if FIEMAP_EXTENT_MERGED is set.
52 static constexpr const uint32_t kUnsupportedExtentFlags =
53 FIEMAP_EXTENT_UNKNOWN | FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_DELALLOC |
54 FIEMAP_EXTENT_NOT_ALIGNED | FIEMAP_EXTENT_DATA_INLINE | FIEMAP_EXTENT_DATA_TAIL |
55 FIEMAP_EXTENT_UNWRITTEN | FIEMAP_EXTENT_SHARED;
56
57 // Large file support must be enabled.
58 static_assert(sizeof(off_t) == sizeof(uint64_t));
59
cleanup(const std::string & file_path,bool created)60 static inline void cleanup(const std::string& file_path, bool created) {
61 if (created) {
62 unlink(file_path.c_str());
63 }
64 }
65
ValidateDmTarget(const DeviceMapper::TargetInfo & target)66 static bool ValidateDmTarget(const DeviceMapper::TargetInfo& target) {
67 const auto& entry = target.spec;
68 if (entry.sector_start != 0) {
69 LOG(INFO) << "Stopping at target with non-zero starting sector";
70 return false;
71 }
72
73 auto target_type = DeviceMapper::GetTargetType(entry);
74 if (target_type == "bow" || target_type == "default-key" || target_type == "crypt") {
75 return true;
76 }
77 if (target_type == "linear") {
78 auto pieces = android::base::Split(target.data, " ");
79 if (pieces[1] != "0") {
80 LOG(INFO) << "Stopping at complex linear target with non-zero starting sector: "
81 << pieces[1];
82 return false;
83 }
84 return true;
85 }
86
87 LOG(INFO) << "Stopping at complex target type " << target_type;
88 return false;
89 }
90
DeviceMapperStackPop(const std::string & bdev,std::string * bdev_raw)91 static bool DeviceMapperStackPop(const std::string& bdev, std::string* bdev_raw) {
92 *bdev_raw = bdev;
93
94 if (!::android::base::StartsWith(bdev, "dm-")) {
95 // We are at the bottom of the device mapper stack.
96 return true;
97 }
98
99 // Get the device name.
100 auto dm_name_file = "/sys/block/" + bdev + "/dm/name";
101 std::string dm_name;
102 if (!android::base::ReadFileToString(dm_name_file, &dm_name)) {
103 PLOG(ERROR) << "Could not read file: " << dm_name_file;
104 return false;
105 }
106 dm_name = android::base::Trim(dm_name);
107
108 auto& dm = DeviceMapper::Instance();
109 std::vector<DeviceMapper::TargetInfo> table;
110 if (!dm.GetTableInfo(dm_name, &table)) {
111 LOG(ERROR) << "Could not read device-mapper table for " << dm_name << " at " << bdev;
112 return false;
113 }
114
115 // The purpose of libfiemap is to provide an extent-based view into
116 // a file. This is difficult if devices are not layered in a 1:1 manner;
117 // we would have to translate and break up extents based on the actual
118 // block mapping. Since this is too complex, we simply stop processing
119 // the device-mapper stack if we encounter a complex case.
120 //
121 // It is up to the caller to decide whether stopping at a virtual block
122 // device is allowable. In most cases it is not, because we want either
123 // "userdata" or an external volume. It is useful for tests however.
124 // Callers can check by comparing the device number to that of userdata,
125 // or by checking whether is a device-mapper node.
126 if (table.size() > 1) {
127 LOG(INFO) << "Stopping at complex table for " << dm_name << " at " << bdev;
128 return true;
129 }
130 if (!ValidateDmTarget(table[0])) {
131 return true;
132 }
133
134 auto dm_leaf_dir = "/sys/block/" + bdev + "/slaves";
135 auto d = std::unique_ptr<DIR, decltype(&closedir)>(opendir(dm_leaf_dir.c_str()), closedir);
136 if (d == nullptr) {
137 PLOG(ERROR) << "Failed to open: " << dm_leaf_dir;
138 return false;
139 }
140
141 struct dirent* de;
142 uint32_t num_leaves = 0;
143 std::string bdev_next = "";
144 while ((de = readdir(d.get())) != nullptr) {
145 if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, "..")) {
146 continue;
147 }
148
149 // We set the first name we find here
150 if (bdev_next.empty()) {
151 bdev_next = de->d_name;
152 }
153 num_leaves++;
154 }
155
156 // if we have more than one leaves, we return immediately. We can't continue to create the
157 // file since we don't know how to write it out using fiemap, so it will be readable via the
158 // underlying block devices later. The reader will also have to construct the same device mapper
159 // target in order read the file out.
160 if (num_leaves > 1) {
161 LOG(ERROR) << "Found " << num_leaves << " leaf block devices under device mapper device "
162 << bdev;
163 return false;
164 }
165
166 // recursively call with the block device we found in order to pop the device mapper stack.
167 return DeviceMapperStackPop(bdev_next, bdev_raw);
168 }
169
GetBlockDeviceForFile(const std::string & file_path,std::string * bdev_path,bool * uses_dm)170 bool FiemapWriter::GetBlockDeviceForFile(const std::string& file_path, std::string* bdev_path,
171 bool* uses_dm) {
172 struct stat sb;
173 if (stat(file_path.c_str(), &sb)) {
174 PLOG(ERROR) << "Failed to get stat for: " << file_path;
175 return false;
176 }
177
178 std::string bdev;
179 if (!BlockDeviceToName(major(sb.st_dev), minor(sb.st_dev), &bdev)) {
180 LOG(ERROR) << "Failed to get block device name for " << major(sb.st_dev) << ":"
181 << minor(sb.st_dev);
182 return false;
183 }
184
185 std::string bdev_raw;
186 if (!DeviceMapperStackPop(bdev, &bdev_raw)) {
187 LOG(ERROR) << "Failed to get the bottom of the device mapper stack for device: " << bdev;
188 return false;
189 }
190
191 if (uses_dm) {
192 *uses_dm = (bdev_raw != bdev);
193 }
194
195 LOG(DEBUG) << "Popped device (" << bdev_raw << ") from device mapper stack starting with ("
196 << bdev << ")";
197
198 *bdev_path = ::android::base::StringPrintf("/dev/block/%s", bdev_raw.c_str());
199
200 // Make sure we are talking to a block device before calling it a success.
201 if (stat(bdev_path->c_str(), &sb)) {
202 PLOG(ERROR) << "Failed to get stat for block device: " << *bdev_path;
203 return false;
204 }
205
206 if ((sb.st_mode & S_IFMT) != S_IFBLK) {
207 PLOG(ERROR) << "File: " << *bdev_path << " is not a block device";
208 return false;
209 }
210
211 return true;
212 }
213
GetBlockDeviceSize(int bdev_fd,const std::string & bdev_path,uint64_t * bdev_size)214 static bool GetBlockDeviceSize(int bdev_fd, const std::string& bdev_path, uint64_t* bdev_size) {
215 uint64_t size_in_bytes = 0;
216 if (ioctl(bdev_fd, BLKGETSIZE64, &size_in_bytes)) {
217 PLOG(ERROR) << "Failed to get total size for: " << bdev_path;
218 return false;
219 }
220
221 *bdev_size = size_in_bytes;
222
223 return true;
224 }
225
GetFileSize(const std::string & file_path)226 static uint64_t GetFileSize(const std::string& file_path) {
227 struct stat sb;
228 if (stat(file_path.c_str(), &sb)) {
229 PLOG(ERROR) << "Failed to get size for file: " << file_path;
230 return 0;
231 }
232
233 return sb.st_size;
234 }
235
PerformFileChecks(const std::string & file_path,uint64_t * blocksz,uint32_t * fs_type)236 static bool PerformFileChecks(const std::string& file_path, uint64_t* blocksz, uint32_t* fs_type) {
237 struct statfs64 sfs;
238 if (statfs64(file_path.c_str(), &sfs)) {
239 PLOG(ERROR) << "Failed to read file system status at: " << file_path;
240 return false;
241 }
242
243 if (!sfs.f_bsize) {
244 LOG(ERROR) << "Unsupported block size: " << sfs.f_bsize;
245 return false;
246 }
247
248 // Check if the filesystem is of supported types.
249 // Only ext4, f2fs, and vfat are tested and supported.
250 switch (sfs.f_type) {
251 case EXT4_SUPER_MAGIC:
252 case F2FS_SUPER_MAGIC:
253 case MSDOS_SUPER_MAGIC:
254 break;
255 default:
256 LOG(ERROR) << "Unsupported file system type: 0x" << std::hex << sfs.f_type;
257 return false;
258 }
259
260 *blocksz = sfs.f_bsize;
261 *fs_type = sfs.f_type;
262 return true;
263 }
264
FallocateFallback(int file_fd,uint64_t block_size,uint64_t file_size,const std::string & file_path,const std::function<bool (uint64_t,uint64_t)> & on_progress)265 static FiemapStatus FallocateFallback(int file_fd, uint64_t block_size, uint64_t file_size,
266 const std::string& file_path,
267 const std::function<bool(uint64_t, uint64_t)>& on_progress) {
268 // Even though this is much faster than writing zeroes, it is still slow
269 // enough that we need to fire the progress callback periodically. To
270 // easily achieve this, we seek in chunks. We use 1000 chunks since
271 // normally we only fire the callback on 1/1000th increments.
272 uint64_t bytes_per_chunk = std::max(file_size / 1000, block_size);
273
274 // Seek just to the end of each chunk and write a single byte, causing
275 // the filesystem to allocate blocks.
276 off_t cursor = 0;
277 off_t end = static_cast<off_t>(file_size);
278 while (cursor < end) {
279 cursor = std::min(static_cast<off_t>(cursor + bytes_per_chunk), end);
280 auto rv = TEMP_FAILURE_RETRY(lseek(file_fd, cursor - 1, SEEK_SET));
281 if (rv < 0) {
282 PLOG(ERROR) << "Failed to lseek " << file_path;
283 return FiemapStatus::FromErrno(errno);
284 }
285 if (rv != cursor - 1) {
286 LOG(ERROR) << "Seek returned wrong offset " << rv << " for file " << file_path;
287 return FiemapStatus::Error();
288 }
289 char buffer[] = {0};
290 if (!android::base::WriteFully(file_fd, buffer, 1)) {
291 PLOG(ERROR) << "Write failed: " << file_path;
292 return FiemapStatus::FromErrno(errno);
293 }
294 if (on_progress && !on_progress(cursor, file_size)) {
295 return FiemapStatus::Error();
296 }
297 }
298 return FiemapStatus::Ok();
299 }
300
301 // F2FS-specific ioctl
302 // It requires the below kernel commit merged in v4.16-rc1.
303 // 1ad71a27124c ("f2fs: add an ioctl to disable GC for specific file")
304 // In android-4.4,
305 // 56ee1e817908 ("f2fs: updates on v4.16-rc1")
306 // In android-4.9,
307 // 2f17e34672a8 ("f2fs: updates on v4.16-rc1")
308 // In android-4.14,
309 // ce767d9a55bc ("f2fs: updates on v4.16-rc1")
310 #ifndef F2FS_IOC_SET_PIN_FILE
311 #ifndef F2FS_IOCTL_MAGIC
312 #define F2FS_IOCTL_MAGIC 0xf5
313 #endif
314 #define F2FS_IOC_GET_PIN_FILE _IOR(F2FS_IOCTL_MAGIC, 14, __u32)
315 #define F2FS_IOC_SET_PIN_FILE _IOW(F2FS_IOCTL_MAGIC, 13, __u32)
316 #endif
317
IsFilePinned(int file_fd,const std::string & file_path,uint32_t fs_type)318 static bool IsFilePinned(int file_fd, const std::string& file_path, uint32_t fs_type) {
319 if (fs_type != F2FS_SUPER_MAGIC) {
320 // No pinning necessary for ext4 or vfat. The blocks, once allocated,
321 // are expected to be fixed.
322 return true;
323 }
324
325 // f2fs: export FS_NOCOW_FL flag to user
326 uint32_t flags;
327 int error = ioctl(file_fd, FS_IOC_GETFLAGS, &flags);
328 if (error < 0) {
329 if ((errno == ENOTTY) || (errno == ENOTSUP)) {
330 PLOG(ERROR) << "Failed to get flags, not supported by kernel: " << file_path;
331 } else {
332 PLOG(ERROR) << "Failed to get flags: " << file_path;
333 }
334 return false;
335 }
336 if (!(flags & FS_NOCOW_FL)) {
337 return false;
338 }
339
340 // F2FS_IOC_GET_PIN_FILE returns the number of blocks moved.
341 uint32_t moved_blocks_nr;
342 error = ioctl(file_fd, F2FS_IOC_GET_PIN_FILE, &moved_blocks_nr);
343 if (error < 0) {
344 if ((errno == ENOTTY) || (errno == ENOTSUP)) {
345 PLOG(ERROR) << "Failed to get file pin status, not supported by kernel: " << file_path;
346 } else {
347 PLOG(ERROR) << "Failed to get file pin status: " << file_path;
348 }
349 return false;
350 }
351
352 if (moved_blocks_nr) {
353 LOG(WARNING) << moved_blocks_nr << " blocks moved in file " << file_path;
354 }
355 return moved_blocks_nr == 0;
356 }
357
PinFile(int file_fd,const std::string & file_path,uint32_t fs_type)358 static bool PinFile(int file_fd, const std::string& file_path, uint32_t fs_type) {
359 if (IsFilePinned(file_fd, file_path, fs_type)) {
360 return true;
361 }
362 if (fs_type != F2FS_SUPER_MAGIC) {
363 // No pinning necessary for ext4/msdos. The blocks, once allocated, are
364 // expected to be fixed.
365 return true;
366 }
367
368 uint32_t pin_status = 1;
369 int error = ioctl(file_fd, F2FS_IOC_SET_PIN_FILE, &pin_status);
370 if (error < 0) {
371 if ((errno == ENOTTY) || (errno == ENOTSUP)) {
372 PLOG(ERROR) << "Failed to pin file, not supported by kernel: " << file_path;
373 } else {
374 PLOG(ERROR) << "Failed to pin file: " << file_path;
375 }
376 return false;
377 }
378
379 return true;
380 }
381
382 // write zeroes in 'blocksz' byte increments until we reach file_size to make sure the data
383 // blocks are actually written to by the file system and thus getting rid of the holes in the
384 // file.
WriteZeroes(int file_fd,const std::string & file_path,size_t blocksz,uint64_t file_size,const std::function<bool (uint64_t,uint64_t)> & on_progress)385 static FiemapStatus WriteZeroes(int file_fd, const std::string& file_path, size_t blocksz,
386 uint64_t file_size,
387 const std::function<bool(uint64_t, uint64_t)>& on_progress) {
388 auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, blocksz), free);
389 if (buffer == nullptr) {
390 LOG(ERROR) << "failed to allocate memory for writing file";
391 return FiemapStatus::Error();
392 }
393
394 off64_t offset = lseek64(file_fd, 0, SEEK_SET);
395 if (offset < 0) {
396 PLOG(ERROR) << "Failed to seek at the beginning of : " << file_path;
397 return FiemapStatus::FromErrno(errno);
398 }
399
400 int permille = -1;
401 while (offset < file_size) {
402 if (!::android::base::WriteFully(file_fd, buffer.get(), blocksz)) {
403 PLOG(ERROR) << "Failed to write" << blocksz << " bytes at offset" << offset
404 << " in file " << file_path;
405 return FiemapStatus::FromErrno(errno);
406 }
407
408 offset += blocksz;
409
410 // Don't invoke the callback every iteration - wait until a significant
411 // chunk (here, 1/1000th) of the data has been processed.
412 int new_permille = (static_cast<uint64_t>(offset) * 1000) / file_size;
413 if (new_permille != permille && static_cast<uint64_t>(offset) != file_size) {
414 if (on_progress && !on_progress(offset, file_size)) {
415 return FiemapStatus::Error();
416 }
417 permille = new_permille;
418 }
419 }
420
421 if (lseek64(file_fd, 0, SEEK_SET) < 0) {
422 PLOG(ERROR) << "Failed to reset offset at the beginning of : " << file_path;
423 return FiemapStatus::FromErrno(errno);
424 }
425 return FiemapStatus::Ok();
426 }
427
428 // Reserve space for the file on the file system and write it out to make sure the extents
429 // don't come back unwritten. Return from this function with the kernel file offset set to 0.
430 // If the filesystem is f2fs, then we also PIN the file on disk to make sure the blocks
431 // aren't moved around.
AllocateFile(int file_fd,const std::string & file_path,uint64_t blocksz,uint64_t file_size,unsigned int fs_type,std::function<bool (uint64_t,uint64_t)> on_progress)432 static FiemapStatus AllocateFile(int file_fd, const std::string& file_path, uint64_t blocksz,
433 uint64_t file_size, unsigned int fs_type,
434 std::function<bool(uint64_t, uint64_t)> on_progress) {
435 bool need_explicit_writes = true;
436 switch (fs_type) {
437 case EXT4_SUPER_MAGIC:
438 break;
439 case F2FS_SUPER_MAGIC: {
440 bool supported;
441 if (!F2fsPinBeforeAllocate(file_fd, &supported)) {
442 return FiemapStatus::Error();
443 }
444 if (supported) {
445 if (!PinFile(file_fd, file_path, fs_type)) {
446 return FiemapStatus::Error();
447 }
448 need_explicit_writes = false;
449 }
450 break;
451 }
452 case MSDOS_SUPER_MAGIC:
453 // fallocate() is not supported, and not needed, since VFAT does not support holes.
454 // Instead we can perform a much faster allocation.
455 return FallocateFallback(file_fd, blocksz, file_size, file_path, on_progress);
456 default:
457 LOG(ERROR) << "Missing fallocate() support for file system " << fs_type;
458 return FiemapStatus::Error();
459 }
460
461 // F2FS can return EAGAIN and partially fallocate. Keep trying to fallocate,
462 // and if we don't make forward progress, return ENOSPC.
463 std::optional<off_t> prev_size;
464 while (true) {
465 if (fallocate(file_fd, 0, 0, file_size) == 0) {
466 break;
467 }
468 if (errno != EAGAIN) {
469 PLOG(ERROR) << "Failed to allocate space for file: " << file_path
470 << " size: " << file_size;
471 return FiemapStatus::FromErrno(errno);
472 }
473
474 struct stat s;
475 if (fstat(file_fd, &s) < 0) {
476 PLOG(ERROR) << "Failed to fstat after fallocate failure: " << file_path;
477 return FiemapStatus::FromErrno(errno);
478 }
479 if (!prev_size) {
480 prev_size = {s.st_size};
481 continue;
482 }
483 if (*prev_size >= s.st_size) {
484 LOG(ERROR) << "Fallocate retry failed, got " << s.st_size << ", asked for "
485 << file_size;
486 return FiemapStatus(FiemapStatus::ErrorCode::NO_SPACE);
487 }
488 LOG(INFO) << "Retrying fallocate, got " << s.st_size << ", asked for " << file_size;
489 }
490
491 if (need_explicit_writes) {
492 auto status = WriteZeroes(file_fd, file_path, blocksz, file_size, on_progress);
493 if (!status.is_ok()) {
494 return status;
495 }
496 }
497
498 // flush all writes here ..
499 if (fsync(file_fd)) {
500 PLOG(ERROR) << "Failed to synchronize written file:" << file_path;
501 return FiemapStatus::FromErrno(errno);
502 }
503
504 // Send one last progress notification.
505 if (on_progress && !on_progress(file_size, file_size)) {
506 return FiemapStatus::Error();
507 }
508 return FiemapStatus::Ok();
509 }
510
HasPinnedExtents(const std::string & file_path)511 bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
512 android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY));
513 if (fd < 0) {
514 PLOG(ERROR) << "open: " << file_path;
515 return false;
516 }
517
518 struct statfs64 sfs;
519 if (fstatfs64(fd, &sfs)) {
520 PLOG(ERROR) << "fstatfs64: " << file_path;
521 return false;
522 }
523 return IsFilePinned(fd, file_path, sfs.f_type);
524 }
525
IsValidExtent(const fiemap_extent * extent,std::string_view file_path)526 static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
527 if (extent->fe_flags & kUnsupportedExtentFlags) {
528 LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
529 << " has unsupported flags";
530 return false;
531 }
532 return true;
533 }
534
IsLastExtent(const fiemap_extent * extent)535 static bool IsLastExtent(const fiemap_extent* extent) {
536 return !!(extent->fe_flags & FIEMAP_EXTENT_LAST);
537 }
538
FiemapToExtents(struct fiemap * fiemap,std::vector<struct fiemap_extent> * extents,std::string_view file_path)539 static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
540 std::string_view file_path) {
541 uint32_t num_extents = fiemap->fm_mapped_extents;
542 if (num_extents == 0) {
543 LOG(ERROR) << "File " << file_path << " has zero extent";
544 return false;
545 }
546 const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
547 if (!IsLastExtent(last_extent)) {
548 LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path
549 << " num_extents=" << num_extents << " max_extents=" << kMaxExtents;
550 return false;
551 }
552
553 // Iterate through each extent, read and make sure its valid before adding it to the vector
554 // merging contiguous extents.
555 fiemap_extent* prev = &fiemap->fm_extents[0];
556 if (!IsValidExtent(prev, file_path)) return false;
557
558 for (uint32_t i = 1; i < num_extents; i++) {
559 fiemap_extent* next = &fiemap->fm_extents[i];
560
561 // Make sure extents are returned in order
562 if (next != last_extent && IsLastExtent(next)) {
563 LOG(ERROR) << "Extents are being received out-of-order";
564 return false;
565 }
566
567 // Check if extent's flags are valid
568 if (!IsValidExtent(next, file_path)) return false;
569
570 // Check if the current extent is contiguous with the previous one.
571 // An extent can be combined with its predecessor only if:
572 // 1. There is no physical space between the previous and the current
573 // extent, and
574 // 2. The physical distance between the previous and current extent
575 // corresponds to their logical distance (contiguous mapping).
576 if (prev->fe_physical + prev->fe_length == next->fe_physical &&
577 next->fe_physical - prev->fe_physical == next->fe_logical - prev->fe_logical) {
578 prev->fe_length += next->fe_length;
579 } else {
580 extents->emplace_back(*prev);
581 prev = next;
582 }
583 }
584 extents->emplace_back(*prev);
585
586 return true;
587 }
588
ReadFiemap(int file_fd,const std::string & file_path,std::vector<struct fiemap_extent> * extents)589 static bool ReadFiemap(int file_fd, const std::string& file_path,
590 std::vector<struct fiemap_extent>* extents) {
591 uint64_t fiemap_size = sizeof(struct fiemap) + kMaxExtents * sizeof(struct fiemap_extent);
592 auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
593 if (buffer == nullptr) {
594 LOG(ERROR) << "Failed to allocate memory for fiemap";
595 return false;
596 }
597
598 struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(buffer.get());
599 fiemap->fm_start = 0;
600 fiemap->fm_length = UINT64_MAX;
601 // make sure file is synced to disk before we read the fiemap
602 fiemap->fm_flags = FIEMAP_FLAG_SYNC;
603 fiemap->fm_extent_count = kMaxExtents;
604
605 if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
606 PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
607 return false;
608 }
609 return FiemapToExtents(fiemap, extents, file_path);
610 }
611
ReadFibmap(int file_fd,const std::string & file_path,std::vector<struct fiemap_extent> * extents)612 static bool ReadFibmap(int file_fd, const std::string& file_path,
613 std::vector<struct fiemap_extent>* extents) {
614 struct stat s;
615 if (fstat(file_fd, &s)) {
616 PLOG(ERROR) << "Failed to stat " << file_path;
617 return false;
618 }
619
620 unsigned int blksize;
621 if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) {
622 PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path;
623 return false;
624 }
625 if (!blksize) {
626 LOG(ERROR) << "Invalid filesystem block size: " << blksize;
627 return false;
628 }
629
630 uint64_t num_blocks = (s.st_size + blksize - 1) / blksize;
631 if (num_blocks > std::numeric_limits<uint32_t>::max()) {
632 LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")";
633 return false;
634 }
635
636 for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) {
637 uint32_t block = block_number;
638 if (ioctl(file_fd, FIBMAP, &block)) {
639 PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path;
640 return false;
641 }
642 if (!block) {
643 LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported";
644 return false;
645 }
646
647 if (!extents->empty() && block == last_block + 1) {
648 extents->back().fe_length += blksize;
649 } else {
650 extents->push_back(fiemap_extent{.fe_logical = block_number,
651 .fe_physical = static_cast<uint64_t>(block) * blksize,
652 .fe_length = static_cast<uint64_t>(blksize),
653 .fe_flags = 0});
654 if (extents->size() > kMaxExtents) {
655 LOG(ERROR) << "File has more than " << kMaxExtents << "extents: " << file_path;
656 return false;
657 }
658 }
659 last_block = block;
660 }
661 return true;
662 }
663
Open(const std::string & file_path,uint64_t file_size,bool create,std::function<bool (uint64_t,uint64_t)> progress)664 FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create,
665 std::function<bool(uint64_t, uint64_t)> progress) {
666 FiemapUniquePtr ret;
667 if (!Open(file_path, file_size, &ret, create, progress).is_ok()) {
668 return nullptr;
669 }
670 return ret;
671 }
672
Open(const std::string & file_path,uint64_t file_size,FiemapUniquePtr * out,bool create,std::function<bool (uint64_t,uint64_t)> progress)673 FiemapStatus FiemapWriter::Open(const std::string& file_path, uint64_t file_size,
674 FiemapUniquePtr* out, bool create,
675 std::function<bool(uint64_t, uint64_t)> progress) {
676 out->reset();
677
678 // if 'create' is false, open an existing file and do not truncate.
679 int open_flags = O_RDWR | O_CLOEXEC;
680 if (create) {
681 if (access(file_path.c_str(), F_OK) == 0) {
682 LOG(WARNING) << "File " << file_path << " already exists, truncating";
683 }
684 open_flags |= O_CREAT | O_TRUNC;
685 }
686 ::android::base::unique_fd file_fd(
687 TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR)));
688 if (file_fd < 0) {
689 PLOG(ERROR) << "Failed to create file at: " << file_path;
690 return FiemapStatus::FromErrno(errno);
691 }
692
693 std::string abs_path;
694 if (!::android::base::Realpath(file_path, &abs_path)) {
695 int saved_errno = errno;
696 PLOG(ERROR) << "Invalid file path: " << file_path;
697 cleanup(file_path, create);
698 return FiemapStatus::FromErrno(saved_errno);
699 }
700
701 std::string bdev_path;
702 if (!GetBlockDeviceForFile(abs_path, &bdev_path)) {
703 LOG(ERROR) << "Failed to get block dev path for file: " << file_path;
704 cleanup(abs_path, create);
705 return FiemapStatus::Error();
706 }
707
708 ::android::base::unique_fd bdev_fd(
709 TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC)));
710 if (bdev_fd < 0) {
711 int saved_errno = errno;
712 PLOG(ERROR) << "Failed to open block device: " << bdev_path;
713 cleanup(file_path, create);
714 return FiemapStatus::FromErrno(saved_errno);
715 }
716
717 uint64_t bdevsz;
718 if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) {
719 int saved_errno = errno;
720 LOG(ERROR) << "Failed to get block device size for : " << bdev_path;
721 cleanup(file_path, create);
722 return FiemapStatus::FromErrno(saved_errno);
723 }
724
725 if (!create) {
726 file_size = GetFileSize(abs_path);
727 if (file_size == 0) {
728 LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path;
729 return FiemapStatus::FromErrno(errno);
730 }
731 }
732
733 uint64_t blocksz;
734 uint32_t fs_type;
735 if (!PerformFileChecks(abs_path, &blocksz, &fs_type)) {
736 LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path;
737 cleanup(abs_path, create);
738 return FiemapStatus::Error();
739 }
740
741 // Align up to the nearest block size.
742 if (file_size % blocksz) {
743 file_size += blocksz - (file_size % blocksz);
744 }
745
746 if (create) {
747 auto status =
748 AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress));
749 if (!status.is_ok()) {
750 LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size
751 << " bytes";
752 cleanup(abs_path, create);
753 return status;
754 }
755 }
756
757 // f2fs may move the file blocks around.
758 if (!PinFile(file_fd, abs_path, fs_type)) {
759 cleanup(abs_path, create);
760 LOG(ERROR) << "Failed to pin the file in storage";
761 return FiemapStatus::Error();
762 }
763
764 // now allocate the FiemapWriter and start setting it up
765 FiemapUniquePtr fmap(new FiemapWriter());
766 switch (fs_type) {
767 case EXT4_SUPER_MAGIC:
768 case F2FS_SUPER_MAGIC:
769 if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) {
770 LOG(ERROR) << "Failed to read fiemap of file: " << abs_path;
771 cleanup(abs_path, create);
772 return FiemapStatus::Error();
773 }
774 break;
775 case MSDOS_SUPER_MAGIC:
776 if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) {
777 LOG(ERROR) << "Failed to read fibmap of file: " << abs_path;
778 cleanup(abs_path, create);
779 return FiemapStatus::Error();
780 }
781 break;
782 }
783
784 fmap->file_path_ = abs_path;
785 fmap->bdev_path_ = bdev_path;
786 fmap->file_size_ = file_size;
787 fmap->bdev_size_ = bdevsz;
788 fmap->fs_type_ = fs_type;
789 fmap->block_size_ = blocksz;
790
791 LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
792 << bdev_path;
793 *out = std::move(fmap);
794 return FiemapStatus::Ok();
795 }
796
797 } // namespace fiemap
798 } // namespace android
799