• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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     if (fallocate(file_fd, 0, 0, file_size)) {
462         PLOG(ERROR) << "Failed to allocate space for file: " << file_path << " size: " << file_size;
463         return FiemapStatus::FromErrno(errno);
464     }
465 
466     if (need_explicit_writes) {
467         auto status = WriteZeroes(file_fd, file_path, blocksz, file_size, on_progress);
468         if (!status.is_ok()) {
469             return status;
470         }
471     }
472 
473     // flush all writes here ..
474     if (fsync(file_fd)) {
475         PLOG(ERROR) << "Failed to synchronize written file:" << file_path;
476         return FiemapStatus::FromErrno(errno);
477     }
478 
479     // Send one last progress notification.
480     if (on_progress && !on_progress(file_size, file_size)) {
481         return FiemapStatus::Error();
482     }
483     return FiemapStatus::Ok();
484 }
485 
HasPinnedExtents(const std::string & file_path)486 bool FiemapWriter::HasPinnedExtents(const std::string& file_path) {
487     android::base::unique_fd fd(open(file_path.c_str(), O_NOFOLLOW | O_CLOEXEC | O_RDONLY));
488     if (fd < 0) {
489         PLOG(ERROR) << "open: " << file_path;
490         return false;
491     }
492 
493     struct statfs64 sfs;
494     if (fstatfs64(fd, &sfs)) {
495         PLOG(ERROR) << "fstatfs64: " << file_path;
496         return false;
497     }
498     return IsFilePinned(fd, file_path, sfs.f_type);
499 }
500 
CountFiemapExtents(int file_fd,const std::string & file_path,uint32_t * num_extents)501 static bool CountFiemapExtents(int file_fd, const std::string& file_path, uint32_t* num_extents) {
502     struct fiemap fiemap = {};
503     fiemap.fm_start = 0;
504     fiemap.fm_length = UINT64_MAX;
505     fiemap.fm_flags = FIEMAP_FLAG_SYNC;
506     fiemap.fm_extent_count = 0;
507 
508     if (ioctl(file_fd, FS_IOC_FIEMAP, &fiemap)) {
509         PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
510         return false;
511     }
512 
513     if (num_extents) {
514         *num_extents = fiemap.fm_mapped_extents;
515     }
516     return true;
517 }
518 
IsValidExtent(const fiemap_extent * extent,std::string_view file_path)519 static bool IsValidExtent(const fiemap_extent* extent, std::string_view file_path) {
520     if (extent->fe_flags & kUnsupportedExtentFlags) {
521         LOG(ERROR) << "Extent at location " << extent->fe_logical << " of file " << file_path
522                    << " has unsupported flags";
523         return false;
524     }
525     return true;
526 }
527 
IsLastExtent(const fiemap_extent * extent)528 static bool IsLastExtent(const fiemap_extent* extent) {
529     return !!(extent->fe_flags & FIEMAP_EXTENT_LAST);
530 }
531 
FiemapToExtents(struct fiemap * fiemap,std::vector<struct fiemap_extent> * extents,uint32_t num_extents,std::string_view file_path)532 static bool FiemapToExtents(struct fiemap* fiemap, std::vector<struct fiemap_extent>* extents,
533                             uint32_t num_extents, std::string_view file_path) {
534     if (num_extents == 0) return false;
535 
536     const struct fiemap_extent* last_extent = &fiemap->fm_extents[num_extents - 1];
537     if (!IsLastExtent(last_extent)) {
538         LOG(ERROR) << "FIEMAP did not return a final extent for file: " << file_path;
539         return false;
540     }
541 
542     // Iterate through each extent, read and make sure its valid before adding it to the vector
543     // merging contiguous extents.
544     fiemap_extent* prev = &fiemap->fm_extents[0];
545     if (!IsValidExtent(prev, file_path)) return false;
546 
547     for (uint32_t i = 1; i < num_extents; i++) {
548         fiemap_extent* next = &fiemap->fm_extents[i];
549 
550         // Make sure extents are returned in order
551         if (next != last_extent && IsLastExtent(next)) {
552             LOG(ERROR) << "Extents are being received out-of-order";
553             return false;
554         }
555 
556         // Check if extent's flags are valid
557         if (!IsValidExtent(next, file_path)) return false;
558 
559         // Check if the current extent is contiguous with the previous one.
560         // An extent can be combined with its predecessor only if:
561         //  1. There is no physical space between the previous and the current
562         //  extent, and
563         //  2. The physical distance between the previous and current extent
564         //  corresponds to their logical distance (contiguous mapping).
565         if (prev->fe_physical + prev->fe_length == next->fe_physical &&
566             next->fe_physical - prev->fe_physical == next->fe_logical - prev->fe_logical) {
567             prev->fe_length += next->fe_length;
568         } else {
569             extents->emplace_back(*prev);
570             prev = next;
571         }
572     }
573     extents->emplace_back(*prev);
574 
575     return true;
576 }
577 
ReadFiemap(int file_fd,const std::string & file_path,std::vector<struct fiemap_extent> * extents)578 static bool ReadFiemap(int file_fd, const std::string& file_path,
579                        std::vector<struct fiemap_extent>* extents) {
580     uint32_t num_extents;
581     if (!CountFiemapExtents(file_fd, file_path, &num_extents)) {
582         return false;
583     }
584     if (num_extents == 0) {
585         LOG(ERROR) << "File " << file_path << " has zero extents";
586         return false;
587     }
588     if (num_extents > kMaxExtents) {
589         LOG(ERROR) << "File has " << num_extents << ", maximum is " << kMaxExtents << ": "
590                    << file_path;
591         return false;
592     }
593 
594     uint64_t fiemap_size = sizeof(struct fiemap) + num_extents * sizeof(struct fiemap_extent);
595     auto buffer = std::unique_ptr<void, decltype(&free)>(calloc(1, fiemap_size), free);
596     if (buffer == nullptr) {
597         LOG(ERROR) << "Failed to allocate memory for fiemap";
598         return false;
599     }
600 
601     struct fiemap* fiemap = reinterpret_cast<struct fiemap*>(buffer.get());
602     fiemap->fm_start = 0;
603     fiemap->fm_length = UINT64_MAX;
604     // make sure file is synced to disk before we read the fiemap
605     fiemap->fm_flags = FIEMAP_FLAG_SYNC;
606     fiemap->fm_extent_count = num_extents;
607 
608     if (ioctl(file_fd, FS_IOC_FIEMAP, fiemap)) {
609         PLOG(ERROR) << "Failed to get FIEMAP from the kernel for file: " << file_path;
610         return false;
611     }
612     if (fiemap->fm_mapped_extents != num_extents) {
613         LOG(ERROR) << "FIEMAP returned unexpected extent count (" << num_extents
614                    << " expected, got " << fiemap->fm_mapped_extents << ") for file: " << file_path;
615         return false;
616     }
617 
618     return FiemapToExtents(fiemap, extents, num_extents, file_path);
619 }
620 
ReadFibmap(int file_fd,const std::string & file_path,std::vector<struct fiemap_extent> * extents)621 static bool ReadFibmap(int file_fd, const std::string& file_path,
622                        std::vector<struct fiemap_extent>* extents) {
623     struct stat s;
624     if (fstat(file_fd, &s)) {
625         PLOG(ERROR) << "Failed to stat " << file_path;
626         return false;
627     }
628 
629     unsigned int blksize;
630     if (ioctl(file_fd, FIGETBSZ, &blksize) < 0) {
631         PLOG(ERROR) << "Failed to get FIGETBSZ for " << file_path;
632         return false;
633     }
634     if (!blksize) {
635         LOG(ERROR) << "Invalid filesystem block size: " << blksize;
636         return false;
637     }
638 
639     uint64_t num_blocks = (s.st_size + blksize - 1) / blksize;
640     if (num_blocks > std::numeric_limits<uint32_t>::max()) {
641         LOG(ERROR) << "Too many blocks for FIBMAP (" << num_blocks << ")";
642         return false;
643     }
644 
645     for (uint32_t last_block, block_number = 0; block_number < num_blocks; block_number++) {
646         uint32_t block = block_number;
647         if (ioctl(file_fd, FIBMAP, &block)) {
648             PLOG(ERROR) << "Failed to get FIBMAP for file " << file_path;
649             return false;
650         }
651         if (!block) {
652             LOG(ERROR) << "Logical block " << block_number << " is a hole, which is not supported";
653             return false;
654         }
655 
656         if (!extents->empty() && block == last_block + 1) {
657             extents->back().fe_length += blksize;
658         } else {
659             extents->push_back(fiemap_extent{.fe_logical = block_number,
660                                              .fe_physical = static_cast<uint64_t>(block) * blksize,
661                                              .fe_length = static_cast<uint64_t>(blksize),
662                                              .fe_flags = 0});
663             if (extents->size() > kMaxExtents) {
664                 LOG(ERROR) << "File has more than " << kMaxExtents << "extents: " << file_path;
665                 return false;
666             }
667         }
668         last_block = block;
669     }
670     return true;
671 }
672 
Open(const std::string & file_path,uint64_t file_size,bool create,std::function<bool (uint64_t,uint64_t)> progress)673 FiemapUniquePtr FiemapWriter::Open(const std::string& file_path, uint64_t file_size, bool create,
674                                    std::function<bool(uint64_t, uint64_t)> progress) {
675     FiemapUniquePtr ret;
676     if (!Open(file_path, file_size, &ret, create, progress).is_ok()) {
677         return nullptr;
678     }
679     return ret;
680 }
681 
Open(const std::string & file_path,uint64_t file_size,FiemapUniquePtr * out,bool create,std::function<bool (uint64_t,uint64_t)> progress)682 FiemapStatus FiemapWriter::Open(const std::string& file_path, uint64_t file_size,
683                                 FiemapUniquePtr* out, bool create,
684                                 std::function<bool(uint64_t, uint64_t)> progress) {
685     out->reset();
686 
687     // if 'create' is false, open an existing file and do not truncate.
688     int open_flags = O_RDWR | O_CLOEXEC;
689     if (create) {
690         if (access(file_path.c_str(), F_OK) == 0) {
691             LOG(WARNING) << "File " << file_path << " already exists, truncating";
692         }
693         open_flags |= O_CREAT | O_TRUNC;
694     }
695     ::android::base::unique_fd file_fd(
696             TEMP_FAILURE_RETRY(open(file_path.c_str(), open_flags, S_IRUSR | S_IWUSR)));
697     if (file_fd < 0) {
698         PLOG(ERROR) << "Failed to create file at: " << file_path;
699         return FiemapStatus::FromErrno(errno);
700     }
701 
702     std::string abs_path;
703     if (!::android::base::Realpath(file_path, &abs_path)) {
704         int saved_errno = errno;
705         PLOG(ERROR) << "Invalid file path: " << file_path;
706         cleanup(file_path, create);
707         return FiemapStatus::FromErrno(saved_errno);
708     }
709 
710     std::string bdev_path;
711     if (!GetBlockDeviceForFile(abs_path, &bdev_path)) {
712         LOG(ERROR) << "Failed to get block dev path for file: " << file_path;
713         cleanup(abs_path, create);
714         return FiemapStatus::Error();
715     }
716 
717     ::android::base::unique_fd bdev_fd(
718             TEMP_FAILURE_RETRY(open(bdev_path.c_str(), O_RDONLY | O_CLOEXEC)));
719     if (bdev_fd < 0) {
720         int saved_errno = errno;
721         PLOG(ERROR) << "Failed to open block device: " << bdev_path;
722         cleanup(file_path, create);
723         return FiemapStatus::FromErrno(saved_errno);
724     }
725 
726     uint64_t bdevsz;
727     if (!GetBlockDeviceSize(bdev_fd, bdev_path, &bdevsz)) {
728         int saved_errno = errno;
729         LOG(ERROR) << "Failed to get block device size for : " << bdev_path;
730         cleanup(file_path, create);
731         return FiemapStatus::FromErrno(saved_errno);
732     }
733 
734     if (!create) {
735         file_size = GetFileSize(abs_path);
736         if (file_size == 0) {
737             LOG(ERROR) << "Invalid file size of zero bytes for file: " << abs_path;
738             return FiemapStatus::FromErrno(errno);
739         }
740     }
741 
742     uint64_t blocksz;
743     uint32_t fs_type;
744     if (!PerformFileChecks(abs_path, &blocksz, &fs_type)) {
745         LOG(ERROR) << "Failed to validate file or file system for file:" << abs_path;
746         cleanup(abs_path, create);
747         return FiemapStatus::Error();
748     }
749 
750     // Align up to the nearest block size.
751     if (file_size % blocksz) {
752         file_size += blocksz - (file_size % blocksz);
753     }
754 
755     if (create) {
756         auto status =
757                 AllocateFile(file_fd, abs_path, blocksz, file_size, fs_type, std::move(progress));
758         if (!status.is_ok()) {
759             LOG(ERROR) << "Failed to allocate file: " << abs_path << " of size: " << file_size
760                        << " bytes";
761             cleanup(abs_path, create);
762             return status;
763         }
764     }
765 
766     // f2fs may move the file blocks around.
767     if (!PinFile(file_fd, abs_path, fs_type)) {
768         cleanup(abs_path, create);
769         LOG(ERROR) << "Failed to pin the file in storage";
770         return FiemapStatus::Error();
771     }
772 
773     // now allocate the FiemapWriter and start setting it up
774     FiemapUniquePtr fmap(new FiemapWriter());
775     switch (fs_type) {
776         case EXT4_SUPER_MAGIC:
777         case F2FS_SUPER_MAGIC:
778             if (!ReadFiemap(file_fd, abs_path, &fmap->extents_)) {
779                 LOG(ERROR) << "Failed to read fiemap of file: " << abs_path;
780                 cleanup(abs_path, create);
781                 return FiemapStatus::Error();
782             }
783             break;
784         case MSDOS_SUPER_MAGIC:
785             if (!ReadFibmap(file_fd, abs_path, &fmap->extents_)) {
786                 LOG(ERROR) << "Failed to read fibmap of file: " << abs_path;
787                 cleanup(abs_path, create);
788                 return FiemapStatus::Error();
789             }
790             break;
791     }
792 
793     fmap->file_path_ = abs_path;
794     fmap->bdev_path_ = bdev_path;
795     fmap->file_size_ = file_size;
796     fmap->bdev_size_ = bdevsz;
797     fmap->fs_type_ = fs_type;
798     fmap->block_size_ = blocksz;
799 
800     LOG(VERBOSE) << "Successfully created FiemapWriter for file " << abs_path << " on block device "
801                  << bdev_path;
802     *out = std::move(fmap);
803     return FiemapStatus::Ok();
804 }
805 
806 }  // namespace fiemap
807 }  // namespace android
808