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