├── .gitignore ├── .gitmodules ├── Makefile ├── Makefile.inc ├── README.md ├── UNLICENSE ├── exfat.cpp ├── exfat.h └── flakyflash.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "common"] 2 | path = common 3 | url = https://github.com/whitslack/common.git 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | common/Makefile -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | SOURCES += $(wildcard *.cpp) $(wildcard common/*.cpp) 2 | CPPFLAGS += -D_FILE_OFFSET_BITS=64 3 | 4 | 5 | FLAKYFLASH_OBJECTS := $(addprefix $(OBJDIR)/,flakyflash.o exfat.o $(addprefix common/,cli.o fd.o format.o io.o uuid.o)) 6 | FLAKYFLASH := $(BINDIR)/flakyflash$(EXEC_SUFFIX) 7 | ALL += $(FLAKYFLASH) 8 | 9 | $(OBJDIR)/flakyflash.o : CXXFLAGS += -Wno-attributes 10 | $(FLAKYFLASH) : $(FLAKYFLASH_OBJECTS) 11 | $(DO_LINK.cpp) 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [F3]: https://github.com/AltraMayor/f3 "F3 - Fight Flash Fraud" 2 | 3 | [FAT]: http://download.microsoft.com/download/1/6/1/161ba512-40e2-4cc9-843a-923143f3456c/fatgen103.doc 4 | 5 | [exFAT]: https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification 6 | 7 | 8 | # Flakyflash 9 | 10 | Flakyflash is a Linux-based tool for diagnosing and salvaging [FAT][]- and [exFAT][]-formatted flash media having flaky sectors that do not retain data correctly. Note, it is not intended for diagnosing "fake flash" (a.k.a. "fraudulent flash") media; see [F3][] for that. 11 | 12 | Flakyflash works by reading each free data cluster in a FAT or exFAT file system and then *re-reading* it and comparing the two reads. If they differ, then the cluster is assumed to be "flaky," and Flakyflash marks the cluster as bad in the file allocation table so that file system drivers will not allocate data to it. Only free data clusters (and, as an option, already marked-bad data clusters) are checked. Clusters that are currently in use by files and subdirectory metadata are not touched in any way. Obviously, for the most thorough checking, one should run this tool on a completely empty file system so that every data cluster may be checked for flakiness. 13 | 14 | ## Usage 15 | 16 | ``` 17 | usage: flakyflash [options] 18 | 19 | options: 20 | -b,--bad-clusters=[,...] 21 | -f,--free-clusters=[,...] 22 | -n,--dry-run 23 | -v,--verbose 24 | 25 | actions: 26 | read: read cluster; mark bad if device errors 27 | reread: re-read cluster; mark bad if different 28 | zeroout: issue BLKZEROOUT ioctl on cluster 29 | (elided if a previous "read" found cluster already zeroed) 30 | readzeros: read cluster; mark bad if not zeroed 31 | f3write: fill cluster with reproducible data 32 | (elided if a previous "read" found cluster already correct) 33 | f3read: read cluster; mark bad if subtly changed 34 | secdiscard: issue BLKSECDISCARD ioctl on cluster 35 | discard: issue BLKDISCARD ioctl on cluster 36 | trash: fill cluster with pseudorandom garbage 37 | bad: mark cluster as bad unconditionally 38 | free: mark cluster as free unconditionally 39 | list: write cluster number to stdout 40 | 41 | defaults: 42 | --bad-clusters= 43 | --free-clusters=read,reread 44 | ``` 45 | 46 | If run with the `--dry-run` option, Flakyflash does not perform any writes to the media. Any destructive actions specified in `--bad-clusters` or `--free-clusters` will be ignored with a warning. 47 | 48 | If run with the `--verbose` option, Flakyflash outputs a human-readable decoding of all standard [FAT][] or [exFAT][] file system superblock fields, including FAT32-specific fields (if applicable) and the fields of the File System Information sector (if present). 49 | 50 | For each free cluster (and, optionally, for each bad cluster), Flakyflash performs a user-specified sequence of actions, which may comprise: 51 | 52 | * **`read`** – Reads the cluster from the media. 53 | 54 | * **`reread`** – Reads the cluster from the media again. If the data retrieved do not match the data retrieved by the previous `read` action (or written by the previous `zeroout`, `f3write`, or `trash` action), then Flakyflash marks the cluster as bad and continues immediately to the next cluster. If no previous action had established a baseline for the current cluster, then `reread` implicitly performs a `read` first. 55 | 56 | * **`zeroout`** – Issues a `BLKZEROOUT` `ioctl` on the cluster, which causes the kernel to overwrite the cluster on the media with null bytes. If a previous action had already established that the cluster contains only null bytes, then the `zeroout` action is skipped to avoid excessive writing to the flash media. The `zeroout` action itself establishes that the cluster contains only null bytes, which implies that multiple `zeroout` actions in a row will collapse to a single action. 57 | 58 | * **`readzeros`** – Reads the cluster from the media. If the data retrieved are not all null bytes, then Flakyflash marks the cluster as bad and continues immediately to the next cluster. This action may be used to check for spurious bit flips (a.k.a. "bit rot") if it is known for certain that the clusters should contain only null bytes, such as if a previous session of Flakyflash had zeroed them out using the `zeroout` action. 59 | 60 | * **`f3write`** – Overwrites the entire cluster on the media with reproducible data derived from the cluster's offset by the same linear congruential generator as [F3][] uses. If a previous action had already established that the cluster contains exactly these data, then the `f3write` action is skipped to avoid excessive writing to the flash media. The `f3write` action itself establishes that the cluster contains exactly these data, which implies that multiple `f3write` actions in a row will collapse to a single action. 61 | 62 | * **`f3read`** – Reads the cluster from the media and compares the data retrieved versus the data that the `f3write` action would write to the same cluster. If (and only if) more than zero but fewer than one eighth of the bits differ, then Flakyflash marks the cluster as bad and continues immediately to the next cluster. If the cluster appears to contain data that the `f3write` action would write to some other cluster, then Flakyflash emits a warning message suggesting that the flash media may be fraudulent. Otherwise, if more than one eighth of the bits differ from the intended data, then Flakyflash emits a warning message indicating that the cluster is corrupted. This action may be used to check for spurious bit flips (a.k.a. "bit rot") if it is known for certain that the clusters were most recently written using the `f3write` action. 63 | 64 | * **`secdiscard`** – Issues a `BLKSECDISCARD` `ioctl` on the cluster, which is supposed to cause the cluster to be erased irrecoverably, although hardware support for this is spotty. If the device does not support this action, then Flakyflash will emit an error message and abort with status code 134 (indicating `SIGABRT`). 65 | 66 | * **`discard`** – Issues a `BLKDISCARD` `ioctl` on the cluster, which indicates to the device that the data contents of the cluster are no longer needed. Some devices will immediately begin reading a discarded cluster as all null bytes, whereas others will put the cluster into an indeterminate state. If the device does not support this action, then Flakyflash will emit an error message and abort with status code 134 (indicating `SIGABRT`). 67 | 68 | * **`trash`** – Overwrites the entire cluster on the media with pseudorandom garbage generated by the kernel. The `trash` action establishes a new baseline for the cluster, against which a subsequent `reread` action may compare to determine if the write succeeded. 69 | 70 | * **`bad`** – Unconditionally marks the cluster as bad and continues immediately to the next cluster. There may be no good use for this action, but it is included for completeness. 71 | 72 | * **`free`** – Unconditionally marks the cluster as free. This action may be used to effect retesting of clusters previously marked as bad by specifying it as an action to perform on bad clusters. 73 | 74 | * **`list`** – Writes the cluster number to the standard output stream, followed by a newline character. Note that for historical reasons the first data cluster is numbered 2. 75 | 76 | To reduce I/O overhead (and potentially also to reduce wear on cheap flash media), Flakyflash logically merges contiguous runs of clusters having the same disposition (free or bad) into chunks and performs the user-specified actions upon a whole chunk at a time. Where possible, Flakyflash attempts to align these chunks to device offsets that are multiples of 8 MiB, taking into account any partition offset. If any action that was attempted upon a multiple-cluster chunk encounters a hardware error (i.e., a system call returns error code `EIO`), then Flakyflash reattempts the action (and all remaining actions in the specified sequence) on each cluster in the chunk individually so as to determine exactly which clusters are problematic. If any action that was attempted upon a single cluster encounters a hardware error, then Flakyflash marks the cluster as bad and continues immediately to the next cluster. 77 | 78 | Any changes to the FAT are written to the media only once at the completion of testing. Interrupting Flakyflash before it has finished will cause any pending FAT changes to be lost. Note that writes to data clusters are performed *during* testing and are not deferred to the end. 79 | 80 | ## Examples 81 | 82 | flakyflash --verbose --bad-clusters= --free-clusters= /dev/sdX1 83 | 84 | Only outputs a human-readable decoding of the file system superblocks but does not test any data clusters. Note that the default action list for bad clusters is empty, so the `--bad-clusters=` argument may be omitted with the same effect. 85 | 86 | flakyflash --free-clusters=read,reread /dev/sdX1 87 | 88 | Tests each free data cluster by reading it and re-reading it. Marks as bad any clusters that do not read the same on the second read. Note that the default action list for free clusters is `read,reread`, so the `--free-clusters=read,reread` argument may be omitted with the same effect. 89 | 90 | flakyflash --free-clusters=read,reread,reread,reread /dev/sdX1 91 | 92 | Almost the same as the preceding example except that each free data cluster is read a total of four times instead of twice. If any read of the cluster fails to match any other read of the same cluster, then Flakyflash marks the cluster as bad. 93 | 94 | flakyflash --free-clusters=read,zeroout,reread /dev/sdX1 95 | 96 | Reads each free data cluster, and if it does not already contain only null bytes, then fills the cluster with null bytes. Re-reads the cluster afterward, and if it does not then contain only null bytes, then marks the cluster as bad. 97 | 98 | flakyflash --free-clusters=zeroout,reread /dev/sdX1 99 | 100 | Almost the same as the preceding example except that every free cluster is overwritten with null bytes unconditionally, without first checking whether the cluster already contains only null bytes. 101 | 102 | flakyflash --free-clusters=zeroout,reread,trash,reread,zeroout,reread,discard /dev/sdX1 103 | 104 | Really exercises each free data cluster by overwriting it with null bytes, verifying that it reads back as all null bytes, overwriting it with pseudorandom garbage, verifying that the garbage reads back correctly, overwriting it null bytes again, verifying that it reads back as all null bytes, and finally discarding it. This will very likely detect most weak clusters except if the device has an onboard cache. 105 | 106 | flakyflash --free-clusters=read,f3write,reread /dev/sdX1 107 | 108 | Reads each free data cluster, and if it does not already contain exactly the data that `f3write` would write to it, then overwrites the cluster with those deterministic data. Re-reads the cluster afterward, and if it does not then contain the intended data, then marks the cluster as bad. This may be used to prepare the media for a data retention test. After thusly preparing the media, it should be placed in storage for a period of time. Upon retrieval of the media from storage, the next example shall conclude the test. 109 | 110 | flakyflash --free-clusters=f3read /dev/sdX1 111 | 112 | Reads each free data cluster and verifies that it contains the same deterministic data that the previous example wrote to it. Marks as bad any clusters whose data have changed subtly since they were written. Emits warnings about any clusters whose data have changed more than subtly or whose data were supposed to have been written to some other cluster. These latter cases may indicate that the flash media is fraudulent, and [F3][] may be further employed to diagnose such media. 113 | 114 | flakyflash --bad-clusters=free,read,reread --free-clusters= /dev/sdX1 115 | 116 | Retests all data clusters previously marked as bad. Each bad cluster is first marked as free (i.e., not bad) and is then read and re-read. If the two reads do not match, then the cluster is marked as bad again. Note that any changes to the FAT are written to the media only once at the completion of testing, so this will not issue redundant writes to the media. 117 | 118 | 119 | ## Building 120 | 121 | You'll need GCC 10 or newer to build Flakyflash. 122 | 123 | git clone --recurse-submodules https://github.com/whitslack/flakyflash.git 124 | cd flakyflash 125 | make 126 | 127 | There is no installation needed, but you can install the compiled binary if you want to: 128 | 129 | install -Dt /usr/local/sbin "out/$(gcc -dumpmachine)/flakyflash" 130 | -------------------------------------------------------------------------------- /UNLICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /exfat.cpp: -------------------------------------------------------------------------------- 1 | #include "exfat.h" 2 | 3 | #include 4 | 5 | 6 | namespace exfat { 7 | 8 | 9 | void timestamp_to_tm(struct tm &tm, Timestamp timestamp) noexcept { 10 | tm.tm_sec = ((timestamp >> DOUBLE_SECONDS_SHIFT) & (1u << DOUBLE_SECONDS_WIDTH) - 1) * 2; 11 | tm.tm_min = (timestamp >> MINUTE_SHIFT) & (1u << MINUTE_WIDTH) - 1; 12 | tm.tm_hour = (timestamp >> HOUR_SHIFT) & (1u << HOUR_WIDTH) - 1; 13 | tm.tm_mday = (timestamp >> DAY_SHIFT) & (1u << DAY_WIDTH) - 1; 14 | tm.tm_mon = ((timestamp >> MONTH_SHIFT) & (1u << MONTH_WIDTH) - 1) - 1; 15 | tm.tm_year = ((timestamp >> YEAR_SHIFT) & (1u << YEAR_WIDTH) - 1) + 80; 16 | tm.tm_wday = -1; 17 | tm.tm_yday = -1; 18 | tm.tm_isdst = -1; 19 | } 20 | 21 | Timestamp timestamp_from_tm(const struct tm &tm) noexcept { 22 | return Timestamp { 23 | (static_cast(tm.tm_sec) / 2 & (1u << DOUBLE_SECONDS_WIDTH) - 1) << DOUBLE_SECONDS_SHIFT | 24 | (static_cast(tm.tm_min) & (1u << MINUTE_WIDTH) - 1) << MINUTE_SHIFT | 25 | (static_cast(tm.tm_hour) & (1u << HOUR_WIDTH) - 1) << HOUR_SHIFT | 26 | (static_cast(tm.tm_mday) & (1u << DAY_WIDTH) - 1) << DAY_SHIFT | 27 | (static_cast(tm.tm_mon) + 1 & (1u << MONTH_WIDTH) - 1) << MONTH_SHIFT | 28 | (static_cast(tm.tm_year) - 80 & (1u << YEAR_WIDTH) - 1) << YEAR_SHIFT 29 | }; 30 | } 31 | 32 | 33 | ClusterChainIO::ClusterChainIO(FileDescriptor &dev_fd, const struct BootSector &bs, const le *fat, uint32_t starting_cluster, uint64_t starting_position) : 34 | dev_fd(dev_fd), bs(bs), fat(fat), cluster(starting_cluster) 35 | { 36 | const uint32_t cluster_size = UINT32_C(1) << bs.logical_sectors_per_cluster_shift + bs.bytes_per_logical_sector_shift; 37 | const uint32_t max_cluster = bs.total_data_clusters + 1; 38 | while (starting_position > cluster_size) { 39 | if (_unlikely((cluster = fat[cluster]) > max_cluster || cluster < 2)) { 40 | throw std::underflow_error(cluster == 0 ? "premature end of cluster chain" : "FAT references invalid cluster"); 41 | } 42 | starting_position -= cluster_size; 43 | } 44 | cluster_position = static_cast(starting_position); 45 | } 46 | 47 | _nodiscard ssize_t ClusterChainIO::read(void *buf, size_t n) { 48 | if (_unlikely(n == 0)) { 49 | return 0; 50 | } 51 | ssize_t ret = 0; 52 | const uint32_t cluster_size = UINT32_C(1) << bs.logical_sectors_per_cluster_shift + bs.bytes_per_logical_sector_shift; 53 | const uint32_t max_cluster = bs.total_data_clusters + 1; 54 | uint32_t cluster_remain = cluster_size - cluster_position; 55 | do { 56 | if (cluster_remain == 0) { 57 | uint32_t next_cluster = fat[cluster]; 58 | if (_unlikely(next_cluster < 2 || next_cluster > max_cluster)) { 59 | if (!~next_cluster) { // end of cluster chain 60 | return ret ?: -1; 61 | } 62 | throw std::underflow_error("FAT references invalid cluster"); 63 | } 64 | cluster = next_cluster, cluster_position = 0, cluster_remain = cluster_size; 65 | } 66 | ssize_t r = dev_fd.pread(buf, std::min(n, cluster_remain), (bs.data_start_lsn + (uint64_t { cluster - 2 } << bs.logical_sectors_per_cluster_shift) << bs.bytes_per_logical_sector_shift) + cluster_position); 67 | if (_unlikely(r <= 0)) { 68 | if (r == 0) { 69 | break; 70 | } 71 | throw std::underflow_error("read past end of device"); 72 | } 73 | buf = static_cast(buf) + r, n -= r; 74 | cluster_position += static_cast(r), cluster_remain -= static_cast(r); 75 | ret += r; 76 | } while (n > 0); 77 | return ret; 78 | } 79 | 80 | _nodiscard size_t ClusterChainIO::write(const void *buf, size_t n) { 81 | if (_unlikely(n == 0)) { 82 | return 0; 83 | } 84 | size_t ret = 0; 85 | const uint32_t cluster_size = UINT32_C(1) << bs.logical_sectors_per_cluster_shift + bs.bytes_per_logical_sector_shift; 86 | const uint32_t max_cluster = bs.total_data_clusters + 1; 87 | uint32_t cluster_remain = cluster_size - cluster_position; 88 | do { 89 | if (cluster_remain == 0) { 90 | uint32_t next_cluster = fat[cluster]; 91 | if (_unlikely(next_cluster < 2 || next_cluster > max_cluster)) { 92 | if (!~next_cluster) { // end of cluster chain 93 | return ret; 94 | } 95 | throw std::underflow_error("FAT references invalid cluster"); 96 | } 97 | cluster = next_cluster, cluster_position = 0, cluster_remain = cluster_size; 98 | } 99 | size_t w = dev_fd.pwrite(buf, std::min(n, cluster_remain), (bs.data_start_lsn + (uint64_t { cluster - 2 } << bs.logical_sectors_per_cluster_shift) << bs.bytes_per_logical_sector_shift) + cluster_position); 100 | if (_unlikely(w == 0)) { 101 | break; 102 | } 103 | buf = static_cast(buf) + w, n -= w; 104 | cluster_position += static_cast(w), cluster_remain -= static_cast(w); 105 | ret += w; 106 | } while (n > 0); 107 | return ret; 108 | } 109 | 110 | 111 | _nodiscard const union DirectoryEntry * Directory::next_entry() { 112 | if (!buffer.grem()) { 113 | buffer.clear(); 114 | ssize_t r = source.read(buffer.pptr, buffer.prem()); 115 | if (r <= 0) { 116 | return nullptr; 117 | } 118 | buffer.pptr += r; 119 | if (auto n = static_cast(r) % sizeof(union DirectoryEntry)) { 120 | source.read_fully(buffer.pptr, n); 121 | buffer.pptr += n; 122 | } 123 | } 124 | auto ret = reinterpret_cast(buffer.gptr); 125 | if (ret->generic.entry_type == EntryType { }) { // end of directory 126 | return nullptr; 127 | } 128 | buffer.gptr += sizeof(union DirectoryEntry); 129 | return ret; 130 | } 131 | 132 | 133 | } // namespace exfat 134 | 135 | 136 | #include "common/io.tcc" 137 | 138 | template class Readable; 139 | template class Writable; 140 | -------------------------------------------------------------------------------- /exfat.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "common/buffer.h" 7 | #include "common/compiler.h" 8 | #include "common/endian.h" 9 | #include "common/enumflags.h" 10 | #include "common/fd.h" 11 | #include "common/io.h" 12 | 13 | 14 | // https://docs.microsoft.com/en-us/windows/win32/fileio/exfat-specification 15 | 16 | namespace exfat { 17 | 18 | 19 | enum class VolumeFlags : uint16_t { // §3.1.13 20 | ACTIVE_FAT = 1 << 0, // §3.1.13.1: ActiveFat 21 | VOLUME_DIRTY = 1 << 1, // §3.1.13.2: VolumeDirty 22 | MEDIA_FAILURE = 1 << 2, // §3.1.13.3: MediaFailure 23 | CLEAR_TO_ZERO = 1 << 3, // §3.1.13.4: ClearToZero 24 | }; 25 | DEFINE_ENUM_FLAG_OPS(VolumeFlags) 26 | 27 | struct BootSector { // §3.1 28 | std::byte jump_instruction[3]; // §3.1.1: JumpBoot 29 | char file_system_name[8]; // §3.1.2: FileSystemName: "EXFAT " 30 | std::byte must_be_zero[53]; // §3.1.3: MustBeZero 31 | le hidden_sectors; // §3.1.4: PartitionOffset 32 | le total_logical_sectors; // §3.1.5: VolumeLength 33 | le reserved_logical_sectors; // §3.1.6: FatOffset 34 | le logical_sectors_per_fat; // §3.1.7: FatLength 35 | le data_start_lsn; // §3.1.8: ClusterHeapOffset 36 | le total_data_clusters; // §3.1.9: ClusterCount 37 | le root_dir_start_cluster; // §3.1.10: FirstClusterOfRootDirectory 38 | le volume_id; // §3.1.11: VolumeSerialNumber 39 | le version; // §3.1.12: FileSystemRevision 40 | le volume_flags; // §3.1.13: VolumeFlags 41 | uint8_t bytes_per_logical_sector_shift; // §3.1.14: BytesPerSectorShift 42 | uint8_t logical_sectors_per_cluster_shift; // §3.1.15: SectorsPerClusterShift 43 | uint8_t fats; // §3.1.16: NumberOfFats 44 | uint8_t physical_drive_number; // §3.1.17: DriveSelect 45 | uint8_t percent_in_use; // §3.1.18: PercentInUse 46 | std::byte reserved[7]; // Reserved 47 | std::byte opaque[0x1FE - 0x078]; // §3.1.19: BootCode 48 | std::byte boot_sector_signature[2]; // §3.1.20: BootSignature: 0x55 0xAA 49 | }; 50 | static_assert(sizeof(struct BootSector) == 512); 51 | 52 | enum TypeImportance : uint8_t { // §6.2.1.2 53 | CRITICAL = 0 << 5, 54 | BENIGN = 1 << 5 55 | }; 56 | 57 | enum TypeCategory : uint8_t { // §6.2.1.3 58 | PRIMARY = 0 << 6, 59 | SECONDARY = 1 << 6 60 | }; 61 | 62 | enum InUse : uint8_t { // §6.2.1.4 63 | IN_USE = 1 << 7 64 | }; 65 | 66 | enum EntryType : uint8_t { // §6.2.1 67 | // InUse | TypeImportance | TypeCategory | TypeCode 68 | ALLOC_BITMAP = +IN_USE | +CRITICAL | +PRIMARY | 1, 69 | UPCASE_TABLE = +IN_USE | +CRITICAL | +PRIMARY | 2, 70 | VOLUME_LABEL = +IN_USE | +CRITICAL | +PRIMARY | 3, 71 | FILE = +IN_USE | +CRITICAL | +PRIMARY | 5, 72 | STREAM_EXTENSION = +IN_USE | +CRITICAL | +SECONDARY | 0, 73 | FILE_NAME = +IN_USE | +CRITICAL | +SECONDARY | 1, 74 | VOLUME_GUID = +IN_USE | +BENIGN | +PRIMARY | 0, 75 | VENDOR_EXTENSION = +IN_USE | +BENIGN | +SECONDARY | 0, 76 | VENDOR_ALLOC = +IN_USE | +BENIGN | +SECONDARY | 1, 77 | }; 78 | 79 | struct GenericDirectoryEntry { // §6.2 80 | EntryType entry_type; // §6.2.1: EntryType 81 | std::byte opaque[19]; // CustomDefined 82 | le first_cluster; // §6.2.2: FirstCluster 83 | le data_length; // §6.2.3: DataLength 84 | }; 85 | static_assert(sizeof(struct GenericDirectoryEntry) == 32); 86 | 87 | enum class GeneralPrimaryFlags : uint16_t { // §6.3.4 88 | ALLOC_POSSIBLE = 1 << 0, // §6.3.4.1: AllocationPossible 89 | NO_FAT_CHAIN = 1 << 1, // §6.3.4.2: NoFatChain 90 | }; 91 | DEFINE_ENUM_FLAG_OPS(GeneralPrimaryFlags) 92 | 93 | struct GenericPrimaryDirectoryEntry { // §6.3 94 | EntryType entry_type; // §6.3.1: EntryType 95 | uint8_t secondary_count; // §6.3.2: SecondaryCount 96 | le set_checksum; // §6.3.3: SetChecksum 97 | le flags; // §6.3.4: GeneralPrimaryFlags 98 | std::byte opaque[14]; // CustomDefined 99 | le first_cluster; // §6.3.5: FirstCluster 100 | le data_length; // §6.3.6: DataLength 101 | }; 102 | static_assert(sizeof(struct GenericPrimaryDirectoryEntry) == 32); 103 | 104 | enum class GeneralSecondaryFlags : uint8_t { // §6.4.2 105 | ALLOC_POSSIBLE = 1 << 0, // §6.4.2.1: AllocationPossible 106 | NO_FAT_CHAIN = 1 << 1, // §6.4.2.2: NoFatChain 107 | }; 108 | DEFINE_ENUM_FLAG_OPS(GeneralSecondaryFlags) 109 | 110 | struct GenericSecondaryDirectoryEntry { // §6.4 111 | EntryType entry_type; // §6.4.1: EntryType 112 | GeneralSecondaryFlags flags; // §6.4.2: GeneralSecondaryFlags 113 | std::byte opaque[18]; // CustomDefined 114 | le first_cluster; // §6.4.3: FirstCluster 115 | le data_length; // §6.4.4: DataLength 116 | }; 117 | static_assert(sizeof(struct GenericSecondaryDirectoryEntry) == 32); 118 | 119 | enum class BitmapFlags : uint8_t { // §7.1.2 120 | BITMAP_ID = 1 << 0, // §7.1.2.1: BitmapIdentifier 121 | }; 122 | DEFINE_ENUM_FLAG_OPS(BitmapFlags) 123 | 124 | struct AllocationBitmapDirectoryEntry { // §7.1 125 | EntryType entry_type; // §7.1.1: EntryType: ALLOC_BITMAP 126 | BitmapFlags flags; // §7.1.2: BitmapFlags 127 | std::byte reserved[18]; // Reserved 128 | le first_cluster; // §7.1.3: FirstCluster 129 | le data_length; // §7.1.4: DataLength 130 | }; 131 | static_assert(sizeof(struct AllocationBitmapDirectoryEntry) == 32); 132 | 133 | struct UpcaseTableDirectoryEntry { // §7.2 134 | EntryType entry_type; // §7.2.1: EntryType: UPCASE_TABLE 135 | std::byte reserved1[3]; // Reserved1 136 | le table_checksum; // §7.2.2: TableChecksum 137 | std::byte reserved2[12]; // Reserved2 138 | le first_cluster; // §7.2.3: FirstCluster 139 | le data_length; // §7.2.4: DataLength 140 | }; 141 | static_assert(sizeof(struct UpcaseTableDirectoryEntry) == 32); 142 | 143 | struct VolumeLabelDirectoryEntry { // §7.3 144 | EntryType entry_type; // §7.3.1: EntryType: VOLUME_LABEL 145 | uint8_t char_count; // §7.3.2: CharacterCount 146 | le volume_label[11]; // §7.3.3: VolumeLabel 147 | std::byte reserved[8]; // Reserved 148 | }; 149 | static_assert(sizeof(struct VolumeLabelDirectoryEntry) == 32); 150 | 151 | enum class FileAttributes : uint16_t { // §7.4.4 152 | READ_ONLY = 1 << 0, 153 | HIDDEN = 1 << 1, 154 | SYSTEM = 1 << 2, 155 | DIRECTORY = 1 << 4, 156 | ARCHIVE = 1 << 5, 157 | }; 158 | DEFINE_ENUM_FLAG_OPS(FileAttributes) 159 | 160 | enum Timestamp : uint32_t { // §7.4.8 161 | DOUBLE_SECONDS_SHIFT = 0, // §7.4.8.1: DoubleSeconds 162 | DOUBLE_SECONDS_WIDTH = 5, 163 | MINUTE_SHIFT = 5, // §7.4.8.2: Minute 164 | MINUTE_WIDTH = 6, 165 | HOUR_SHIFT = 11, // §7.4.8.3: Hour 166 | HOUR_WIDTH = 5, 167 | DAY_SHIFT = 16, // §7.4.8.4: Day 168 | DAY_WIDTH = 5, 169 | MONTH_SHIFT = 21, // §7.4.8.5: Month 170 | MONTH_WIDTH = 4, 171 | YEAR_SHIFT = 25, // §7.4.8.6: Year 172 | YEAR_WIDTH = 7 173 | }; 174 | 175 | void timestamp_to_tm(struct tm &tm, Timestamp timestamp) noexcept; 176 | Timestamp _pure timestamp_from_tm(const struct tm &tm) noexcept; 177 | 178 | struct FileDirectoryEntry { // §7.4 179 | EntryType entry_type; // §7.4.1: EntryType: FILE 180 | uint8_t secondary_count; // §7.4.2: SecondaryCount 181 | le set_checksum; // §7.4.3: SetChecksum 182 | le attributes; // §7.4.4: FileAttributes 183 | std::byte reserved1[2]; // Reserved1 184 | le create_time; // §7.4.5: CreateTimestamp 185 | le modify_time; // §7.4.6: LastModifiedTimestamp 186 | le access_time; // §7.4.7: LastAccessedTimestamp 187 | uint8_t create_time_10ms; // §7.4.5: Create10msIncrement 188 | uint8_t modify_time_10ms; // §7.4.6: LastModified10msIncrement 189 | uint8_t create_utc_offset; // §7.4.5: CreateUtcOffset 190 | uint8_t modify_utc_offset; // §7.4.6: LastModifiedUtcOffset 191 | uint8_t access_utc_offset; // §7.4.7: LastAccessedUtcOffset 192 | std::byte reserved2[7]; // Reserved2 193 | }; 194 | static_assert(sizeof(struct FileDirectoryEntry) == 32); 195 | 196 | struct VolumeGUIDDirectoryEntry { // §7.5 197 | EntryType entry_type; // §7.5.1: EntryType: VOLUME_GUID 198 | uint8_t secondary_count; // §7.5.2: SecondaryCount 199 | le set_checksum; // §7.5.3: SetChecksum 200 | le flags; // §7.5.4: GeneralPrimaryFlags 201 | std::array volume_guid; // §7.5.5: VolumeGuid 202 | std::byte reserved[10]; // Reserved 203 | }; 204 | static_assert(sizeof(struct VolumeGUIDDirectoryEntry) == 32); 205 | 206 | struct StreamExtensionDirectoryEntry { // §7.6 207 | EntryType entry_type; // §7.6.1: EntryType: STREAM_EXTENSION 208 | GeneralSecondaryFlags flags; // §7.6.2: GeneralSecondaryFlags 209 | std::byte reserved1; // Reserved1 210 | uint8_t name_length; // §7.6.3: NameLength 211 | le name_hash; // §7.6.4: NameHash 212 | std::byte reserved2[2]; // Reserved2 213 | le valid_data_length; // §7.6.5: ValidDataLength 214 | std::byte reserved3[4]; // Reserved3 215 | le first_cluster; // §7.6.6: FirstCluster 216 | le data_length; // §7.6.7: DataLength 217 | }; 218 | static_assert(sizeof(struct StreamExtensionDirectoryEntry) == 32); 219 | 220 | struct FileNameDirectoryEntry { // §7.7 221 | EntryType entry_type; // §7.7.1: EntryType: FILE_NAME 222 | GeneralSecondaryFlags flags; // §7.7.2: GeneralSecondaryFlags 223 | le file_name[15]; // §7.7.3: FileName 224 | }; 225 | static_assert(sizeof(struct FileNameDirectoryEntry) == 32); 226 | 227 | struct VendorExtensionDirectoryEntry { // §7.8 228 | EntryType entry_type; // §7.8.1: EntryType: VENDOR_EXTENSION 229 | GeneralSecondaryFlags flags; // §7.8.2: GeneralSecondaryFlags 230 | std::array vendor_guid; // §7.8.3: VendorGuid 231 | std::byte opaque[14]; // VendorDefined 232 | }; 233 | static_assert(sizeof(struct VendorExtensionDirectoryEntry) == 32); 234 | 235 | struct VendorAllocationDirectoryEntry { // §7.9 236 | EntryType entry_type; // §7.9.1: EntryType: VENDOR_ALLOC 237 | GeneralSecondaryFlags flags; // §7.9.2: GeneralSecondaryFlags 238 | std::array vendor_guid; // §7.9.3: VendorGuid 239 | std::byte opaque[2]; // VendorDefined 240 | le first_cluster; // §7.9.4: FirstCluster 241 | le data_length; // §7.9.5: DataLength 242 | }; 243 | static_assert(sizeof(struct VendorAllocationDirectoryEntry) == 32); 244 | 245 | union DirectoryEntry { 246 | struct GenericDirectoryEntry generic; 247 | struct GenericPrimaryDirectoryEntry generic_primary; 248 | struct GenericSecondaryDirectoryEntry generic_secondary; 249 | struct AllocationBitmapDirectoryEntry alloc_bitmap; 250 | struct UpcaseTableDirectoryEntry upcase_table; 251 | struct VolumeLabelDirectoryEntry volume_label; 252 | struct FileDirectoryEntry file; 253 | struct VolumeGUIDDirectoryEntry volume_guid; 254 | struct StreamExtensionDirectoryEntry stream_extension; 255 | struct FileNameDirectoryEntry file_name; 256 | struct VendorExtensionDirectoryEntry vendor_extension; 257 | struct VendorAllocationDirectoryEntry vendor_alloc; 258 | }; 259 | static_assert(sizeof(union DirectoryEntry) == 32); 260 | 261 | 262 | template 263 | [[nodiscard]] static inline auto _pure checksum(T checksum, Itr begin, Itr end) noexcept(noexcept(static_cast(*begin++))) 264 | -> std::enable_if_t && std::is_unsigned_v, decltype(static_cast(*begin++))> 265 | { 266 | while (begin != end) { 267 | checksum = static_cast(std::rotr(checksum, 1) + static_cast(*begin++)); 268 | } 269 | return checksum; 270 | } 271 | 272 | 273 | class ClusterChainIO : public Readable, public Writable { 274 | public: 275 | FileDescriptor &dev_fd; 276 | const struct BootSector &bs; 277 | const le * const fat; 278 | private: 279 | uint32_t cluster; 280 | uint32_t cluster_position; 281 | public: 282 | explicit ClusterChainIO(FileDescriptor &dev_fd, const struct BootSector &bs, const le *fat, uint32_t starting_cluster, uint64_t starting_position = 0); 283 | public: 284 | _nodiscard ssize_t read(void *buf, size_t n); 285 | using Readable::read; 286 | _nodiscard size_t write(const void *buf, size_t n); 287 | using Writable::write; 288 | }; 289 | 290 | 291 | class Directory { 292 | public: 293 | Source &source; 294 | private: 295 | DynamicBuffer buffer; 296 | public: 297 | explicit Directory(Source &source) = delete; 298 | template explicit Directory(Source &source, Args &&...args) : 299 | source(source), buffer(std::forward(args)...) { } 300 | public: 301 | _nodiscard const union DirectoryEntry * next_entry(); 302 | }; 303 | 304 | 305 | } // namespace exfat 306 | 307 | 308 | extern template class Readable; 309 | extern template class Writable; 310 | -------------------------------------------------------------------------------- /flakyflash.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #include "common/cli.h" 18 | #include "common/compiler.h" 19 | #include "common/endian.h" 20 | #include "common/fd.h" 21 | #include "common/format.h" 22 | #include "common/narrow.h" 23 | #include "common/uuid.h" 24 | 25 | #include "exfat.h" 26 | 27 | 28 | #if __cpp_lib_constexpr_functional < 201907L 29 | namespace std { 30 | struct __is_transparent; 31 | struct identity { 32 | using is_transparent = __is_transparent; 33 | template constexpr T && operator()(T &&t) const noexcept { return std::forward(t); } 34 | }; 35 | } 36 | #endif 37 | 38 | 39 | struct BootSector { 40 | 41 | #pragma pack(push, 1) 42 | struct BPB { 43 | le bytes_per_logical_sector; // BPB_BytsPerSec 44 | uint8_t logical_sectors_per_cluster; // BPB_SecPerClus 45 | le reserved_logical_sectors; // BPB_RsvdSecCnt 46 | uint8_t fats; // BPB_NumFATs 47 | le root_dir_entries; // BPB_RootEntCnt 48 | le old_total_logical_sectors; // BPB_TotSec16 49 | std::byte media_descriptor; // BPB_Media 50 | le logical_sectors_per_fat; // BPB_FATSz16 51 | le physical_sectors_per_track; // BPB_SecPerTrk 52 | le heads; // BPB_NumHeads 53 | le hidden_sectors; // BPB_HiddSec 54 | le total_logical_sectors; // BPB_TotSec32 55 | }; 56 | static_assert(sizeof(struct BPB) == 25); 57 | 58 | struct EBPB { 59 | uint8_t physical_drive_number; // BS_DrvNum 60 | std::byte reserved; // BS_Reserved1 61 | std::byte extended_boot_signature; // BS_BootSig 62 | le volume_id; // BS_VolID 63 | char volume_label[11]; // BS_VolLab 64 | char file_system_type[8]; // BS_FilSysType 65 | }; 66 | static_assert(sizeof(struct EBPB) == 26); 67 | #pragma pack(pop) 68 | 69 | struct FATParams { 70 | struct EBPB ebpb; 71 | std::byte opaque[0x1FC - 0x03E]; 72 | }; 73 | static_assert(sizeof(struct FATParams) == 512 - (3 + 8 + 25) - (1 + 1 + 2)); 74 | 75 | struct FAT32Params { 76 | le logical_sectors_per_fat; // BPB_FATSz32 77 | le mirroring_flags; // BPB_ExtFlags 78 | le version; // BPB_FSVer 79 | le root_dir_start_cluster; // BPB_RootClus 80 | le fs_info_lsn; // BPB_FSInfo 81 | le boot_sector_backup_lsn; // BPB_BkBootSec 82 | std::byte reserved[12]; // BPB_Reserved 83 | struct EBPB ebpb; 84 | std::byte opaque[0x1FC - 0x05A]; 85 | }; 86 | static_assert(sizeof(struct FAT32Params) == 512 - (3 + 8 + 25) - (1 + 1 + 2)); 87 | 88 | std::byte jump_instruction[3]; // BS_jmpBoot 89 | char oem_name[8]; // BS_OEMName 90 | struct BPB bpb; 91 | union { 92 | struct FATParams fat; 93 | struct FAT32Params fat32; 94 | }; 95 | std::byte padding; 96 | uint8_t old_physical_drive_number; 97 | std::byte boot_sector_signature[2]; // 0x55, 0xAA 98 | }; 99 | static_assert(sizeof(struct BootSector) == 512); 100 | 101 | 102 | struct FSInfoSector { 103 | std::byte fs_info_sector_signature1[4]; // FSI_LeadSig: "RRaA" 104 | std::byte reserved1[480]; // FSI_Reserved1 105 | std::byte fs_info_sector_signature2[4]; // FSI_StrucSig: "rrAa" 106 | le last_known_free_data_clusters; // FSI_Free_Count 107 | le most_recently_allocated_data_cluster; // FSI_Nxt_Free 108 | std::byte reserved2[12]; // FSI_Reserved2 109 | std::byte fs_info_sector_signature3[4]; // FSI_TrailSig: 0x00, 0x00, 0x55, 0xAA 110 | }; 111 | static_assert(sizeof(struct FSInfoSector) == 512); 112 | 113 | 114 | static std::ostream & operator<<(std::ostream &os, std::byte b) { 115 | return os << std::hex << static_cast(b) << std::dec; 116 | } 117 | 118 | template 119 | static std::ostream & operator<<(std::ostream &os, const std::byte (&b)[N]) { 120 | if constexpr (N > 0) { 121 | os << std::hex << static_cast(b[0]); 122 | for (size_t i = 1; i < N; ++i) { 123 | os.put(' ') << static_cast(b[i]); 124 | } 125 | os << std::dec; 126 | } 127 | return os; 128 | } 129 | 130 | static std::string _pure to_string(std::u16string_view sv) { 131 | std::string ret; 132 | auto &codecvt = std::use_facet>(std::locale()); 133 | std::mbstate_t state { }; 134 | const char16_t *from_next; 135 | char *to_next; 136 | ret.resize(sv.size() * codecvt.max_length()); 137 | codecvt.out(state, sv.data(), sv.data() + sv.size(), from_next, ret.data(), ret.data() + ret.size(), to_next); 138 | ret.resize(to_next - ret.data()); 139 | return ret; 140 | } 141 | 142 | struct fat_version { 143 | const uint16_t version; 144 | constexpr explicit fat_version(uint16_t version) noexcept : version(version) { } 145 | friend std::ostream & operator<<(std::ostream &os, const struct fat_version &v) { 146 | auto fill = os.fill('0'); 147 | auto flags = os.flags(std::ios_base::dec | std::ios_base::right); 148 | os << (v.version >> 8) << '.' << std::setw(2) << (v.version & 0xFF); 149 | os.flags(flags); 150 | os.fill(fill); 151 | return os; 152 | } 153 | }; 154 | 155 | struct exfat_volume_flags { 156 | exfat::VolumeFlags flags; 157 | constexpr explicit exfat_volume_flags(exfat::VolumeFlags flags) noexcept : flags(flags) { } 158 | friend std::ostream & operator<<(std::ostream &os, const struct exfat_volume_flags &f) { 159 | using Flags = exfat::VolumeFlags; 160 | auto flags = f.flags; 161 | if (+(flags & (Flags::ACTIVE_FAT | Flags::VOLUME_DIRTY | Flags::MEDIA_FAILURE | Flags::CLEAR_TO_ZERO))) { 162 | if (+(flags & Flags::ACTIVE_FAT)) { 163 | os << "ActiveFat"; 164 | if (+(flags &= ~Flags::ACTIVE_FAT)) { 165 | os << " | "; 166 | } 167 | } 168 | if (+(flags & Flags::VOLUME_DIRTY)) { 169 | os << "VolumeDirty"; 170 | if (+(flags &= ~Flags::VOLUME_DIRTY)) { 171 | os << " | "; 172 | } 173 | } 174 | if (+(flags & Flags::MEDIA_FAILURE)) { 175 | os << "MediaFailure"; 176 | if (+(flags &= ~Flags::MEDIA_FAILURE)) { 177 | os << " | "; 178 | } 179 | } 180 | if (+(flags & Flags::CLEAR_TO_ZERO)) { 181 | os << "ClearToZero"; 182 | if (+(flags &= ~Flags::CLEAR_TO_ZERO)) { 183 | os << " | "; 184 | } 185 | } 186 | if (!flags) { 187 | return os; 188 | } 189 | } 190 | return os << flags; 191 | } 192 | }; 193 | 194 | static void getrandom_fully(void *buf, size_t buflen, unsigned flags = 0) { 195 | for (ssize_t r; (r = ::getrandom(buf, buflen, flags)) >= 0;) { 196 | if ((buflen -= r) == 0) { 197 | return; 198 | } 199 | buf = static_cast(buf) + r; 200 | } 201 | throw std::system_error(errno, std::system_category(), "getrandom"); 202 | } 203 | 204 | static bool f3_fill(void *buf, size_t size, uint64_t x) noexcept { 205 | bool changed = false; 206 | auto vec = static_cast *>(buf); 207 | size_t n = size / sizeof *vec; 208 | assert(n * sizeof *vec == size); 209 | for (size_t i = 0; i < n; ++i) { 210 | changed |= std::exchange(vec[i], x) != x; 211 | x = x * UINT64_C(0x10000000F) + 17; 212 | } 213 | return changed; 214 | } 215 | 216 | static size_t _pure count_flipped_bits(const void *buf1, const void *buf2, size_t size) noexcept { 217 | auto words1 = static_cast(buf1), words2 = static_cast(buf2); 218 | size_t n = size / sizeof(unsigned long); 219 | assert(n * sizeof(unsigned long) == size); 220 | return std::inner_product(words1, words1 + n, words2, size_t { }, std::plus { }, 221 | [](unsigned long x, unsigned long y) noexcept { 222 | return std::popcount(x ^ y); 223 | }); 224 | } 225 | 226 | static DynamicBuffer make_aligned_buffer(size_t alignment, size_t size) { 227 | assert(size % alignment == 0); 228 | DynamicBuffer buf; 229 | if (!(buf.bptr = static_cast(std::aligned_alloc(alignment, size)))) { 230 | throw std::bad_alloc(); 231 | } 232 | buf.eptr = (buf.pptr = buf.gptr = buf.bptr) + size; 233 | return buf; 234 | } 235 | 236 | enum class Action { 237 | READ, REREAD, ZEROOUT, READZEROS, F3WRITE, F3READ, SECDISCARD, DISCARD, TRASH, BAD, FREE, LIST 238 | }; 239 | 240 | static Action _pure parse_action(std::string_view sv) { 241 | if (sv == "read") { 242 | return Action::READ; 243 | } 244 | if (sv == "reread") { 245 | return Action::REREAD; 246 | } 247 | if (sv == "zeroout") { 248 | return Action::ZEROOUT; 249 | } 250 | if (sv == "readzeros") { 251 | return Action::READZEROS; 252 | } 253 | if (sv == "f3write") { 254 | return Action::F3WRITE; 255 | } 256 | if (sv == "f3read") { 257 | return Action::F3READ; 258 | } 259 | if (sv == "secdiscard") { 260 | return Action::SECDISCARD; 261 | } 262 | if (sv == "discard") { 263 | return Action::DISCARD; 264 | } 265 | if (sv == "trash") { 266 | return Action::TRASH; 267 | } 268 | if (sv == "bad") { 269 | return Action::BAD; 270 | } 271 | if (sv == "free") { 272 | return Action::FREE; 273 | } 274 | if (sv == "list") { 275 | return Action::LIST; 276 | } 277 | throw std::invalid_argument(std::string { sv }); 278 | } 279 | 280 | struct Actions : std::vector { 281 | using std::vector::vector; 282 | Actions & operator=(std::string_view sv) { 283 | this->clear(); 284 | for (std::string_view::size_type pos; 285 | (pos = sv.find(',')) != std::string_view::npos; 286 | sv.remove_prefix(pos + 1)) 287 | { 288 | if (pos > 0) { 289 | this->emplace_back(parse_action(sv.substr(0, pos))); 290 | } 291 | } 292 | if (!sv.empty()) { 293 | this->emplace_back(parse_action(sv)); 294 | } 295 | return *this; 296 | } 297 | void drop_destructive_actions(const char option[]) { 298 | std::erase_if(*this, [option](Action action) { 299 | switch (action) { 300 | case Action::READ: 301 | case Action::REREAD: 302 | return false; 303 | case Action::ZEROOUT: 304 | log_dropped_action(option, "zeroout"); 305 | return true; 306 | case Action::READZEROS: 307 | return false; 308 | case Action::F3WRITE: 309 | log_dropped_action(option, "f3write"); 310 | return true; 311 | case Action::F3READ: 312 | return false; 313 | case Action::SECDISCARD: 314 | log_dropped_action(option, "secdiscard"); 315 | return true; 316 | case Action::DISCARD: 317 | log_dropped_action(option, "discard"); 318 | return true; 319 | case Action::TRASH: 320 | log_dropped_action(option, "trash"); 321 | return true; 322 | case Action::BAD: 323 | log_dropped_action(option, "bad"); 324 | return true; 325 | case Action::FREE: 326 | log_dropped_action(option, "free"); 327 | return true; 328 | case Action::LIST: 329 | return false; 330 | } 331 | return false; 332 | }); 333 | std::clog.flush(); 334 | } 335 | private: 336 | static void log_dropped_action(const char option[], const char action[]) { 337 | std::clog << "--" << option << ": " << action << " action ignored due to --dry-run\n"; 338 | } 339 | }; 340 | 341 | int main(int argc, char *argv[]) { 342 | std::ios_base::sync_with_stdio(false); 343 | std::locale::global(std::locale("")); 344 | std::clog << std::showbase << std::internal; 345 | cli::Option 346 | bad_clusters_option { "bad-clusters", 'b' }, 347 | free_clusters_option { "free-clusters", 'f' }; 348 | cli::Option<> 349 | dry_run_option { "dry-run", 'n' }, 350 | verbose_option { "verbose", 'v' }, 351 | help_option { "help" }; 352 | bad_clusters_option.args.emplace_back(); 353 | free_clusters_option.args.emplace_back(Actions { Action::READ, Action::REREAD }); 354 | if ((argc = cli::parse(argc, argv, { 355 | &bad_clusters_option, 356 | &free_clusters_option, 357 | &dry_run_option, 358 | &verbose_option, 359 | &help_option 360 | })) != 2 || help_option) 361 | { 362 | std::clog << "usage: " << argv[0] << " [options] \n" 363 | "\n" 364 | "options:\n" 365 | "\t-b,--bad-clusters=[,...]\n" 366 | "\t-f,--free-clusters=[,...]\n" 367 | "\t-n,--dry-run\n" 368 | "\t-v,--verbose\n" 369 | "\n" 370 | "actions:\n" 371 | "\tread: read cluster; mark bad if device errors\n" 372 | "\treread: re-read cluster; mark bad if different\n" 373 | "\tzeroout: issue BLKZEROOUT ioctl on cluster\n" 374 | "\t (elided if a previous \"read\" found cluster already zeroed)\n" 375 | "\treadzeros: read cluster; mark bad if not zeroed\n" 376 | "\tf3write: fill cluster with reproducible data\n" 377 | "\t (elided if a previous \"read\" found cluster already correct)\n" 378 | "\tf3read: read cluster; mark bad if subtly changed\n" 379 | "\tsecdiscard: issue BLKSECDISCARD ioctl on cluster\n" 380 | "\tdiscard: issue BLKDISCARD ioctl on cluster\n" 381 | "\ttrash: fill cluster with pseudorandom garbage\n" 382 | "\tbad: mark cluster as bad unconditionally\n" 383 | "\tfree: mark cluster as free unconditionally\n" 384 | "\tlist: write cluster number to stdout\n" 385 | "\n" 386 | "defaults:\n" 387 | "\t--bad-clusters=\n" 388 | "\t--free-clusters=read,reread\n"; 389 | return EX_USAGE; 390 | } 391 | if (dry_run_option) { 392 | bad_clusters_option.args.back().drop_destructive_actions(bad_clusters_option.long_form); 393 | free_clusters_option.args.back().drop_destructive_actions(free_clusters_option.long_form); 394 | } 395 | 396 | auto const page_size = sysconf(_SC_PAGE_SIZE); 397 | if (page_size < 0) { 398 | throw std::system_error(errno, std::system_category(), "sysconf(_SC_PAGE_SIZE)"); 399 | } 400 | 401 | FileDescriptor fd(argv[1], (dry_run_option ? O_RDONLY : O_RDWR) | O_EXCL | O_DIRECT | O_CLOEXEC); 402 | 403 | auto const bs = new(std::align_val_t(page_size)) struct BootSector; 404 | fd.pread_fully(bs, sizeof *bs, 0); 405 | uint64_t total_logical_sectors; 406 | uint32_t reserved_logical_sectors, logical_sectors_per_fat; 407 | unsigned bytes_per_logical_sector_shift, bytes_per_logical_sector, logical_sectors_per_cluster_shift, fats; 408 | const struct BootSector::BPB *bpb = nullptr; 409 | const struct BootSector::FAT32Params *fat32 = nullptr; 410 | struct exfat::BootSector *exfat = nullptr; 411 | if (bs->boot_sector_signature[0] != static_cast(0x55) || 412 | bs->boot_sector_signature[1] != static_cast(0xAA) || 413 | std::memcmp(bs->oem_name, "NTFS ", 8) == 0 || 414 | (std::memcmp(bs->oem_name, "EXFAT ", 8) == 0 ? 415 | (exfat = reinterpret_cast(bs), 416 | std::any_of(std::begin(exfat->must_be_zero), std::end(exfat->must_be_zero), std::identity { }) || 417 | (bytes_per_logical_sector_shift = exfat->bytes_per_logical_sector_shift) < 9 || 418 | bytes_per_logical_sector_shift > 12 || 419 | (logical_sectors_per_cluster_shift = exfat->logical_sectors_per_cluster_shift) > 25 - bytes_per_logical_sector_shift || 420 | (fats = exfat->fats) < 1 || fats > 2 || 421 | (total_logical_sectors = exfat->total_logical_sectors) < UINT64_C(1) << 20 - bytes_per_logical_sector_shift || 422 | (reserved_logical_sectors = exfat->reserved_logical_sectors) < 24 || 423 | (logical_sectors_per_fat = exfat->logical_sectors_per_fat) < (uint64_t { exfat->total_data_clusters } + 2) * sizeof(uint32_t) + (bytes_per_logical_sector = 1u << bytes_per_logical_sector_shift) - 1 >> bytes_per_logical_sector_shift || 424 | exfat->data_start_lsn < reserved_logical_sectors + uint64_t { logical_sectors_per_fat } * fats || 425 | letoh(exfat->total_data_clusters) != std::min(total_logical_sectors - exfat->data_start_lsn >> logical_sectors_per_cluster_shift, (UINT64_C(1) << 32) - 11) || 426 | exfat->root_dir_start_cluster < 2 || 427 | exfat->root_dir_start_cluster > exfat->total_data_clusters + 1 || 428 | exfat->version < 0x0100 || 429 | exfat->version >> 8 > 99 || 430 | (exfat->version & 0xFF) > 99 || 431 | exfat->percent_in_use > 100 && exfat->percent_in_use != 0xFF) 432 | : (bpb = &bs->bpb, 433 | (bytes_per_logical_sector = bpb->bytes_per_logical_sector) == 0 || 434 | bytes_per_logical_sector != 1u << (bytes_per_logical_sector_shift = std::bit_width(bytes_per_logical_sector) - 1) || 435 | bpb->logical_sectors_per_cluster == 0 || 436 | bpb->logical_sectors_per_cluster != 1u << (logical_sectors_per_cluster_shift = std::bit_width(bpb->logical_sectors_per_cluster) - 1) || 437 | (reserved_logical_sectors = bpb->reserved_logical_sectors) == 0 || 438 | (fats = bpb->fats) == 0 || 439 | static_cast(bpb->media_descriptor) < 0xF8 && 440 | bpb->media_descriptor != static_cast(0xF0) || 441 | (total_logical_sectors = bpb->old_total_logical_sectors) == 0 && 442 | (total_logical_sectors = bpb->total_logical_sectors) == 0 || 443 | (logical_sectors_per_fat = bpb->logical_sectors_per_fat) == 0 && 444 | (logical_sectors_per_fat = (fat32 = &bs->fat32)->logical_sectors_per_fat) == 0))) 445 | { 446 | std::clog << argv[1] << ": device does not contain a FAT or exFAT file system" << std::endl; 447 | return EX_DATAERR; 448 | } 449 | uint32_t data_start_lsn, total_data_clusters; 450 | const unsigned cluster_shift = logical_sectors_per_cluster_shift + bytes_per_logical_sector_shift; 451 | const uint32_t cluster_size = UINT32_C(1) << cluster_shift; 452 | unsigned active_fat = 0; 453 | const struct BootSector::EBPB *ebpb = nullptr; 454 | if (exfat) { 455 | if (exfat->version >> 8 != 1) { 456 | std::clog << argv[1] << ": device contains unsupported exFAT revision " << fat_version(exfat->version) << std::endl; 457 | return EX_DATAERR; 458 | } 459 | { 460 | size_t boot_region_size = size_t(12) << bytes_per_logical_sector_shift; 461 | const std::unique_ptr boot_region { new(std::align_val_t(page_size)) std::byte[boot_region_size] }; 462 | fd.pread_fully(boot_region.get(), boot_region_size, 0); 463 | uint32_t checksum = exfat::checksum(uint32_t { }, boot_region.get(), boot_region.get() + offsetof(struct exfat::BootSector, volume_flags)); 464 | checksum = exfat::checksum(checksum, boot_region.get() + offsetof(struct exfat::BootSector, bytes_per_logical_sector_shift), boot_region.get() + offsetof(struct exfat::BootSector, percent_in_use)); 465 | checksum = exfat::checksum(checksum, boot_region.get() + offsetof(struct exfat::BootSector, reserved), boot_region.get() + boot_region_size - bytes_per_logical_sector); 466 | if (std::any_of(reinterpret_cast *>(boot_region.get() + boot_region_size - bytes_per_logical_sector), reinterpret_cast *>(boot_region.get() + boot_region_size), [checksum_le = htole(checksum)](le word) noexcept { 467 | return word != checksum_le; 468 | })) { 469 | std::clog << argv[1] << ": exFAT main boot region checksum is incorrect" << std::endl; 470 | return EX_DATAERR; 471 | } 472 | } 473 | active_fat = +(exfat->volume_flags & exfat::VolumeFlags::ACTIVE_FAT) ? 1 : 0; 474 | data_start_lsn = exfat->data_start_lsn; 475 | total_data_clusters = exfat->total_data_clusters; 476 | } 477 | else { 478 | if (fat32) { 479 | if (fat32->version != uint16_t(0)) { 480 | std::clog << argv[1] << ": device contains unsupported FAT32 version " << fat_version(fat32->version) << std::endl; 481 | return EX_DATAERR; 482 | } 483 | if (fat32->mirroring_flags & 0x80) { 484 | active_fat = fat32->mirroring_flags & 0xF; 485 | } 486 | ebpb = &fat32->ebpb; 487 | } 488 | else { 489 | ebpb = &bs->fat.ebpb; 490 | } 491 | if (ebpb->extended_boot_signature != static_cast(0x29) && 492 | ebpb->extended_boot_signature != static_cast(0x28)) 493 | { 494 | ebpb = nullptr; 495 | } 496 | data_start_lsn = reserved_logical_sectors + fats * logical_sectors_per_fat + (bpb->root_dir_entries * 32 + bytes_per_logical_sector - 1 >> bytes_per_logical_sector_shift); 497 | total_data_clusters = static_cast(total_logical_sectors - data_start_lsn >> logical_sectors_per_cluster_shift); 498 | } 499 | if (active_fat >= fats) { 500 | std::clog << argv[1] << ": active FAT #" << active_fat << " does not exist on a volume with " << fats << " FAT" << (fats == 1 ? "" : "s") << std::endl; 501 | return EX_DATAERR; 502 | } 503 | if (verbose_option) { 504 | if (exfat) { 505 | std::clog << 506 | "exfat.jump_instruction = " << exfat->jump_instruction << "\n" 507 | "exfat.file_system_name = " << std::quoted(std::string_view { exfat->file_system_name, sizeof exfat->file_system_name }) << "\n" 508 | "exfat.hidden_sectors = " << +exfat->hidden_sectors << "\n" 509 | "exfat.total_logical_sectors = " << +exfat->total_logical_sectors << 510 | " (" << byte_count(uintmax_t { exfat->total_logical_sectors } << bytes_per_logical_sector_shift) << ")\n" 511 | "exfat.reserved_logical_sectors = " << +exfat->reserved_logical_sectors << 512 | " (" << byte_count(uintmax_t { exfat->reserved_logical_sectors } << bytes_per_logical_sector_shift) << ")\n" 513 | "exfat.logical_sectors_per_fat = " << +exfat->logical_sectors_per_fat << 514 | " (" << byte_count(uintmax_t { exfat->logical_sectors_per_fat } << bytes_per_logical_sector_shift) << ")\n" 515 | "exfat.data_start_lsn = " << +exfat->data_start_lsn << 516 | " (" << byte_count(uintmax_t { exfat->data_start_lsn } << bytes_per_logical_sector_shift) << ")\n" 517 | "exfat.total_data_clusters = " << +exfat->total_data_clusters << 518 | " (" << byte_count(uintmax_t { exfat->total_data_clusters } << cluster_shift) << ")\n" 519 | "exfat.root_dir_start_cluster = " << +exfat->root_dir_start_cluster << "\n" 520 | "exfat.volume_id = " << std::hex << +exfat->volume_id << std::dec << "\n" 521 | "exfat.version = " << fat_version(exfat->version) << "\n" 522 | "exfat.volume_flags = " << exfat_volume_flags(exfat->volume_flags) << "\n" 523 | "exfat.bytes_per_logical_sector_shift = " << bytes_per_logical_sector_shift << 524 | " (" << byte_count(bytes_per_logical_sector) << ")\n" 525 | "exfat.logical_sectors_per_cluster_shift = " << logical_sectors_per_cluster_shift << 526 | " (" << byte_count(cluster_size) << ")\n" 527 | "exfat.fats = " << +exfat->fats << "\n" 528 | "exfat.physical_drive_number = " << +exfat->physical_drive_number << "\n" 529 | "exfat.percent_in_use = " << (exfat->percent_in_use == 0xFF ? std::hex : std::dec) << +exfat->percent_in_use << std::dec << "\n" 530 | "exfat.boot_sector_signature = " << exfat->boot_sector_signature << '\n'; 531 | } 532 | else { 533 | std::clog << 534 | "bs.jump_instruction = " << bs->jump_instruction << "\n" 535 | "bs.oem_name = " << std::quoted(std::string_view { bs->oem_name, sizeof bs->oem_name }) << "\n" 536 | "bpb.bytes_per_logical_sector = " << +bpb->bytes_per_logical_sector << "\n" 537 | "bpb.logical_sectors_per_cluster = " << +bpb->logical_sectors_per_cluster << 538 | " (" << byte_count(bpb->logical_sectors_per_cluster << bytes_per_logical_sector_shift) << ")\n" 539 | "bpb.reserved_logical_sectors = " << +bpb->reserved_logical_sectors << 540 | " (" << byte_count(bpb->reserved_logical_sectors << bytes_per_logical_sector_shift) << ")\n" 541 | "bpb.fats = " << +bpb->fats << "\n" 542 | "bpb.root_dir_entries = " << +bpb->root_dir_entries << "\n" 543 | "bpb.old_total_logical_sectors = " << +bpb->old_total_logical_sectors << 544 | " (" << byte_count(bpb->old_total_logical_sectors << bytes_per_logical_sector_shift) << ")\n" 545 | "bpb.media_descriptor = " << bpb->media_descriptor << "\n" 546 | "bpb.logical_sectors_per_fat = " << +bpb->logical_sectors_per_fat << 547 | " (" << byte_count(bpb->logical_sectors_per_fat << bytes_per_logical_sector_shift) << ")\n" 548 | "bpb.physical_sectors_per_track = " << +bpb->physical_sectors_per_track << "\n" 549 | "bpb.heads = " << +bpb->heads << '\n'; 550 | if (bpb->old_total_logical_sectors == uint16_t(0)) { 551 | std::clog << 552 | "bpb.hidden_sectors = " << +bpb->hidden_sectors << "\n" 553 | "bpb.total_logical_sectors = " << +bpb->total_logical_sectors << 554 | " (" << byte_count(uintmax_t { bpb->total_logical_sectors } << bytes_per_logical_sector_shift) << ")\n"; 555 | } 556 | if (fat32) { 557 | std::clog << 558 | "fat32.logical_sectors_per_fat = " << +fat32->logical_sectors_per_fat << 559 | " (" << byte_count(uintmax_t { fat32->logical_sectors_per_fat } << bytes_per_logical_sector_shift) << ")\n" 560 | "fat32.mirroring_flags = " << std::hex << +fat32->mirroring_flags << std::dec << "\n" 561 | "fat32.version = " << fat_version(fat32->version) << "\n" 562 | "fat32.root_dir_start_cluster = " << +fat32->root_dir_start_cluster << "\n" 563 | "fat32.fs_info_lsn = " << +fat32->fs_info_lsn << "\n" 564 | "fat32.boot_sector_backup_lsn = " << +fat32->boot_sector_backup_lsn << "\n" 565 | "fat32.reserved = " << fat32->reserved << '\n'; 566 | } 567 | if (ebpb) { 568 | std::clog << 569 | "ebpb.physical_drive_number = " << +ebpb->physical_drive_number << "\n" 570 | "ebpb.reserved = " << ebpb->reserved << "\n" 571 | "ebpb.extended_boot_signature = " << ebpb->extended_boot_signature << "\n" 572 | "ebpb.volume_id = " << std::hex << +ebpb->volume_id << std::dec << '\n'; 573 | if (ebpb->extended_boot_signature == static_cast(0x29)) { 574 | std::clog << 575 | "ebpb.volume_label = " << std::quoted(std::string_view { ebpb->volume_label, sizeof ebpb->volume_label }) << "\n" 576 | "ebpb.file_system_type = " << std::quoted(std::string_view { ebpb->file_system_type, sizeof ebpb->file_system_type }) << '\n'; 577 | } 578 | } 579 | std::clog << 580 | "bs.old_physical_drive_number = " << +bs->old_physical_drive_number << "\n" 581 | "bs.boot_sector_signature = " << bs->boot_sector_signature << "\n" 582 | "data_start_lsn = " << data_start_lsn << "\n" 583 | "total_data_clusters = " << total_data_clusters << 584 | " (" << byte_count(uintmax_t { total_data_clusters } << cluster_shift) << ')'; 585 | } 586 | } 587 | 588 | std::function get_fat_entry; 589 | std::function put_fat_entry; 590 | uint32_t expected_fat_id, bad_cluster, bitmap_first_cluster = 0; 591 | std::unique_ptr bitmap; 592 | size_t bitmap_size = 0; 593 | if (exfat) { 594 | get_fat_entry = [&bitmap](const void *fat, uint32_t cluster) noexcept -> uint32_t { 595 | if (cluster >= 2) { 596 | auto idx = cluster - 2; 597 | if ((bitmap[idx / 8] & std::byte { 1 } << idx % 8) == std::byte { }) { 598 | return 0; // cluster is free, irrespective of stale entry in FAT 599 | } 600 | if (!static_cast *>(fat)[cluster]) { 601 | return 0xFFFFFFFF; // cluster is in use, irrespective of stale entry in FAT 602 | } 603 | } 604 | return static_cast *>(fat)[cluster]; 605 | }; 606 | put_fat_entry = [&bitmap](void *fat, uint32_t cluster, uint32_t next) { 607 | if (cluster >= 2) { 608 | auto idx = cluster - 2; 609 | if (next) { 610 | bitmap[idx / 8] |= std::byte { 1 } << idx % 8; 611 | } 612 | else { 613 | bitmap[idx / 8] &= ~(std::byte { 1 } << idx % 8); 614 | } 615 | } 616 | static_cast *>(fat)[cluster] = next; 617 | }; 618 | expected_fat_id = 0xFFFFFFF8; 619 | bad_cluster = 0xFFFFFFF7; 620 | } 621 | else { 622 | uint32_t min_fat_size; 623 | unsigned fat_entry_width; 624 | if (total_data_clusters < 4085) { 625 | get_fat_entry = [](const void *fat, uint32_t cluster) noexcept -> uint32_t { 626 | auto row = static_cast(fat) + cluster / 2 * 3; 627 | if (cluster & 1) { 628 | return row[1] >> 4 | row[2] << 4; 629 | } 630 | else { 631 | return row[0] | (row[1] & 0xF) << 8; 632 | } 633 | }; 634 | put_fat_entry = [](void *fat, uint32_t cluster, uint32_t next) { 635 | if (next > 0xFFF) { 636 | throw std::out_of_range("illegal cluster number"); 637 | } 638 | auto row = static_cast(fat) + cluster / 2 * 3; 639 | if (cluster & 1) { 640 | row[1] = static_cast(row[1] & 0xF | next << 4); 641 | row[2] = static_cast(next >> 4); 642 | } 643 | else { 644 | row[0] = static_cast(next); 645 | row[1] = static_cast(row[1] & 0xF0 | next >> 8); 646 | } 647 | }; 648 | expected_fat_id = 0xF00 | static_cast(bpb->media_descriptor); 649 | bad_cluster = 0xFF7; 650 | min_fat_size = ((total_data_clusters + 2) * 3 + 1) / 2; 651 | fat_entry_width = 12; 652 | } 653 | else if (total_data_clusters < 65525) { 654 | get_fat_entry = [](const void *fat, uint32_t cluster) noexcept -> uint32_t { 655 | return static_cast *>(fat)[cluster]; 656 | }; 657 | put_fat_entry = [](void *fat, uint32_t cluster, uint32_t next) { 658 | if (next > 0xFFFF) { 659 | throw std::out_of_range("illegal cluster number"); 660 | } 661 | static_cast *>(fat)[cluster] = static_cast(next); 662 | }; 663 | expected_fat_id = 0xFF00 | static_cast(bpb->media_descriptor); 664 | bad_cluster = 0xFFF7; 665 | min_fat_size = (total_data_clusters + 2) * sizeof(uint16_t); 666 | fat_entry_width = 16; 667 | } 668 | else { 669 | get_fat_entry = [](const void *fat, uint32_t cluster) noexcept -> uint32_t { 670 | return static_cast *>(fat)[cluster] & 0x0FFFFFFF; 671 | }; 672 | put_fat_entry = [](void *fat, uint32_t cluster, uint32_t next) { 673 | if (next > 0x0FFFFFFF) { 674 | throw std::out_of_range("illegal cluster number"); 675 | } 676 | auto &entry = static_cast *>(fat)[cluster]; 677 | entry = entry & ~0x0FFFFFFF | next; 678 | }; 679 | expected_fat_id = 0x0FFFFF00 | static_cast(bpb->media_descriptor); 680 | bad_cluster = 0x0FFFFFF7; 681 | min_fat_size = (total_data_clusters + 2) * sizeof(uint32_t); 682 | fat_entry_width = 32; 683 | } 684 | if (verbose_option) { 685 | std::clog << " [FAT" << fat_entry_width << "]\n"; 686 | } 687 | if (logical_sectors_per_fat < min_fat_size + bytes_per_logical_sector - 1 >> bytes_per_logical_sector_shift) { 688 | std::clog << argv[1] << ": logical_sectors_per_fat=" << logical_sectors_per_fat << " is too small for total_data_clusters=" << total_data_clusters << std::endl; 689 | return EX_DATAERR; 690 | } 691 | } 692 | std::clog.flush(); 693 | 694 | struct FSInfoSector *fs_info = nullptr; 695 | if (fat32 && fat32->fs_info_lsn != uint16_t(0) && fat32->fs_info_lsn != uint16_t(0xFFFF)) { 696 | fs_info = new(std::align_val_t(page_size)) struct FSInfoSector; 697 | fd.pread_fully(fs_info, sizeof *fs_info, fat32->fs_info_lsn << bytes_per_logical_sector_shift); 698 | if (std::memcmp(fs_info->fs_info_sector_signature1, "RRaA", 4) != 0 || 699 | std::memcmp(fs_info->fs_info_sector_signature2, "rrAa", 4) != 0 || 700 | std::memcmp(fs_info->fs_info_sector_signature3, "\0\0\x55\xAA", 4) != 0) 701 | { 702 | delete fs_info, fs_info = nullptr; 703 | std::clog << argv[1] << ": FS Information Sector is invalid\n"; 704 | } 705 | else if (verbose_option) { 706 | std::clog << "fs_info.last_known_free_data_clusters = "; 707 | if (fs_info->last_known_free_data_clusters == 0xFFFFFFFF) { 708 | std::clog << std::hex << +fs_info->last_known_free_data_clusters << std::dec; 709 | } 710 | else { 711 | std::clog << +fs_info->last_known_free_data_clusters << 712 | " (" << byte_count(uintmax_t { fs_info->last_known_free_data_clusters } << cluster_shift) << ')'; 713 | } 714 | std::clog << "\n" 715 | "fs_info.most_recently_allocated_data_cluster = " << (fs_info->most_recently_allocated_data_cluster == 0xFFFFFFFF ? std::hex : std::dec) << +fs_info->most_recently_allocated_data_cluster << std::dec << '\n'; 716 | } 717 | } 718 | std::clog.flush(); 719 | 720 | const size_t fat_size = logical_sectors_per_fat << bytes_per_logical_sector_shift; 721 | auto const fat = new(std::align_val_t(page_size)) std::byte[fat_size]; 722 | fd.pread_fully(fat, fat_size, (reserved_logical_sectors << bytes_per_logical_sector_shift) + active_fat * fat_size); 723 | if (auto entry = get_fat_entry(fat, 0); entry != expected_fat_id) { 724 | std::clog << argv[1] << ": FAT ID is " << std::hex << entry << " but should be " << expected_fat_id << std::dec << '\n'; 725 | } 726 | if (exfat) { 727 | const size_t buffer_size = std::max(page_size, cluster_size); 728 | exfat::ClusterChainIO input(fd, *exfat, reinterpret_cast *>(fat), exfat->root_dir_start_cluster); 729 | InputSource source(input); 730 | exfat::Directory root(source, make_aligned_buffer(page_size, buffer_size)); 731 | while (auto entry = root.next_entry()) { 732 | switch (entry->generic.entry_type) { 733 | case exfat::ALLOC_BITMAP: { 734 | auto &ab = entry->alloc_bitmap; 735 | unsigned bitmap_id = +(ab.flags & exfat::BitmapFlags::BITMAP_ID) ? 1 : 0; 736 | if (verbose_option) { 737 | std::clog << 738 | "alloc_bitmap[" << bitmap_id << "].first_cluster = " << +ab.first_cluster << "\n" 739 | "alloc_bitmap[" << bitmap_id << "].data_length = " << +ab.data_length << 740 | " (" << byte_count(+ab.data_length) << ")\n"; 741 | } 742 | auto const expected_size = (exfat->total_data_clusters + 7) / 8; 743 | if (+ab.data_length != expected_size) { 744 | std::clog << argv[1] << ": exFAT allocation bitmap has size " << +ab.data_length << " (" << byte_count(ab.data_length) << ") but should have size " << expected_size << " (" << byte_count(expected_size) << ") for total_data_clusters=" << +exfat->total_data_clusters << std::endl; 745 | return EX_DATAERR; 746 | } 747 | if (bitmap_id == active_fat) { 748 | bitmap_size = narrow_check(expected_size + cluster_size - 1) & ~(cluster_size - 1); 749 | bitmap.reset(new(std::align_val_t(page_size)) std::byte[bitmap_size]); 750 | exfat::ClusterChainIO(fd, *exfat, reinterpret_cast *>(fat), bitmap_first_cluster = ab.first_cluster).read_fully(bitmap.get(), bitmap_size); 751 | } 752 | break; 753 | } 754 | case exfat::UPCASE_TABLE: { 755 | auto &ut = entry->upcase_table; 756 | if (verbose_option) { 757 | std::clog << 758 | "upcase_table.table_checksum = " << std::hex << +ut.table_checksum << std::dec << "\n" 759 | "upcase_table.first_cluster = " << +ut.first_cluster << "\n" 760 | "upcase_table.data_length = " << +ut.data_length << 761 | " (" << byte_count(+ut.data_length) << ")\n"; 762 | } 763 | break; 764 | } 765 | case exfat::VOLUME_LABEL: { 766 | auto &vl = entry->volume_label; 767 | if (verbose_option) { 768 | std::clog << 769 | "volume_label = " << std::quoted(to_string(std::u16string { vl.volume_label, vl.volume_label + vl.char_count })) << '\n'; 770 | } 771 | break; 772 | } 773 | case exfat::VOLUME_GUID: { 774 | auto &vg = entry->volume_guid; 775 | if (verbose_option) { 776 | std::clog << "volume_guid = " << UUID { vg.volume_guid } << '\n'; 777 | } 778 | break; 779 | } 780 | default: 781 | break; 782 | } 783 | } 784 | if (!bitmap) { 785 | std::clog << argv[1] << ": missing exFAT allocation bitmap" << std::endl; 786 | return EX_DATAERR; 787 | } 788 | } 789 | const uint32_t max_cluster = total_data_clusters + 1; 790 | uint32_t free_clusters = 0, bad_clusters = 0, used_clusters = 0; 791 | for (uint32_t cluster = 2; cluster <= max_cluster; ++cluster) { 792 | auto entry = get_fat_entry(fat, cluster); 793 | if (entry == 0) { 794 | ++free_clusters; 795 | } 796 | else if (entry == bad_cluster) { 797 | ++bad_clusters; 798 | } 799 | else { 800 | ++used_clusters; 801 | } 802 | } 803 | if (verbose_option) { 804 | std::clog << "FAT contains:\n" << 805 | std::setw(10) << used_clusters << " used cluster" << (used_clusters == 1 ? ' ' : 's') << 806 | " (" << std::setw(8) << byte_count(uintmax_t { used_clusters } << cluster_shift) << ")\n" << 807 | std::setw(10) << free_clusters << " free cluster" << (free_clusters == 1 ? ' ' : 's') << 808 | " (" << std::setw(8) << byte_count(uintmax_t { free_clusters } << cluster_shift) << ")\n" << 809 | std::setw(10) << bad_clusters << " bad cluster" << (bad_clusters == 1 ? ' ' : 's') << 810 | " (" << std::setw(8) << byte_count(uintmax_t { bad_clusters } << cluster_shift) << ")\n"; 811 | } 812 | if (fs_info && fs_info->last_known_free_data_clusters != free_clusters && fs_info->last_known_free_data_clusters != 0xFFFFFFFF) { 813 | std::clog << argv[1] << ": FS Information Sector free cluster count is incorrect\n"; 814 | } 815 | std::clog.flush(); 816 | 817 | if (bad_clusters_option.value().empty() && free_clusters_option.value().empty()) { 818 | return EX_OK; 819 | } 820 | 821 | unsigned buf_size_shift = 26u; // 64 MiB 822 | { 823 | struct sysinfo info { }; 824 | if (sysinfo(&info) == 0) { 825 | buf_size_shift = std::min(std::max(static_cast(std::bit_width(info.freeram - 1)) - 1, 23u /* 8 MiB */), buf_size_shift); 826 | } 827 | } 828 | uint32_t alignment_offset_clusters = 0; 829 | try { 830 | struct hd_geometry geo { }; 831 | fd.ioctl(HDIO_GETGEO, &geo); 832 | uint32_t data_start_abs_offset = static_cast((geo.start << 9) + (data_start_lsn << bytes_per_logical_sector_shift)); 833 | if ((data_start_abs_offset & (UINT32_C(1) << cluster_shift) - 1) == 0) { 834 | alignment_offset_clusters = (-data_start_abs_offset & (UINT32_C(1) << buf_size_shift) - 1) >> cluster_shift; 835 | } 836 | } 837 | catch (const std::system_error &) { 838 | // swallow; don't align cluster I/O 839 | } 840 | 841 | uint32_t marked_bad = 0, marked_free = 0, zeroed_out = 0, discarded = 0, trashed = 0, misplaced = 0; 842 | auto const buf1 = new(std::align_val_t(page_size)) std::byte[size_t(1) << buf_size_shift]; 843 | auto const buf2 = new(std::align_val_t(page_size)) std::byte[size_t(1) << buf_size_shift]; 844 | for (uint32_t from_cluster = 2, to_cluster, error_end = 0, progress = 0; 845 | from_cluster <= max_cluster; 846 | from_cluster = to_cluster) 847 | { 848 | to_cluster = from_cluster + 1; 849 | const Actions *actions; 850 | auto const entry = get_fat_entry(fat, from_cluster); 851 | if (entry == 0) { 852 | actions = &free_clusters_option.value(); 853 | } 854 | else if (entry == bad_cluster) { 855 | actions = &bad_clusters_option.value(); 856 | } 857 | else { 858 | continue; 859 | } 860 | if (actions->empty()) { 861 | continue; 862 | } 863 | auto actions_itr = actions->begin(); 864 | restart_action: 865 | if (from_cluster >= error_end) { 866 | for (uint32_t max_to_cluster = static_cast(std::min(from_cluster + (UINT64_C(1) << buf_size_shift - cluster_shift), max_cluster + 1)); 867 | to_cluster <= max_cluster && get_fat_entry(fat, to_cluster) == entry;) 868 | { 869 | if (++to_cluster > max_to_cluster) { 870 | to_cluster = (from_cluster + alignment_offset_clusters + (UINT32_C(1) << buf_size_shift - cluster_shift) & ~((UINT32_C(1) << buf_size_shift - cluster_shift) - 1)) - alignment_offset_clusters; 871 | break; 872 | } 873 | } 874 | } 875 | const uint32_t clusters = to_cluster - from_cluster; 876 | const size_t chunk_size = size_t { clusters } << cluster_shift; 877 | const off_t offset = data_start_lsn + (off_t { from_cluster - 2 } << logical_sectors_per_cluster_shift) << bytes_per_logical_sector_shift; 878 | auto const mark = [fat, &get_fat_entry, &put_fat_entry, &marked_bad, &marked_free, fs_info](const char message[], uint32_t cluster, off_t offset, uint32_t new_entry) { 879 | if (message) { 880 | std::clog << message << " cluster #" << cluster << " at offset " << std::hex << offset << std::dec << std::endl; 881 | } 882 | if (auto old_entry = get_fat_entry(fat, cluster); new_entry != old_entry) { 883 | put_fat_entry(fat, cluster, new_entry); 884 | if (!new_entry != !old_entry) { 885 | ++(new_entry ? marked_bad : marked_free); 886 | } 887 | } 888 | }; 889 | bool buf1_valid = false; 890 | for (; actions_itr != actions->end(); ++actions_itr) { 891 | switch (Action action = *actions_itr) { 892 | case Action::READ: 893 | try { 894 | fd.pread_fully(buf1, chunk_size, offset); 895 | buf1_valid = true; 896 | } 897 | catch (const std::system_error &e) { 898 | if (e.code().value() != EIO) { 899 | throw; 900 | } 901 | if (clusters > 1) { 902 | error_end = to_cluster, to_cluster = from_cluster + 1; 903 | goto restart_action; 904 | } 905 | mark("\rerror while reading", from_cluster, offset, bad_cluster); 906 | goto next_chunk; 907 | } 908 | break; 909 | case Action::READZEROS: 910 | std::memset(buf1, 0, chunk_size); 911 | buf1_valid = true; 912 | [[fallthrough]]; 913 | case Action::REREAD: 914 | try { 915 | if (!buf1_valid) { 916 | fd.pread_fully(buf1, chunk_size, offset); 917 | buf1_valid = true; 918 | } 919 | fd.pread_fully(buf2, chunk_size, offset); 920 | } 921 | catch (const std::system_error &e) { 922 | if (e.code().value() != EIO) { 923 | throw; 924 | } 925 | if (clusters > 1) { 926 | error_end = to_cluster, to_cluster = from_cluster + 1; 927 | goto restart_action; 928 | } 929 | mark("\rerror while reading", from_cluster, offset, bad_cluster); 930 | goto next_chunk; 931 | } 932 | for (uint32_t cluster = from_cluster, o = 0; cluster < to_cluster; ++cluster, o += cluster_size) { 933 | if (get_fat_entry(fat, cluster) != 1 && std::memcmp(buf1 + o, buf2 + o, cluster_size) != 0) { 934 | mark("\rflaky", cluster, offset + o, 1); 935 | } 936 | } 937 | break; 938 | case Action::ZEROOUT: 939 | if (!buf1_valid || std::any_of(buf1, buf1 + chunk_size, std::identity { })) { 940 | try { 941 | uint64_t span[2] = { static_cast(offset), chunk_size }; 942 | fd.ioctl(BLKZEROOUT, span); 943 | zeroed_out += clusters; 944 | std::memset(buf1, 0, chunk_size); 945 | buf1_valid = true; 946 | } 947 | catch (const std::system_error &e) { 948 | if (e.code().value() != EIO) { 949 | throw; 950 | } 951 | if (clusters > 1) { 952 | error_end = to_cluster, to_cluster = from_cluster + 1; 953 | goto restart_action; 954 | } 955 | mark("\rerror while zeroing", from_cluster, offset, bad_cluster); 956 | goto next_chunk; 957 | } 958 | } 959 | break; 960 | case Action::F3WRITE: { 961 | bool need_write = !buf1_valid; 962 | for (uint32_t o = 0; o < chunk_size; o += cluster_size) { 963 | need_write |= f3_fill(buf1 + o, cluster_size, offset + o); 964 | } 965 | if (need_write) { 966 | goto write_trash; 967 | } 968 | break; 969 | } 970 | case Action::F3READ: 971 | try { 972 | fd.pread_fully(buf1, chunk_size, offset); 973 | buf1_valid = true; 974 | } 975 | catch (const std::system_error &e) { 976 | if (e.code().value() != EIO) { 977 | throw; 978 | } 979 | if (clusters > 1) { 980 | error_end = to_cluster, to_cluster = from_cluster + 1; 981 | goto restart_action; 982 | } 983 | mark("\rerror while reading", from_cluster, offset, bad_cluster); 984 | goto next_chunk; 985 | } 986 | for (uint32_t cluster = from_cluster, o = 0; cluster < to_cluster; ++cluster, o += cluster_size) { 987 | if (get_fat_entry(fat, cluster) != 1 && (f3_fill(buf2 + o, cluster_size, offset + o), std::memcmp(buf1 + o, buf2 + o, cluster_size) != 0)) { 988 | if (count_flipped_bits(buf1 + o, buf2 + o, cluster_size) > cluster_size) { 989 | if (off_t found_offset = static_cast(*reinterpret_cast *>(buf1 + o)); 990 | found_offset != offset + o && (f3_fill(buf2 + o, cluster_size, found_offset), std::memcmp(buf1 + o, buf2 + o, cluster_size) == 0)) 991 | { 992 | ++misplaced; 993 | std::clog << "\rdata intended for offset " << std::hex << found_offset << std::dec << " were found in"; 994 | } 995 | else { 996 | std::clog << "\rcorrupted"; 997 | } 998 | std::clog << " cluster #" << cluster << " at offset " << std::hex << offset + o << std::dec << std::endl; 999 | } 1000 | else { 1001 | mark("\rflaky", cluster, offset + o, 1); 1002 | } 1003 | } 1004 | } 1005 | break; 1006 | case Action::SECDISCARD: 1007 | case Action::DISCARD: 1008 | try { 1009 | uint64_t span[2] = { static_cast(offset), chunk_size }; 1010 | fd.ioctl(action == Action::SECDISCARD ? BLKSECDISCARD : BLKDISCARD, span); 1011 | discarded += clusters; 1012 | buf1_valid = false; 1013 | } 1014 | catch (const std::system_error &e) { 1015 | if (e.code().value() != EIO) { 1016 | throw; 1017 | } 1018 | if (clusters > 1) { 1019 | error_end = to_cluster, to_cluster = from_cluster + 1; 1020 | goto restart_action; 1021 | } 1022 | mark("\rerror while discarding", from_cluster, offset, bad_cluster); 1023 | goto next_chunk; 1024 | } 1025 | break; 1026 | case Action::TRASH: 1027 | getrandom_fully(buf1, chunk_size); 1028 | write_trash: 1029 | try { 1030 | fd.pwrite_fully(buf1, chunk_size, offset); 1031 | trashed += clusters; 1032 | buf1_valid = true; 1033 | } 1034 | catch (const std::system_error &e) { 1035 | if (e.code().value() != EIO) { 1036 | throw; 1037 | } 1038 | if (clusters > 1) { 1039 | error_end = to_cluster, to_cluster = from_cluster + 1; 1040 | goto restart_action; 1041 | } 1042 | mark("\rerror while writing", from_cluster, offset, bad_cluster); 1043 | goto next_chunk; 1044 | } 1045 | break; 1046 | case Action::BAD: 1047 | for (uint32_t cluster = from_cluster, o = 0; cluster < to_cluster; ++cluster, o += cluster_size) { 1048 | mark(nullptr, cluster, offset + o, bad_cluster); 1049 | } 1050 | goto next_chunk; 1051 | case Action::FREE: 1052 | for (uint32_t cluster = from_cluster, o = 0; cluster < to_cluster; ++cluster, o += cluster_size) { 1053 | mark(nullptr, cluster, offset + o, 0); 1054 | } 1055 | break; 1056 | case Action::LIST: 1057 | for (uint32_t cluster = from_cluster; cluster < to_cluster; ++cluster) { 1058 | std::cout << cluster << '\n'; 1059 | } 1060 | break; 1061 | } 1062 | } 1063 | next_chunk: 1064 | if (auto old_progress = progress; (progress += to_cluster - from_cluster) / 1024 != old_progress / 1024) { 1065 | uint32_t total = free_clusters + bad_clusters; 1066 | std::clog.put('\r') << static_cast((uint64_t { progress } * 100 + total / 2) / total) << '%' << std::flush; 1067 | } 1068 | } 1069 | std::cout << std::flush; 1070 | std::clog << "\r\033[K"; 1071 | if ((marked_bad || marked_free) && !dry_run_option) { 1072 | for (uint32_t cluster = 2; cluster <= max_cluster; ++cluster) { 1073 | if (get_fat_entry(fat, cluster) == 1) { 1074 | put_fat_entry(fat, cluster, bad_cluster); 1075 | } 1076 | } 1077 | std::clog << "writing modified FAT"; 1078 | if (exfat) { 1079 | std::clog << " and allocation bitmap... " << std::flush; 1080 | auto saved_volume_flags = exfat->volume_flags; 1081 | static_assert(std::is_same_v>); 1082 | if ((exfat->volume_flags & (exfat::VolumeFlags::VOLUME_DIRTY | exfat::VolumeFlags::CLEAR_TO_ZERO)) != exfat::VolumeFlags::VOLUME_DIRTY) { 1083 | exfat->volume_flags &= ~exfat::VolumeFlags::CLEAR_TO_ZERO; 1084 | saved_volume_flags = exfat->volume_flags; 1085 | exfat->volume_flags |= exfat::VolumeFlags::VOLUME_DIRTY; 1086 | fd.pwrite_fully(exfat, sizeof *exfat, 0); 1087 | } 1088 | fd.pwrite_fully(fat, fat_size, (reserved_logical_sectors << bytes_per_logical_sector_shift) + active_fat * fat_size); 1089 | exfat::ClusterChainIO(fd, *exfat, reinterpret_cast *>(fat), bitmap_first_cluster).write_fully(bitmap.get(), bitmap_size); 1090 | if (auto percent_in_use = static_cast((total_data_clusters - (free_clusters - marked_bad + marked_free)) * UINT64_C(100) / total_data_clusters); 1091 | !(saved_volume_flags & exfat::VolumeFlags::VOLUME_DIRTY) || 1092 | exfat->percent_in_use != percent_in_use) 1093 | { 1094 | exfat->volume_flags = saved_volume_flags; 1095 | exfat->percent_in_use = static_cast(percent_in_use); 1096 | fd.pwrite_fully(exfat, sizeof *exfat, 0); 1097 | } 1098 | } 1099 | else { 1100 | std::clog << (fats == 1 ? "" : "s") << "... " << std::flush; 1101 | for (unsigned fat_idx = 0; fat_idx < fats; ++fat_idx) { 1102 | fd.pwrite_fully(fat, fat_size, (reserved_logical_sectors << bytes_per_logical_sector_shift) + fat_idx * fat_size); 1103 | } 1104 | if (fs_info && marked_bad != marked_free) { 1105 | fs_info->last_known_free_data_clusters = free_clusters - marked_bad + marked_free; 1106 | fd.pwrite_fully(fs_info, sizeof *fs_info, fat32->fs_info_lsn << bytes_per_logical_sector_shift); 1107 | } 1108 | } 1109 | std::clog << "done." << std::endl; 1110 | } 1111 | if (marked_bad || verbose_option) { 1112 | std::clog << marked_bad << " cluster" << (marked_bad == 1 ? "" : "s") << 1113 | " (" << byte_count(uintmax_t { marked_bad } << cluster_shift) << ')' << 1114 | (dry_run_option ? " would have been" : "") << " marked bad\n"; 1115 | } 1116 | if (marked_free || verbose_option) { 1117 | std::clog << marked_free << " cluster" << (marked_free == 1 ? "" : "s") << 1118 | " (" << byte_count(uintmax_t { marked_free } << cluster_shift) << ')' << 1119 | (dry_run_option ? " would have been" : "") << " marked free\n"; 1120 | } 1121 | if (zeroed_out || verbose_option) { 1122 | std::clog << zeroed_out << " cluster" << (zeroed_out == 1 ? "" : "s") << 1123 | " (" << byte_count(uintmax_t { zeroed_out } << cluster_shift) << ')' << 1124 | (dry_run_option ? " would have been" : "") << " zeroed out\n"; 1125 | } 1126 | if (discarded || verbose_option) { 1127 | std::clog << discarded << " cluster" << (discarded == 1 ? "" : "s") << 1128 | " (" << byte_count(uintmax_t { discarded } << cluster_shift) << ')' << 1129 | (dry_run_option ? " would have been" : "") << " discarded\n"; 1130 | } 1131 | if (trashed || verbose_option) { 1132 | std::clog << trashed << " cluster" << (trashed == 1 ? "" : "s") << 1133 | " (" << byte_count(uintmax_t { trashed } << cluster_shift) << ')' << 1134 | (dry_run_option ? " would have been" : "") << " trashed\n"; 1135 | } 1136 | if (misplaced) { 1137 | std::clog << misplaced << " cluster" << (misplaced == 1 ? "" : "s") << 1138 | " (" << byte_count(uintmax_t { misplaced } << cluster_shift) << ") " 1139 | "contained data intended for other clusters. This\n" 1140 | "may indicate that your flash media is fraudulent. For more information, see\n" 1141 | "https://github.com/AltraMayor/f3." << std::endl; 1142 | return 2; 1143 | } 1144 | std::clog.flush(); 1145 | return !!marked_bad; 1146 | } 1147 | --------------------------------------------------------------------------------