fix(v5): Laufwerks-Gesundheit zählt nur das eigene Gerät (\Device\CdRomN), nie eine Festplatte
WAS: win32.ts kennt QueryDosDeviceW (am 12.09.2026 an diesem PC gemessen: C: → \Device\HarddiskVolume7, X: → \Device\LanmanRedirector\…\192.168.178.62\rippy); LaufwerkApi.geraeteName liefert das Kernel-Gerät hinter dem Buchstaben. beurteilen() filtert damit auf DIESES Gerät; ohne Gerätename zählt nur noch der cdrom-Treiber (gehoertZuLaufwerk). Vorher nahm der XPath cdrom UND disk für alle Geräte — eine hustende Festplatte oder ein zweites Laufwerk erzeugte „VERDACHT: DIE HARDWARE" in der Kachel des Blu-ray- Laufwerks. WARUM: Durchsicht 12.09.2026, Fund F11 — mit Tests (Festplatten- Ereignisse, zweites Laufwerk, ruhig mit Gerätename). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
551f1d7b2c
commit
783b8e4897
@@ -195,7 +195,16 @@ export class LaufwerkDienst {
|
||||
})
|
||||
return
|
||||
}
|
||||
const bericht = beurteilen(ergebnis.ereignisse, GESUNDHEIT_STUNDEN)
|
||||
// 5.7.0 (F11): nur die Ereignisse DIESES Geräts — \Device\CdRomN
|
||||
// hinter dem Buchstaben, nicht die einer Festplatte oder eines zweiten
|
||||
// Laufwerks. Ohne Gerätename (Nachbau, Fehlschlag) zählt nur cdrom.
|
||||
let geraet = ''
|
||||
try {
|
||||
geraet = this.api?.geraeteName(id) ?? ''
|
||||
} catch (fehler) {
|
||||
this.kontext.protokoll.schreiben(`Laufwerk ${id}: Gerätename nicht ermittelbar (${String(fehler)})`)
|
||||
}
|
||||
const bericht = beurteilen(ergebnis.ereignisse, GESUNDHEIT_STUNDEN, undefined, geraet)
|
||||
this.kontext.protokoll.schreiben(`Laufwerk ${id} geprüft: ${bericht.urteil} — ${bericht.text}`)
|
||||
this.kontext.anFenster({ art: 'laufwerk-gesundheit', id, bericht })
|
||||
}
|
||||
|
||||
@@ -84,6 +84,9 @@ export class Win32Fehler extends Error {
|
||||
export interface LaufwerkApi {
|
||||
laufwerksbuchstaben(): string[]
|
||||
laufwerkstyp(buchstabe: string): number
|
||||
/** Kernel-Gerätename hinter dem Buchstaben (\Device\CdRom0) — '' wenn
|
||||
* nicht zu ermitteln (5.7.0, F11). */
|
||||
geraeteName(buchstabe: string): string
|
||||
/** Öffnet das Gerät. `zugriff` 0 fragt das GERÄT, GENERIC_READ das MEDIUM —
|
||||
* bei gestörter Disc scheitert das zweite und das erste geht weiter
|
||||
* (rc11-Befund, in beweise/laufwerk.js nachgemessen). Wirft Win32Fehler. */
|
||||
|
||||
@@ -62,10 +62,24 @@ export function ereignisseAusXml(xml: string): LaufwerkEreignis[] {
|
||||
return liste
|
||||
}
|
||||
|
||||
/** Gehört dieses Ereignis zu DIESEM Laufwerk? (pur, 5.7.0 — Durchsicht F11)
|
||||
* Mit bekanntem Kernel-Gerät (\Device\CdRom0) zählt nur, was dieses
|
||||
* Gerät nennt; ohne Gerätename zählt nur der cdrom-Treiber — nie mehr die
|
||||
* Festplatten (Provider disk), die vorher ein „VERDACHT: DIE HARDWARE"
|
||||
* für das Blu-ray-Laufwerk auslösten. */
|
||||
export function gehoertZuLaufwerk(e: LaufwerkEreignis, geraet: string): boolean {
|
||||
if (geraet.length > 0) return e.geraet.toLowerCase().includes(geraet.toLowerCase())
|
||||
return e.provider === 'cdrom'
|
||||
}
|
||||
|
||||
/** Urteil aus den Ereignissen (pur). `seit` grenzt auf einen Zeitraum ein
|
||||
* (z. B. den laufenden Rip); ohne `seit` zählt alles, was da ist. */
|
||||
export function beurteilen(ereignisse: readonly LaufwerkEreignis[], stunden: number, seit?: Date): LaufwerkBericht {
|
||||
const relevant = seit === undefined ? [...ereignisse] : ereignisse.filter((e) => new Date(e.zeit).getTime() >= seit.getTime())
|
||||
* (z. B. den laufenden Rip); ohne `seit` zählt alles, was da ist.
|
||||
* `geraet` ist der Kernel-Gerätename des Laufwerks (\Device\CdRom0),
|
||||
* '' wenn unbekannt — dann zählen alle cdrom-Ereignisse. */
|
||||
export function beurteilen(ereignisse: readonly LaufwerkEreignis[], stunden: number, seit?: Date, geraet = ''): LaufwerkBericht {
|
||||
const relevant = ereignisse.filter(
|
||||
(e) => gehoertZuLaufwerk(e, geraet) && (seit === undefined || new Date(e.zeit).getTime() >= seit.getTime()),
|
||||
)
|
||||
let fehlerhafteBloecke = 0
|
||||
let controllerfehler = 0
|
||||
let sonstige = 0
|
||||
@@ -87,7 +101,7 @@ export function beurteilen(ereignisse: readonly LaufwerkEreignis[], stunden: num
|
||||
if (relevant.length === 0) {
|
||||
return {
|
||||
urteil: 'ruhig',
|
||||
text: `Das Windows-Protokoll meldet ${zeitraum} nichts zum Laufwerk — weder Lesefehler noch Hardware-Fehler.`,
|
||||
text: `Das Windows-Protokoll meldet ${zeitraum} nichts zu${geraet.length > 0 ? ` ${geraet}` : 'm Laufwerk'} — weder Lesefehler noch Hardware-Fehler.`,
|
||||
fehlerhafteBloecke,
|
||||
controllerfehler,
|
||||
sonstige,
|
||||
|
||||
@@ -24,6 +24,15 @@ export function win32Api(): LaufwerkApi {
|
||||
)
|
||||
const CloseHandle = kernel32.func('bool __stdcall CloseHandle(void *hObject)')
|
||||
const GetLastError = kernel32.func('uint32_t __stdcall GetLastError()')
|
||||
// 5.7.0 (Durchsicht F11): Welches Kernel-Gerät steckt hinter G:? —
|
||||
// QueryDosDeviceW liefert \Device\CdRom0 (learn.microsoft.com,
|
||||
// fileapi/nf-fileapi-querydosdevicew: Ziel ist eine Mehrfach-Zeichenkette,
|
||||
// der erste Eintrag zählt; am 12.09.2026 an C: dieses PCs gemessen).
|
||||
// Damit zählt die Laufwerks-Gesundheit nur noch die Ereignisse DIESES
|
||||
// Laufwerks, nicht die einer hustenden Festplatte.
|
||||
const QueryDosDeviceW = kernel32.func(
|
||||
'uint32_t __stdcall QueryDosDeviceW(str16 lpDeviceName, _Out_ char16_t *lpTargetPath, uint32_t ucchMax)',
|
||||
)
|
||||
const DeviceIoControl = kernel32.func(
|
||||
'bool __stdcall DeviceIoControl(void *hDevice, uint32_t dwIoControlCode, void *lpInBuffer, uint32_t nInBufferSize, _Out_ void *lpOutBuffer, uint32_t nOutBufferSize, _Out_ uint32_t *lpBytesReturned, void *lpOverlapped)',
|
||||
)
|
||||
@@ -65,6 +74,16 @@ export function win32Api(): LaufwerkApi {
|
||||
CloseHandle(handle)
|
||||
},
|
||||
|
||||
geraeteName(buchstabe: string): string {
|
||||
const ziel = Buffer.alloc(2 * 1024)
|
||||
const laenge = QueryDosDeviceW(`${buchstabe.replace(/:$/, '')}:`, ziel, 1024) as number
|
||||
if (laenge === 0) return ''
|
||||
// UTF-16LE, die erste Zeichenkette bis zum ersten NUL.
|
||||
const text = ziel.toString('utf16le', 0, laenge * 2)
|
||||
const ende = text.indexOf('\0')
|
||||
return (ende >= 0 ? text.slice(0, ende) : text).trim()
|
||||
},
|
||||
|
||||
steuern(handle: unknown, code: number, eingabe?: Buffer | null, ausgabeBytes = 0): Buffer {
|
||||
const aus = ausgabeBytes > 0 ? Buffer.alloc(ausgabeBytes) : null
|
||||
const zurueck = [0]
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
// 5.7.0 (Durchsicht 12.09.2026, F11): Die Laufwerks-Gesundheit zählt nur die
|
||||
// Ereignisse DIESES Laufwerks. Vorher nahm der XPath Provider cdrom UND
|
||||
// disk für ALLE Geräte — eine hustende Festplatte (\Device\Harddisk1)
|
||||
// erzeugte „VERDACHT: DIE HARDWARE" in der Kachel des Blu-ray-Laufwerks.
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { beurteilen, gehoertZuLaufwerk, type LaufwerkEreignis } from '../src/kern/laufwerk/ereignisse'
|
||||
|
||||
const e = (provider: string, id: number, geraet: string): LaufwerkEreignis => ({
|
||||
zeit: '2026-09-02T18:32:00.000Z',
|
||||
provider,
|
||||
id,
|
||||
geraet,
|
||||
})
|
||||
|
||||
const PLATTE = e('disk', 11, '\\Device\\Harddisk1\\DR1')
|
||||
const PLATTE_BLOCK = e('disk', 7, '\\Device\\Harddisk1\\DR1')
|
||||
const CDROM0 = e('cdrom', 7, '\\Device\\CdRom0')
|
||||
const CDROM1 = e('cdrom', 11, '\\Device\\CdRom1')
|
||||
|
||||
describe('gehoertZuLaufwerk', () => {
|
||||
it('mit Gerätename zählt nur dieses Gerät', () => {
|
||||
expect(gehoertZuLaufwerk(CDROM0, '\\Device\\CdRom0')).toBe(true)
|
||||
expect(gehoertZuLaufwerk(CDROM1, '\\Device\\CdRom0')).toBe(false)
|
||||
expect(gehoertZuLaufwerk(PLATTE, '\\Device\\CdRom0')).toBe(false)
|
||||
// Groß/Klein ist Windows egal.
|
||||
expect(gehoertZuLaufwerk(CDROM0, '\\device\\cdrom0')).toBe(true)
|
||||
})
|
||||
it('ohne Gerätename zählt nur der cdrom-Treiber — nie eine Festplatte', () => {
|
||||
expect(gehoertZuLaufwerk(CDROM0, '')).toBe(true)
|
||||
expect(gehoertZuLaufwerk(CDROM1, '')).toBe(true)
|
||||
expect(gehoertZuLaufwerk(PLATTE, '')).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('beurteilen je Gerät', () => {
|
||||
it('Controllerfehler der Festplatte beschuldigen nicht das Laufwerk', () => {
|
||||
const bericht = beurteilen([PLATTE, PLATTE_BLOCK, CDROM0], 24, undefined, '\\Device\\CdRom0')
|
||||
expect(bericht.urteil).toBe('disc')
|
||||
expect(bericht.controllerfehler).toBe(0)
|
||||
expect(bericht.fehlerhafteBloecke).toBe(1)
|
||||
expect(bericht.letzte).toHaveLength(1)
|
||||
})
|
||||
it('ein zweites optisches Laufwerk zählt nicht mit', () => {
|
||||
const bericht = beurteilen([CDROM1, CDROM0], 24, undefined, '\\Device\\CdRom0')
|
||||
expect(bericht.urteil).toBe('disc')
|
||||
expect(bericht.controllerfehler).toBe(0)
|
||||
})
|
||||
it('nur Festplatten-Ereignisse → für das Laufwerk ist es ruhig, mit Gerätename im Text', () => {
|
||||
const bericht = beurteilen([PLATTE, PLATTE_BLOCK], 24, undefined, '\\Device\\CdRom0')
|
||||
expect(bericht.urteil).toBe('ruhig')
|
||||
expect(bericht.text).toContain('\\Device\\CdRom0')
|
||||
})
|
||||
})
|
||||
@@ -119,6 +119,7 @@ function fakeApi(verhalten: FakeVerhalten): LaufwerkApi & { protokoll: string[]
|
||||
protokoll,
|
||||
laufwerksbuchstaben: () => verhalten.buchstaben ?? ['C', 'G'],
|
||||
laufwerkstyp: (b) => verhalten.typen?.[b] ?? (b === 'G' ? DRIVE_CDROM : 3),
|
||||
geraeteName: (b) => (b === 'G' ? 'GERAET-CDROM0' : ''),
|
||||
oeffnen(pfad, zugriff) {
|
||||
protokoll.push(`oeffnen ${pfad} zugriff=${zugriff}`)
|
||||
if (verhalten.oeffnenFehler !== undefined) throw new Win32Fehler('CreateFileW', verhalten.oeffnenFehler)
|
||||
|
||||
Reference in New Issue
Block a user