checkdisk 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checkdisk-0.1.0/MANIFEST.in +7 -0
- checkdisk-0.1.0/PKG-INFO +47 -0
- checkdisk-0.1.0/Readme.md +32 -0
- checkdisk-0.1.0/Workabouts.md +224 -0
- checkdisk-0.1.0/checkdisk.egg-info/PKG-INFO +47 -0
- checkdisk-0.1.0/checkdisk.egg-info/SOURCES.txt +19 -0
- checkdisk-0.1.0/checkdisk.egg-info/dependency_links.txt +1 -0
- checkdisk-0.1.0/checkdisk.egg-info/entry_points.txt +2 -0
- checkdisk-0.1.0/checkdisk.egg-info/top_level.txt +1 -0
- checkdisk-0.1.0/checkdisk.py +4328 -0
- checkdisk-0.1.0/format.py +677 -0
- checkdisk-0.1.0/pyproject.toml +33 -0
- checkdisk-0.1.0/replay.py +1085 -0
- checkdisk-0.1.0/setup.cfg +4 -0
- checkdisk-0.1.0/tests/ci_tests.py +220 -0
- checkdisk-0.1.0/tests/crash_tests.py +166 -0
- checkdisk-0.1.0/tests/fixtures/dirty-big-win.img.gz +0 -0
- checkdisk-0.1.0/tests/replay_tests.py +118 -0
- checkdisk-0.1.0/tests/tests.py +1897 -0
- checkdisk-0.1.0/tests/win_github_ci.py +178 -0
- checkdisk-0.1.0/tests/win_vhd.py +263 -0
checkdisk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: checkdisk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dependency-free NTFS repair tool — the chkdsk /f flow on raw devices and images
|
|
5
|
+
License-Expression: 0BSD
|
|
6
|
+
Project-URL: Homepage, https://github.com/h5rdly/checkdisk
|
|
7
|
+
Keywords: ntfs,chkdsk,filesystem,repair,recovery,forensics,mft
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Classifier: Topic :: System :: Filesystems
|
|
12
|
+
Classifier: Topic :: System :: Recovery Tools
|
|
13
|
+
Requires-Python: >=3.13
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# checkdisk
|
|
17
|
+
|
|
18
|
+
A self-contained, pure-Python NTFS repair tool — the parts of `chkdsk /f` that matter for a crashed volume (dangling dirents, torn indexes,
|
|
19
|
+
lost files, torn truncates, `$Bitmap`, `$Secure`, the USN journal).
|
|
20
|
+
|
|
21
|
+
`checkdisk` uses a native read/write engine that parses and rewrites on-disk NTFS structures directly, with multi-sector fixups and plan-then-commit
|
|
22
|
+
atomicity.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install checkdisk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or skip installing — `checkdisk.py` is self contained
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
`checkdisk` works on the raw (unmounted) device or an image file — never through
|
|
35
|
+
a mount:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
checkdisk list # find NTFS partitions (mounts nothing)
|
|
39
|
+
sudo umount /mnt/point # get off the volume first
|
|
40
|
+
sudo setfacl -m u:$USER:rw /dev/sdXN # or run the tool with sudo
|
|
41
|
+
checkdisk /f /dev/sdXN # dry run: reports, writes nothing
|
|
42
|
+
checkdisk /f /dev/sdXN --really # repair (each fix re-verified)
|
|
43
|
+
checkdisk /f /dev/sdXN # confirm: expect 0 remaining
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`/r` adds the full surface read. Running from the repo instead:
|
|
47
|
+
`python checkdisk.py /f ...` — identical.
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# checkdisk
|
|
2
|
+
|
|
3
|
+
A self-contained, pure-Python NTFS repair tool — the parts of `chkdsk /f` that matter for a crashed volume (dangling dirents, torn indexes,
|
|
4
|
+
lost files, torn truncates, `$Bitmap`, `$Secure`, the USN journal).
|
|
5
|
+
|
|
6
|
+
`checkdisk` uses a native read/write engine that parses and rewrites on-disk NTFS structures directly, with multi-sector fixups and plan-then-commit
|
|
7
|
+
atomicity.
|
|
8
|
+
|
|
9
|
+
## Install
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
pip install checkdisk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Or skip installing — `checkdisk.py` is self contained
|
|
16
|
+
|
|
17
|
+
## Quick start
|
|
18
|
+
|
|
19
|
+
`checkdisk` works on the raw (unmounted) device or an image file — never through
|
|
20
|
+
a mount:
|
|
21
|
+
|
|
22
|
+
```sh
|
|
23
|
+
checkdisk list # find NTFS partitions (mounts nothing)
|
|
24
|
+
sudo umount /mnt/point # get off the volume first
|
|
25
|
+
sudo setfacl -m u:$USER:rw /dev/sdXN # or run the tool with sudo
|
|
26
|
+
checkdisk /f /dev/sdXN # dry run: reports, writes nothing
|
|
27
|
+
checkdisk /f /dev/sdXN --really # repair (each fix re-verified)
|
|
28
|
+
checkdisk /f /dev/sdXN # confirm: expect 0 remaining
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
`/r` adds the full surface read. Running from the repo instead:
|
|
32
|
+
`python checkdisk.py /f ...` — identical.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
## The volume at a glance
|
|
2
|
+
|
|
3
|
+
An NTFS volume is bootstrapped from a single sector and then becomes entirely
|
|
4
|
+
self-describing — **everything is a file**, including all metadata.
|
|
5
|
+
|
|
6
|
+
The **boot sector** (`RawVolume.__init__`) carries:
|
|
7
|
+
|
|
8
|
+
| offset | field | note |
|
|
9
|
+
|-------:|-------|------|
|
|
10
|
+
| 3 | `"NTFS"` magic | |
|
|
11
|
+
| 11 | bytes/sector (u16) | almost always 512 |
|
|
12
|
+
| 13 | sectors/cluster (u8) | values > 0x80 encode `2^(256-x)` |
|
|
13
|
+
| 40 | total sectors (u64) | |
|
|
14
|
+
| 48 | LCN of $MFT (u64) | where record 0 lives |
|
|
15
|
+
| 64 | MFT record size (s8) | negative → `2^-x` bytes (typically −10 = 1024); positive → clusters |
|
|
16
|
+
|
|
17
|
+
The first 24 MFT records are reserved for system files. The ones this tool
|
|
18
|
+
touches:
|
|
19
|
+
|
|
20
|
+
| # | file | role |
|
|
21
|
+
|--:|------|------|
|
|
22
|
+
| 0 | `$MFT` | the Master File Table itself — one record per file |
|
|
23
|
+
| 1 | `$MFTMirr` | mirror of the first 4 records (drivers verify it) |
|
|
24
|
+
| 2 | `$LogFile` | metadata journal (see §6) |
|
|
25
|
+
| 3 | `$Volume` | volume flags — including the **dirty bit** |
|
|
26
|
+
| 5 | `.` (root) | root directory |
|
|
27
|
+
| 6 | `$Bitmap` | one bit per cluster: allocated or free (§7) |
|
|
28
|
+
| 9 | `$Secure` | all security descriptors, deduplicated (§5) |
|
|
29
|
+
| 10 | `$UpCase` | 64K-entry uppercasing table — defines case-insensitivity (§4) |
|
|
30
|
+
| 11 | `$Extend` | directory holding `$UsnJrnl`, `$Reparse`, `$ObjId`, `$Quota` |
|
|
31
|
+
|
|
32
|
+
Addresses are **LCN** (logical cluster number, volume-absolute) or **VCN**
|
|
33
|
+
(virtual cluster number, position within one file's data). Runlists map VCN→LCN.
|
|
34
|
+
|
|
35
|
+
## MFT records and attributes
|
|
36
|
+
|
|
37
|
+
### The FILE record
|
|
38
|
+
|
|
39
|
+
Each file is a fixed-size **FILE record** (`read_record`, `_load_record`):
|
|
40
|
+
|
|
41
|
+
| offset | field |
|
|
42
|
+
|-------:|-------|
|
|
43
|
+
| 0 | `"FILE"` magic |
|
|
44
|
+
| 4 | update-sequence array offset / count (fixups, below) |
|
|
45
|
+
| 16 | **sequence number** — bumped every time the record is freed |
|
|
46
|
+
| 18 | hard-link count |
|
|
47
|
+
| 20 | first-attribute offset |
|
|
48
|
+
| 22 | flags: bit 0 = in use, bit 1 = directory |
|
|
49
|
+
| 32 | base-record reference (non-zero ⇒ this is an *extension* record) |
|
|
50
|
+
|
|
51
|
+
A **file reference (mref)** is 64 bits: low 48 = record number, high 16 = the
|
|
52
|
+
sequence number *at the time the reference was made* (`MREF_MASK`). This is
|
|
53
|
+
NTFS's stale-pointer defence: when a record is reused, its sequence changes,
|
|
54
|
+
and every old reference to it becomes detectably stale. Half the repairs in
|
|
55
|
+
this tool reduce to "does the sequence still match?" (`_mref_live`).
|
|
56
|
+
|
|
57
|
+
### Multi-sector fixups — how NTFS detects torn writes
|
|
58
|
+
|
|
59
|
+
Any structure larger than a sector (FILE records, INDX blocks) is protected by
|
|
60
|
+
the **update sequence array**: before writing, the last 2 bytes of every sector
|
|
61
|
+
are stashed in the header's USA and replaced by a counter (USN); the counter is
|
|
62
|
+
bumped each write. On read, all sector tails must equal the USN — if the disk
|
|
63
|
+
died mid-write, some sectors are old and some new, the tails disagree, and the
|
|
64
|
+
record is **torn** (`_apply_fixups` / inverse `_seal_fixups`). Torn = the
|
|
65
|
+
strongest possible corruption signal: the structure's content cannot be
|
|
66
|
+
trusted at all.
|
|
67
|
+
|
|
68
|
+
The seal/unseal pair is also a bug magnet for anything that *writes* these
|
|
69
|
+
structures, and the failure has a recognizable fingerprint. Re-seal a buffer
|
|
70
|
+
that was never unsealed and the stale USN gets captured into the USA as if it
|
|
71
|
+
were data: exactly two garbage bytes at each sector-tail offset (0x1FE, 0x3FE,
|
|
72
|
+
…), typically equal to a recent USN, with every other byte intact. Corruption
|
|
73
|
+
confined to those offsets means a mis-sealed writer, not random damage — worth
|
|
74
|
+
checking before blaming the disk.
|
|
75
|
+
|
|
76
|
+
### Attributes
|
|
77
|
+
|
|
78
|
+
A record's content is a chain of **attributes** (`_attrs` walks it): each has a
|
|
79
|
+
type, length, optional UTF-16LE name, and is **resident** (value inline in the
|
|
80
|
+
record) or **non-resident** (value on clusters, located by a runlist). The
|
|
81
|
+
chain ends with type `0xFFFFFFFF`. Types used here: `$STANDARD_INFORMATION`
|
|
82
|
+
(0x10, holds the security_id at value offset 52), `$ATTRIBUTE_LIST` (0x20),
|
|
83
|
+
`$FILE_NAME` (0x30), `$OBJECT_ID` (0x40), `$DATA` (0x80), `$INDEX_ROOT` (0x90),
|
|
84
|
+
`$INDEX_ALLOCATION` (0xA0), `$BITMAP` (0xB0), `$REPARSE_POINT` (0xC0).
|
|
85
|
+
|
|
86
|
+
`$FILE_NAME` is the linchpin of repair: it stores the parent directory's mref
|
|
87
|
+
(offset 0), name length (64), namespace (65: POSIX/WIN32/DOS), and the name
|
|
88
|
+
(66). **The same fact exists twice** — in the file's record *and* in the
|
|
89
|
+
parent's index — and that redundancy is what makes directory repair possible.
|
|
90
|
+
|
|
91
|
+
### Runlists (mapping pairs)
|
|
92
|
+
|
|
93
|
+
Non-resident data is described by a compact varint encoding
|
|
94
|
+
(`_check_mapping_pairs` decodes + validates, `_encode_mapping_pairs` builds):
|
|
95
|
+
each pair is a header byte (low nibble = length-field size, high nibble =
|
|
96
|
+
offset-field size), a run length, and an LCN **delta from the previous run**
|
|
97
|
+
(signed). **Both fields are signed**, the length included: a 128-cluster run
|
|
98
|
+
must encode its length as two bytes (`80 00`) — a lone `80` sign-extends to
|
|
99
|
+
−128 and every NTFS implementation then rejects the whole runlist. Offset
|
|
100
|
+
size 0 = sparse run (a hole). A zero header byte terminates.
|
|
101
|
+
The attribute header redundantly states `lowest_vcn`/`highest_vcn` — the
|
|
102
|
+
decoded runs must cover exactly that range, another self-check this tool
|
|
103
|
+
leans on.
|
|
104
|
+
|
|
105
|
+
### Extension records and $ATTRIBUTE_LIST
|
|
106
|
+
|
|
107
|
+
When one record can't hold all attributes, they spill into **extension
|
|
108
|
+
records**; a resident-or-not `$ATTRIBUTE_LIST` in the base enumerates where
|
|
109
|
+
every attribute lives (`_record_attrs_uncached` follows it transparently;
|
|
110
|
+
`_spill_index_alloc` performs the spill natively when a directory outgrows its
|
|
111
|
+
base record).
|
|
112
|
+
|
|
113
|
+
## Indexes — the B+ trees
|
|
114
|
+
|
|
115
|
+
A directory is not a list; it's a **B+ tree keyed by name**. Three attributes
|
|
116
|
+
cooperate (all named `$I30` for directories):
|
|
117
|
+
|
|
118
|
+
- `$INDEX_ROOT` (resident): header (collation rule, index block size) + the
|
|
119
|
+
root node. A *small* index is just this.
|
|
120
|
+
- `$INDEX_ALLOCATION` (non-resident): the **INDX blocks** — 4 KiB fixup-
|
|
121
|
+
protected nodes (`"INDX"` magic, header VCN at offset 16, INDEX_HEADER at 24).
|
|
122
|
+
- `$BITMAP`: one bit per INDX block — allocated blocks whose bit is clear are
|
|
123
|
+
free and may contain stale garbage legally.
|
|
124
|
+
|
|
125
|
+
A node is a run of **INDEX_ENTRY** structures: `{value, entry_len(8),
|
|
126
|
+
key_len(10), flags(12)}` with the key at offset 16. Flags: bit 0 = has a
|
|
127
|
+
subnode (its VCN sits in the entry's **last 8 bytes**), bit 1 = END entry (no
|
|
128
|
+
key; terminates the node; its subnode pointer, if any, covers everything
|
|
129
|
+
greater than the last key). Directory entries store the child's mref as the
|
|
130
|
+
value at offset 0; the key is the **entire `$FILE_NAME` attribute value**.
|
|
131
|
+
|
|
132
|
+
Ordering is `COLLATION_FILE_NAME` (0x01): compare names **uppercased through
|
|
133
|
+
the volume's own `$UpCase` table**, not through any programming language's
|
|
134
|
+
notion of case (`_upcase_seq`, `names_equal`). A prefix sorts first.
|
|
135
|
+
|
|
136
|
+
That table is more load-bearing than it looks: **Windows chkdsk silently
|
|
137
|
+
skips its entire index verification stage on a volume whose `$UpCase` is not
|
|
138
|
+
the exact frozen table it expects** — no warning, exit 0. The frozen table
|
|
139
|
+
predates modern Unicode: characters that gained uppercase mappings later map
|
|
140
|
+
to *themselves* on real volumes, and the iota-subscript vocalics map to their
|
|
141
|
+
titlecase forms.
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
## The journals
|
|
145
|
+
|
|
146
|
+
- **`$LogFile`** is a metadata write-ahead log. This tool — like chkdsk —
|
|
147
|
+
**resets it rather than replaying it** (`reset_logfile` fills it with
|
|
148
|
+
`0xFF`): the structural checks reconcile the on-disk state directly, which
|
|
149
|
+
makes replay redundant, and a reset log is unambiguously clean. The reset
|
|
150
|
+
happens as `/f --really`'s **final step, and only when every finding above
|
|
151
|
+
it converged** — an unresolved finding leaves the log and flag alone so the
|
|
152
|
+
next mount still triggers a full check.
|
|
153
|
+
- The **dirty bit** lives in `$Volume`'s `$VOLUME_INFORMATION` (flags bit 0).
|
|
154
|
+
The write engine *sets it on open and clears it on clean close*
|
|
155
|
+
(`RawVolumeRW.__init__`/`close`) so that a crash mid-repair leaves the
|
|
156
|
+
volume flagged for a full check — the same discipline the driver uses. A
|
|
157
|
+
volume that arrives *already* flagged (a crashed session) is itself a
|
|
158
|
+
finding: the flag is not cosmetic — Windows re-checks a dirty volume at
|
|
159
|
+
boot, and Linux's ntfs3 **refuses to mount one at all** — so a repair that
|
|
160
|
+
fixes every structure but leaves the flag set has not finished the job.
|
|
161
|
+
- **`$UsnJrnl`** (`\$Extend\$UsnJrnl`) is the *user-visible* change journal:
|
|
162
|
+
`$Max` (32 bytes: max size, allocation delta, journal id, LowestValidUsn)
|
|
163
|
+
and `$J`, an append-only sparse stream whose **byte offset is the USN**.
|
|
164
|
+
Old content is punched out with sparse holes, so the file's allocated tail
|
|
165
|
+
is the live window. Each V2 record self-states its USN at offset 24 — it
|
|
166
|
+
must equal the record's own position (`_walk_usn_records`).
|
|
167
|
+
|
|
168
|
+
### What `$LogFile` replay actually does (research)
|
|
169
|
+
|
|
170
|
+
The `$LogFile` is an LFS (Log File Service) circular log of two page kinds: the
|
|
171
|
+
first two 4 KiB pages are **restart pages** (`RSTR`) holding a checkpoint pointer
|
|
172
|
+
plus per-client state; the rest are **record pages** (`RCRD`) carrying the log
|
|
173
|
+
records. A driver's crash recovery is three passes from the last checkpoint:
|
|
174
|
+
|
|
175
|
+
- **restart selection** — take the restart page with the higher `current_lsn`;
|
|
176
|
+
the other is a stale backup (fall back to it if the newest checkpoint's own
|
|
177
|
+
pages never reached the disk).
|
|
178
|
+
- **analysis** — rebuild the transaction / dirty-page / open-attribute tables
|
|
179
|
+
from the checkpoint's table dumps, walking records forward to find `redo_lsn`,
|
|
180
|
+
the oldest LSN any still-dirty page needs.
|
|
181
|
+
- **redo** — from `redo_lsn`, re-apply each committed op whose target page is
|
|
182
|
+
older than the record (`page_lsn < record_lsn`, the LSN gate; a page already
|
|
183
|
+
past the change is skipped).
|
|
184
|
+
- **undo** — roll back ops of transactions that never committed, walking each
|
|
185
|
+
transaction's undo-next chain backward.
|
|
186
|
+
|
|
187
|
+
The load-bearing subtlety, learned building a replayer validated chkdsk-clean on
|
|
188
|
+
a real Windows crash: **NTFS logs multi-sector-protected pages (FILE records,
|
|
189
|
+
INDX blocks) in *logical* form — the update-sequence fixup is NOT applied.** In a
|
|
190
|
+
logged page image the sector tails hold the real data, not the USN; sealing is a
|
|
191
|
+
flush-time concern. So a redo that lays down a fresh INDX block writes a logical
|
|
192
|
+
page, and a *later* redo against it must accept that logical form (its fixup
|
|
193
|
+
check 'fails' because tails ≠ USN — expected, not a torn page) and re-seal only
|
|
194
|
+
on write-back. A replayer that demands a sealed on-disk block at every step
|
|
195
|
+
silently drops those ops.
|
|
196
|
+
|
|
197
|
+
The same rule governs the **read side**, and getting it wrong there is much
|
|
198
|
+
quieter. Target FILE records must be loaded in logical form too (fixups
|
|
199
|
+
applied), because log ops describe the unsealed image and the write path
|
|
200
|
+
re-seals on flush. Modify a still-sealed buffer and the re-seal captures the
|
|
201
|
+
*old* USN into the USA as if it were data: every field that straddles a
|
|
202
|
+
sector-tail offset (0x1FE / 0x3FE of a 1 KiB record) silently gains two garbage
|
|
203
|
+
bytes, and everything else stays perfect. On a real crash volume this surfaced
|
|
204
|
+
as `$Extend\$RmMetadata\$Repair:$Verify` claiming `allocated_size =
|
|
205
|
+
0x19 << 48 | true size` — the leaked USN sitting in the high bytes — plus
|
|
206
|
+
phantom dangling entries and bitmap drift in other records whose structures
|
|
207
|
+
happened to cross a tail. The bug carried a second lesson: **Windows 10's
|
|
208
|
+
chkdsk passed the damaged volume; Windows Server 2022's flagged it — and this
|
|
209
|
+
tool's own checker had flagged it all along.** "chkdsk-clean" is a floor, not a
|
|
210
|
+
proof, and the floor's height depends on which chkdsk; when a strict checker
|
|
211
|
+
and a lenient oracle disagree, suspect the oracle's leniency before the
|
|
212
|
+
checker's pedantry.
|
|
213
|
+
|
|
214
|
+
Two more traps: the **v2 log relocates the record-page
|
|
215
|
+
header** — its logical file offset is at 0x3C and last-end LSN at 0x20, so the
|
|
216
|
+
0x18 "first free byte" field some parsers read is v1-shaped and truncates a v2
|
|
217
|
+
walk; and a **dry run applies nothing** (redo/undo only run when you commit), so
|
|
218
|
+
"zero ops applied" can just mean you didn't ask.
|
|
219
|
+
|
|
220
|
+
Why this tool still **resets** rather than replays: the structural checks
|
|
221
|
+
reconcile the on-disk state directly, so replay yields no repair the passes
|
|
222
|
+
don't already achieve, and an all-`0xFF` log is unambiguously clean to the next
|
|
223
|
+
mount. A genuine redo/undo engine (`replay.py`) that *does* produce a
|
|
224
|
+
chkdsk-clean volume is kept as research, not the repair path.
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: checkdisk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Dependency-free NTFS repair tool — the chkdsk /f flow on raw devices and images
|
|
5
|
+
License-Expression: 0BSD
|
|
6
|
+
Project-URL: Homepage, https://github.com/h5rdly/checkdisk
|
|
7
|
+
Keywords: ntfs,chkdsk,filesystem,repair,recovery,forensics,mft
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
11
|
+
Classifier: Topic :: System :: Filesystems
|
|
12
|
+
Classifier: Topic :: System :: Recovery Tools
|
|
13
|
+
Requires-Python: >=3.13
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
|
|
16
|
+
# checkdisk
|
|
17
|
+
|
|
18
|
+
A self-contained, pure-Python NTFS repair tool — the parts of `chkdsk /f` that matter for a crashed volume (dangling dirents, torn indexes,
|
|
19
|
+
lost files, torn truncates, `$Bitmap`, `$Secure`, the USN journal).
|
|
20
|
+
|
|
21
|
+
`checkdisk` uses a native read/write engine that parses and rewrites on-disk NTFS structures directly, with multi-sector fixups and plan-then-commit
|
|
22
|
+
atomicity.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
pip install checkdisk
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Or skip installing — `checkdisk.py` is self contained
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
`checkdisk` works on the raw (unmounted) device or an image file — never through
|
|
35
|
+
a mount:
|
|
36
|
+
|
|
37
|
+
```sh
|
|
38
|
+
checkdisk list # find NTFS partitions (mounts nothing)
|
|
39
|
+
sudo umount /mnt/point # get off the volume first
|
|
40
|
+
sudo setfacl -m u:$USER:rw /dev/sdXN # or run the tool with sudo
|
|
41
|
+
checkdisk /f /dev/sdXN # dry run: reports, writes nothing
|
|
42
|
+
checkdisk /f /dev/sdXN --really # repair (each fix re-verified)
|
|
43
|
+
checkdisk /f /dev/sdXN # confirm: expect 0 remaining
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
`/r` adds the full surface read. Running from the repo instead:
|
|
47
|
+
`python checkdisk.py /f ...` — identical.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
MANIFEST.in
|
|
2
|
+
Readme.md
|
|
3
|
+
Workabouts.md
|
|
4
|
+
checkdisk.py
|
|
5
|
+
format.py
|
|
6
|
+
pyproject.toml
|
|
7
|
+
replay.py
|
|
8
|
+
checkdisk.egg-info/PKG-INFO
|
|
9
|
+
checkdisk.egg-info/SOURCES.txt
|
|
10
|
+
checkdisk.egg-info/dependency_links.txt
|
|
11
|
+
checkdisk.egg-info/entry_points.txt
|
|
12
|
+
checkdisk.egg-info/top_level.txt
|
|
13
|
+
tests/ci_tests.py
|
|
14
|
+
tests/crash_tests.py
|
|
15
|
+
tests/replay_tests.py
|
|
16
|
+
tests/tests.py
|
|
17
|
+
tests/win_github_ci.py
|
|
18
|
+
tests/win_vhd.py
|
|
19
|
+
tests/fixtures/dirty-big-win.img.gz
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
checkdisk
|