середу, 21 серпня 2013 р.

Аналіз CHKDSK.COM з PC-DOS 1.00

CHKDSK. Скільки в цьому слові! ;-) (Для тих, хто ще застав відповідну епоху). Скільки надій на неї покладали, скільки розчарувань, чи навпаки -- несподіваних порятунків. Програма перевірки та виправлення помилок на дисках.


В DOS все починалося з FAT, тому поки говоритимемо саме про нього. Що таке FAT? Файлова система, спосіб організації файлів на диску. Детальніше див. File Allocation Table на wiki  та FAT details на OSDev. А коротко --- все просто, структура диску наступна:
  • Нульовий сектор диску (фізичної дискети, або логічного диску на HDD) --- boot-сектор. Він, окрім завантажувача операційної системи, містить інформацію про файлову систему, так званий BPB  (BIOS Parameter Block).
  • Згідно даних у BPB, або, у старіших версіях, просто в першому секторі, можна знайти початок FAT --- File Allocation Table, таблиці, яка дала ім'я і самій файловій системі. Вона містить записи для кожного кластера --- чи він порожній,  зарезервованим ОС, пошкодженим, належить файлу. Якщо належить файлу, у записі зберігається номер наступного кластера в файлі, або код останнього кластера файлу. Розмір цього запису -- 12/16/32 біти і визначав, про яку FAT мова: FAT-12, FAT-16, FAT-32 (остання, насправді, використовує тільки 28 біт, але кожен запис 32-бітний). Кластер -- група апаратних секторів. Один сектор --- 512 байт (деякі дуже старі, епохи до IBM-PC, дискети, мали 128 байт, сучасні HDD потроху переходять на 4Кб сектори, однак все рівно емулюють 512-байтові, для зворотної сумісності). Кількість секторів теж можна взяти з BPB.
  • Після FAT (яка присутня, зазвичай, у двох копіях, але бувають варіації, про які має писати у BPB), йде каталог. Каталог складається із 32-байтових записів, які містять інформацію про ім'я файлу, його розмір, дату та час модифікації/створення, атрибути, і --- номер першого кластера з даними. (Довгі імена файлів, довші за 8.3, зберігаються хитріше, див. посилання вище.). Таким чином, щоб знайти дані файлу, слід взяти з його запису в каталозі перший кластер, з запису FAT, що відповідає цьому кластеру, взяти номер наступного (або переконатися, що він зразу й останній), і так, поки не зустрінемо останній. Що цікаво, структура цього запису якраз співпадає із форматом FCB, тому ранні версії DOS всю інформацію про файл зберігали прямо в FCB, не виділяючи якісь внутрішні структури даних.
  • Перший байт FAT --- media ID, використовувався ранніми системами для ідентифікації файлової системи. 
DOS 1.00 не мав BPB, "зате" мав чітке уявлення про структуру дискети --- бут-сектор, дві копії FAT, розміром 1 сектор кожна, після них --- каталог на 4 сектора, розмір кластера --- 1 сектор.

Однак, з поправкою на ці, природні тоді, очікування щодо дискети, її CHKDSK виявився  доволі просунутою програмою. Наприклад, він покладався на DOS, не роблячи власних припущень про розмір FAT, сектора чи їх кількість у кластері. Судячи з його розміру, я теж зразу подумав, що він всього лиш виводить вільну пам'ять, кількість файлів та місце на диску, але виявилося --- дааалеко не тільки. Як не смішно, так думав багато хто, про що свідчить стаття 1985 року, "CHKDSK, a Command That's Misunderstood".

Офіційна документація, DOS 1.00 -- First Edition Revised  (January  1982), сторінки 3-19 -- 3-20, стверджує:

Крім того, в додатку пише про наступні можливі повідомлення:
  • Allocation error for file filename -- CHKDSK.  An invalid  sector number was  found  in  the
    file  allocation table.  The file  was  truncated  at  the end  of the  last valid  sector. 
  • XXXXXXXXXX bytes disk space freed -- CHKDSK.  Diskette space marked as  allocated  was  not allocated.  Therefore, the space was  freed  and  made available.
  • Directory error-file:  filename -- CHKDSK.  No valid  sectors were allocated  to the  file.  The  filename  is removed  from  the directory. 
  • Diskette not initialized -- CHKDSK.  During its analysis  of the diskette, CHKDSK could  not recognize  the directory or file  allocation  table.  The  diskette  should be  formatted  again  before  further  use  (it may be  possible  first  to  copy files  to another  diskette in  order to preserve  as  much data as  possible). 
  • File size error for file  filename -- CHKDSK.  The  file  size  shown in  the directory is
    different from  the  actual size  allocated  for  the  file.  The  size  in  the directory is  adjusted, up  or down, to show  the  actual size  (rounded to  a  512-'byte boundary).
  • Files cross-linked:  filename  and filename --- CHKDSK.  The  same  data blockis allocated to both files. No  corrective action is  taken automatically, so  you must correct the  problem.  For example, you can: Make  copies of both files  (use COpy command). Delete the  original  files  (use  ERASE command).   Review  the  files  for validity  and edit as  necessary. 
  • Invalid  parameter  --- CHKDSK, DISKCOMP,  DISKCOPY,  FORMAT, and SYS.  The parameter entered  for these  commands was  not a drive  specifier.  Be  sure  to enter a valid  drive  specifier, followed  by  a colon.

Подивимося на його організацію детальніше.

;  This file is generated by The Interactive Disassembler (IDA)     
;  Copyright (c) 2010 by Hex-Rays SA, <support@hex-rays.com>     
;     Licensed to: Freeware version       
;
; Modified to compile by fasm and commented by Indrekis, indrekis2.blogspot.com
;
; Input MD5   : 9813BAE4E76B904552F58A3A826A0EF1

; File Name   : PC-DOS 1.00\CHKDSK.COM
; Format      : MS-DOS COM-file
; Base Address: 0h Range: 100h-673h Loaded length: 573h

FieldSize_forFoundFCBs = 2Ch
OldInt24Hadnler  = 673h
FoundFilesNumber  = 677h
FoundFCBsAreaAddr  = 679h
TotalClustersInFiles  = 67Bh
TotalClustersPlus1  = 67Dh
FoundDamadgedFiles  = 67Fh
InitialDefDriver  = 681h
stackTop  = 782h

  ;.8086
  use16
  org 100h

  include "my_fcb_2b.inc"

start: 
  mov sp, stackTop ; Shrink stack, move it closer to our code, to
     ; use memory above for data.
  or al, al  ; COM-format executables begin running with the following register values:
     ;   AL = 00h if first FCB has valid drive letter, FFh if not
     ;   AH = 00h if second FCB has valid drive letter, FFh if not
     ; CS,DS,ES,SS = PSP segment SP = offset of last word available in first 64K segment
     ; ( http://www.ctyme.com/intr/rb-2939.htm )
  jnz short InvalidDriveSpec
; Here we know that user specified correct (existing at least) disk number
  cmp byte [ds:5Dh], 20h ; PSP: 5Ch-6Bh  16 bytes -- Unopened Standard FCB 1
     ; So here will be any symbol, entered after [a-z][:][\]
  jnz short AnyArgIsInvalid ; User should give only disk, no filenames
  mov ah, 19h
  int 21h  ; DOS - GET DEFAULT DISK NUMBER
  mov [ds:InitialDefDriver], al ; Save current default driver.
     ; Memory somewhere above or code
  mov dl, [ds:5Ch] ; Disk from PSP FCB1
  or dl, dl  ; Check if already current disk
  jz short DiskAlreadyCur
  mov ah, 0Eh
  dec dl  ; disk numbering in FCB differs by one from others:
     ;   0 means default, 1 - A, and so on
     ;   adjusting for INT 21h/AH=0Eh call
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  cmp al, dl  ; Check if successfully changed current drive
  ja short WorkDriveIsCurrent
; If disk do not exists at all or cannot make it current:

InvalidDriveSpec:   
  mov dx, aInvalidDriveSp ; "Invalid drive specification$"
; Universal "print message and exit" code

PrnMsgNExit:    
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  int 20h  ; DOS - PROGRAM TERMINATION
     ; returns to DOS--identical to INT 21/AH=00h
; ===========================================================================

AnyArgIsInvalid:   
  mov dx, aInvalidParamet ; "Invalid parameter$"
  jmp short PrnMsgNExit
; ===========================================================================

WrongMediaByte:    
  mov ax, cs
  mov ds, ax
  mov ah, 0Eh
  mov dl, [ds:InitialDefDriver]
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  mov dx, aDisketteNotIni ; "Diskette not initialized $"
  jmp short PrnMsgNExit
; ===========================================================================

DiskAlreadyCur:    
  mov dl, al
; Now disk is current. Start checking.

WorkDriveIsCurrent:   
  mov [CurDriveNumber], dl
  inc dl
  mov [extFCB_1.Driver], dl ; Converted to FCB-style
  dec dl
  call GetCurDrvInfo ; Gets cur. drive info, sets as default the disk,
     ; which was default when CHKDSK started.
     ; On return AL -- sectors per cluster, DX -- total
     ; clusters number, CX -- bytes per sector,
     ; [DS:BX] --- media ID byte. Spoils DS!
  cmp byte [bx], 0FEh ; Check the media byte, only known by DOS 1.00
     ;  0xFEh: 5.25-inch Single sided, 40 tracks per side, 8 sectors per track (160 KB)
  jnz short WrongMediaByte ; Error message would be "diskette not initialized"...
  push ds  ; Save DS, pointing to media byte
  push es  ; Restore DS to out segment
  pop ds
  mov [TotalClusters], dx ; total number of clusters
  mov [BytesPerSector], cx ; bytes per sector
  mov ah, 0  ; AL -- sectors per cluster
  mov [SectorsPerCluster], ax
  mov cx, dx
  mov di, stackTop
  xor ax, ax
  rep stosw  ; Zeroing area, above of stack, equal in size to
     ;   clusters*2 -- in fact, two bytes per cluster
  mov cx, ax  ; cx=0
  mov [ds:TotalClustersInFiles], ax
  mov [ds:FoundFilesNumber], ax
  mov [ds:FoundDamadgedFiles], ax
  mov bp, di  ; BP poins to byte after the cleared area
  mov [ds:FoundFCBsAreaAddr], bp
  mov dx, di  ; Set DTA above cleared area too
  mov ah, 1Ah
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
  mov ah, 11h
  mov dx, extFCB_1
  int 21h  ; DOS - SEARCH FIRST USING FCB
     ; DS:DX -> FCB
  inc al  ; Returns 0 or FF --> +1 will give 1 or 0, 0 means error
  pop es  ; ES -- segment of media-byte (and, in fact, FAT copy in memory)
  jnz short FileFound
  jmp Finished_with_files
; ===========================================================================

FileFound:    
  inc cx  ; count found file
  add di, FieldSize_forFoundFCBs ; Next file DTA -- 2Ch bytes above previous
     ;  (which, in turn, is above stack top)
  mov dx, di
  mov ah, 1Ah
  int 21h  ; DOS - SET DISK TRANSFER AREA ADDRESS
     ; DS:DX -> disk transfer buffer
  mov ah, 12h
  mov dx, extFCB_1
  int 21h  ; DOS - SEARCH NEXT USING FCB
     ; DS:DX -> FCB
     ; Return: AL = status
  inc al  ; Returns 0 or FF --> +1 will give 1 or 0, 0 means error
  jnz short FileFound
  mov [ds:FoundFilesNumber], cx ; cx contains found files number
  mov cx, 1
  mov dx, [TotalClusters]
  inc dx
  mov [ds:TotalClustersPlus1], dx
  add bp, 22h  ; According to http://www.ctyme.com/intr/rb-2574.htm#Table1347
     ; for DOS 1.0 (in non-extended FCB)
     ; 16h  WORD  location in directory (if high byte = FFh, low byte is device ID)
     ; 18h  WORD  number of first cluster in file
     ; 1Ah  WORD  current absolute cluster number on disk
     ; 1Ch  WORD  current relative cluster number within file
     ; (0 = first cluster of file, 1 = second cluster, etc.)
     ; 1Eh  BYTE  dirty flag (00h = not dirty)
     ; 1Fh  BYTE  unused
     ;
     ; 22h-7 = 1Bh --> looks strange...
     ;
     ; Anyway, both check belove (if that value, offset 22h in
     ; extended FCB, is less than max. clusters number), and
     ; article http://www.borrett.id.au/computing/art-1985-11-01.htm
     ; which states, that error:
     ; "Directory error-file: filename."
     ;  Indicates that a directory entry was discovered
     ;  for a nonexistent file. In other words, there
     ;  were no allocation units assigned to this file.
     ;  CHKDSK removes the phantom file from the directory.
     ; hint, that it is check for the first cluster of file.
; In DOS 1.1 FindFirst/FindNext just copies content of catalog entry
; to FCB, after the disk code. (See DOS 1.1 disasembly by Don Jindra)
; And in catalog we have 2-byte field at offset 1Ah with first cluster of file
; 1Ah + 1h (driver code) + 7h (extended FCB) = 22h
;
; So, it looks like Ralf Brown's Interrupt list
; has an error in FCB format description ( http://www.ctyme.com/intr/rb-2574.htm ),
; when states different layout for DOS 1.00.

AnalNextFile:    
  xor ax, ax
  mov si, [bp+0] ; First cluster in file
  cmp si, [ds:TotalClustersPlus1]
  mov di, aDirectoryError ; WTF?
  ja short TooLargeFirstClusterNumber ; Jump short if above (CF=0 and ZF=0)
  or si, si
  jz short ScanClusters ; If SI=0, will go to "jmp isLastCLuster"
; In following loop
; SI -- analized in current iteration cluster
; CX -- file number (in found files list)

SeeNextCluster:    
  mov di, cx  ; cx -- number of file, we are working on (from the table FoundFCBsArea)
  shl si, 1  ; si=2*si
     ; Before it SI contains current (analyzed) cluster number
  xchg di, [si+77Eh] ; Stack top is: stackTop = 782h
     ; Lowest file cluster number is 2, so si*2>=4,
     ; 77Eh+4=782h
     ; So we are referencing memory above stack top,
     ; which is used as FAT map
  shr si, 1  ; si=si/2
  or di, di
  jnz short FilesAreCrosslinked ; Before we encountered it, cluster should be free.
  inc ax  ; AX contains number of analized clusters in file?
  call GetNextFileCluster ; Find next file cluster (FAT copy -- ES:BX)
     ; Input: SI -- current cluster number
     ; Output: DI -- next cluster number
     ;
  jz short ClusterNumberWrong
  cmp di, 0FF8h ; 0xFF8-0xFFF marker of last cluster in file for latter DOS
     ;

ScanClusters:    
  jnb short isLastCluster ; "Jump short if not below (CF=0)"
  cmp di, [ds:TotalClustersPlus1]
  ja short ClusterNumberWrong
  mov si, di  ; Current cluster number to SI
  jmp short SeeNextCluster
; ===========================================================================

TooLargeFirstClusterNumber:  
  push ax  ; ax should be 0?
  mov [bp+2], ax ; BP points to FCB+22h, presumably, first file cluster
  mov [bp+4], ax ; According to http://www.ctyme.com/intr/rb-2574.htm
     ; For DOS 1.1:
     ; 1Bh  WORD  current absolute cluster number on disk
     ; 1Dh  WORD  current relative cluster number within file
     ; Here we have DOS 1.0, for which that link has different structure.
     ; But, as also for first cluster, looks resonable, that table is wrong...
     ;
     ;
  mov ax, cx  ; CX - found file number in FCB table?
  mov dx, FieldSize_forFoundFCBs
  dec ax
  mul dx  ; DX:AX := AX * r/m16 (DX)
  mov dx, ax  ; Result expected to be less than 64K
  add dx, [ds:FoundFCBsAreaAddr] ; DX -- pointer to FCB of bad file to delete.
  mov ah, 13h
  int 21h  ; DOS - DELETE FILE via FCB
     ; DS:DX -> FCB with filename field filled with
     ; template for deletion ('?' wildcard allowed, but not '*')
     ; Return: AL = 00h file found, FFh file not found
  push ds
  push cx
  push ax
; Do DOS 1.0 change current disk on delete, so we need
; to set it again?
  call GetCurDrvInfo ; Gets cur. drive info, sets as default the disk,
     ; which was default when CHKDSK started.
     ; On return AL -- sectors per cluster, DX -- total
     ; clusters number, CX -- bytes per sector,
     ; [DS:BX] --- media ID byte. Spoils DS!
  pop ax
  pop cx
  pop ds
  or al, al  ; Cheking error from FCB delete operation
  jz short FileWithBadClusterNumberDeleted
  jmp WrongMediaByte ; If file deletion failed, print "diskette not initialized"...
     ; Strange error for that case :-)
; ===========================================================================

FileWithBadClusterNumberDeleted: 
  inc word [ds:FoundDamadgedFiles]
  mov dx, aDirectoryError ; "Directory error-file : $"
  jmp short PrintErrorWithFilename
; ===========================================================================

FilesAreCrosslinked:   
  mov dx, aFilesCrossLink ; "Files cross-linked: \r\n    $"
  push ax
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  mov ax, di
  call PrintFileName ; ; Prints file name and extension for entry number CX from
     ; ; FoundFCBsArea. AX -- number of file FCB in that area.
  mov dx, aAnd ; "  and  $"
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  mov ax, cx
  call PrintFileName ; ; Prints file name and extension for entry number CX from
     ; ; FoundFCBsArea. AX -- number of file FCB in that area.
  mov dx, aCRLF
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  pop ax
  add [ds:TotalClustersInFiles], ax
  jmp DoNextFile
; ===========================================================================

ClusterNumberWrong:   
  mov dx, 0FFFh ; Set current cluster as last
  call SetFATEntry ; Set cluster entry in FAT (image in memory) to low 12 bits of DX
     ; DX -- new FAT entry value
     ; SI -- cluster we are working on
  mov dx, aAllocationErro ; "Allocation error for file $"
  push ax

PrintErrorWithFilename:   
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  mov ax, cx
  call PrintFileName ; ; Prints file name and extension for entry number CX from
     ; ; FoundFCBsArea. AX -- number of file FCB in that area.
  mov dx, aCRLF
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  pop ax  ; If came here from FileWithBadClusterNumberDeleted,
     ; AX = 0

isLastCluster:    
  add [ds:TotalClustersInFiles], ax
  mov si, ax
  mov ax, [bp+2] ; BP=FCB+22h, see comments above
     ; Because FCB+7+1 -- copy of dir entry
     ; BP+2/BP+4=FCB+24h=FCB+7h+1h+1Ch -- filesize.
  mov dx, [bp+4]
  push cx  ; DX:AX -- file size from catalog
  mov cx, [SectorsPerCluster]
  dec cx
  add ax, cx
  adc dx, 0
  inc cx

ContDiv01:    
  shr cx, 1  ; CX=CX/2
     ; low-order bit of r/m;
  jb short FinishedDiv01
  shr dx, 1
  rcr ax, 1  ; Effectively div DX:AX by sectors per cluster
  jmp short ContDiv01
; ===========================================================================

FinishedDiv01:    
  div [BytesPerSector] ; DX:AX div by Arg
     ; AX -- Quotient
     ; DX -- Remainder
  or dx, dx
  jz short FileSizeISMultipleOfCluster
  inc ax  ; Rounding to next cluster

FileSizeISMultipleOfCluster:  
  cmp ax, si  ; AX -- clusters in file from catalog
     ; SI -- clusters in file from FAT scanning
     ;
  jz short RestoreCXAndDoNextFile ; If not Z, have size mismatch
; Correcting file size to size, obtained from FAT scanning
  xor di, di
  mov cx, [SectorsPerCluster]
  call MulDI_SI_by_CX
  mov [bp+6], si ; BP points to extFCB+22, so bp+6 and bp+8 --
     ; 4-byte random access record number.
  mov [bp+8], di
  mov dx, bp
  sub dx, 22h  ; BP was FCB+22h :-)
  mov ah, 0Fh
  int 21h  ; DOS - OPEN DISK FILE
     ; DS:DX -> FCB
     ; Return: AL = 00h file found, FFh file not found
  or al, al
  jz short TryEmptyWriteToFile
  jmp WrongMediaByte ; If failed to open file
; ===========================================================================

TryEmptyWriteToFile:   
  mov ax, [BytesPerSector]
  mov [bp-0Dh], ax ; BP points to extFCB+22, so BP-0Dh=0Eh+7h =
     ;   = logical record size
  mov cx, 0  ; Empty write
  mov ah, 28h
  int 21h  ; DOS - RANDOM BLOCK WRITE
     ; DS:DX -> FCB
     ; CX = number of records to be written
     ; if zero, truncate file to current random file position
  mov ah, 10h
  int 21h  ; DOS - CLOSE DISK FILE
     ; DS:DX -> FCB
     ; Return: AL = 00h directory update successful
     ; FFh file not found in directory
  push ds
  call GetCurDrvInfo ; Gets cur. drive info, sets as default the disk,
     ; which was default when CHKDSK started.
     ; On return AL -- sectors per cluster, DX -- total
     ; clusters number, CX -- bytes per sector,
     ; [DS:BX] --- media ID byte. Spoils DS!
  pop ds
  mov dx, aFileSizeErrorF ; "File size error for file $"
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  pop ax
  push ax
  call PrintFileName ; ; Prints file name and extension for entry number CX from
     ; ; FoundFCBsArea. AX -- number of file FCB in that area.
  mov dx, aCRLF
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"

RestoreCXAndDoNextFile:   
  pop cx  ; Restore file number in CX

DoNextFile:    
  add bp, 2Ch  ; Next FCB in FoundFCBsArea
  inc cx  ; Next file
  cmp cx, [ds:FoundFilesNumber]
  ja short Finished_with_files ; Was it last file?
  jmp AnalNextFile
; ===========================================================================

Finished_with_files:   
  xor ax, ax
  mov bp, stackTop ; table for FAT
  mov si, 2  ; First data cluster
  mov cx, [TotalClusters]

OverAllClusters:   
  test word [bp+0], 0FFFFh ; Test if at least single bit is set
     ; The field is equal to file number for
     ; which this cluster belongs.
  jnz short GoToNextCluster
; Test if cluster, not referenced by files, points to other cluster.
; If it does -- it is the lost cluster, mark as empty.
  call GetNextFileCluster ; Find next file cluster (FAT copy -- ES:BX)
     ; Input: SI -- current cluster number
     ; Output: DI -- next cluster number
     ;
  jz short GoToNextCluster
  xor dx, dx  ; Mark cluster as free
  call SetFATEntry ; Set cluster entry in FAT (image in memory) to low 12 bits of DX
     ; DX -- new FAT entry value
     ; SI -- cluster we are working on
  inc ax  ; Freed cluster number

GoToNextCluster:   
  inc bp
  inc bp  ; Next record in our FAT table
  inc si  ; Next cluster
  loop OverAllClusters
  or ax, ax
  jz short NoLostClusters
  mov bx, aBytesDiskSpace ; " bytes disk space freed\r\n\r\n$"
  call PrintClustersAsBytes ; AX -- clusters

NoLostClusters:    
  mov dl, [cs:InitialDefDriver]
  mov ah, 0Eh
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  mov si, [ds:FoundFilesNumber]
  sub si, [ds:FoundDamadgedFiles]
  xor di, di
  mov bx, aDiskFiles ; " disk files\r\n$"
  call PrintNumbers ; Prints DI:SI in decimal form, then string, pointed by BX
  mov ax, [TotalClusters]
  mov bx, aBytesTotalDisk ; " bytes total disk space\r\n$"
  call PrintClustersAsBytes ; AX -- clusters
  mov ax, [TotalClusters]
  sub ax, [ds:TotalClustersInFiles]
  mov bx, aBytesRemainAva ; " bytes remain available\r\n\r\n$"
  call PrintClustersAsBytes ; AX -- clusters
  mov ax, [ds:2] ; PSP: Memory size in paragraphs
  mov dx, 10h
  mul dx  ; Mul AX by 10h to obtain memory in bytes
  mov si, ax
  mov di, dx
  mov bx, aBytesTotalMemo ; " bytes total memory\r\n$"
  call PrintNumbers ; Prints DI:SI in decimal form, then string, pointed by BX
  mov ax, [ds:2] ; PSP: Memory size in paragraphs
  mov dx, cs  ; Current code segment is at the beginning of free memory,
     ; when chkdsk is not running.
  sub ax, dx
  mov dx, 10h
  mul dx  ; Mul AX by 10h to obtain memory in bytes
  mov si, ax
  mov di, dx
  mov bx, aBytesFree ; " bytes free"
  call PrintNumbers ; Prints DI:SI in decimal form, then string, pointed by BX
  call SafeResetDisk
  mov dl, [cs:InitialDefDriver]
  mov ah, 0Eh  ; Return to initial default driver
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  int 20h  ; DOS - PROGRAM TERMINATION
;start  endp   ; returns to DOS--identical to INT 21/AH=00h


; ============== subroutine =================================================


SafeResetDisk:
  mov ax, ds
  push ds
  mov es, ax
  xor ax, ax
  mov ds, ax  ; DS -- 0
  mov si, 90h  ; 90h/4=24h -- 24h interrupt vector
     ;  CRITICAL ERROR HANDLER
  mov di, OldInt24Hadnler
  mov cx, 2
  cld
  rep movsw
  mov al, 24h
  mov ah, 25h
  mov dx, int24Hadnler
  pop ds
  int 21h  ; DOS - SET INTERRUPT VECTOR
     ; AL = interrupt number
     ; DS:DX = new vector to be used for specified interrupt
  mov ah, 0Dh
  int 21h  ; DOS - DISK RESET
  push es
  xor ax, ax
  mov es, ax
  cld
  mov di, 90h
  mov si, OldInt24Hadnler
  mov cx, 2
  rep movsw
  pop es
  retn
;SafeResetDisk endp

; ===========================================================================
; http://www.ctyme.com/intr/rb-4112.htm
; 
; Values critical error handler is called with:.
; AH = type and processing flags (see #02544).
; AL = drive number if AH bit 7 clear.
; BP:SI -> device driver header (see #01646 at INT 21/AH=52h)
; (BP:[SI+4] bit 15 set if character device).
; DI low byte contains error code if AH bit 7 set (see #02545).
; 
; Bitfields for critical error type and processing flags:
; 
; Bit(s) Description (Table 02544)
; 7      class.
; =0  disk I/O error.
; =1  -- if block device, bad FAT image in memory
; -- if char device, error code in DI
; 6      unused
; 5      Ignore allowed (DOS 3.0+)
; 4      Retry allowed (DOS 3.0+)
; 3      Fail allowed (DOS 3.0+)
; 2-1    disk area of error.
; 00 = DOS area 01 = FAT.
; 10 = root dir 11 = data area
; 0      set if write, clear if read

int24Hadnler:    
  sti
  and di, 0FFh ; If have err code in low part of DI
  jnz short CallDefInt24Handler
  cmp ah, 11b  ; If bit0 - write, and bit1 (low of area of error) is 1
     ;  (FAT/data area)
  jnz short CallDefInt24Handler
  cmp [cs:CurDriveNumber], al ; If for current drive
  jnz short CallDefInt24Handler
  cmp [cs:HaveChangedFAT], 1 ; If haven't changed FAT
  jz short CallDefInt24Handler
  xor al, al  ; Return: "ignore error and continue processing request"
  iret
; ===========================================================================

CallDefInt24Handler:   
  db 2Eh  ; CS segment override prefix
  mov si, ax  ;
  push dx
  mov dl, [ds:InitialDefDriver]
  mov ah, 0Eh
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  pop dx
  mov ax, si
  jmp dword [cs:OldInt24Hadnler]

; ============== subroutine =================================================

; Gets cur. drive info, sets as default the disk,
; which was default when CHKDSK started.
; On return AL -- sectors per cluster, DX -- total
; clusters number, CX -- bytes per sector,
; [DS:BX] --- media ID byte. Spoils DS!

GetCurDrvInfo:
  mov dl, [cs:CurDriveNumber]
  mov ah, 0Eh  ; Not sure, if it is neccessery to init it once more...
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  mov ah, 1Bh
  int 21h  ; DOS - GET ALLOCATION TABLE INFORMATION FOR DEFAULT DRIVE
     ; (http://www.ctyme.com/intr/rb-2590.htm)
     ; AL = sectors per cluster (allocation unit)
     ; CX = bytes per sector
     ; DX = total number of clusters
     ; DS:BX -> media ID byte
  push dx
  push ax
  mov dl, [cs:InitialDefDriver]
  mov ah, 0Eh
  int 21h  ; DOS - SELECT DISK
     ; DL = new default drive number (0 = A, 1 = B, etc.)
     ; Return: AL = number of logical drives
  pop ax
  pop dx
  retn
;GetCurDrvInfo endp


; ============== subroutine =================================================

; Find next file cluster (FAT copy -- ES:BX)
; Input: SI -- current cluster number
; Output: DI -- next cluster number
;

GetNextFileCluster:
  mov di, si
  shr di, 1
  add di, si  ; Now DI contains word number, which contains next cluster number
     ; (relatively to FAT beginning).
     ; If SI is even [BX+DI] will contain 12 bits in higher part of word
  mov di, [es:bx+di]
  test si, 1  ; Test if SI is odd
  jz short BitsAreInPlace
  shr di, 1  ; DI contains 12 bit of next cluster in higher bits.
     ; Shift them low
  shr di, 1
  shr di, 1
  shr di, 1

BitsAreInPlace:    
  and di, 0FFFh ; mask to use 12 bits only
  retn
;GetNextFileCluster endp


; ============== subroutine =================================================

; Set cluster entry in FAT (image in memory) to low 12 bits of DX
; DX -- new FAT entry value
; SI -- cluster we are working on

SetFATEntry:
  mov [HaveChangedFAT], 1
  push si
  mov di, si
  shr si, 1
  add si, bx
  add si, di
  shr di, 1  ; Bit0 to CF
  mov di, [es:si] ; SI points to word with 12 bit of cluster number,
     ; se also previous function -- alog. is the same.
  jnb short WasEven02 ; Jump short if not below (CF=0)
     ; Means if DI (SI on func start) is odd
  shl dx, 1  ; Move DX bits to correct -- upper 12, position
  shl dx, 1
  shl dx, 1
  shl dx, 1
  and di, word 0Fh  ; Zero out upper 12 bits
  jmp short WasOdd02
; ===========================================================================

WasEven02:    
  and di, 0F000h ; Zero lower 12 bits
; DI is prepeared, so to not change 4 bits of word,
; that corresponds to other cluster, not the one under consideration

WasOdd02:    
  or di, dx
  mov [es:si], di
  pop si

locret_451:    
  retn
;SetFATEntry endp


; ============== subroutine =================================================


MulDI_SI_by_CX:
     ; MulDI_SI_by_CX+8 j ...
  shr cx, 1
  jb short locret_451
  shl si, 1
  rcl di, 1
  jmp short MulDI_SI_by_CX
;MulDI_SI_by_CX endp


; ============== subroutine =================================================

; AX -- clusters

PrintClustersAsBytes:
  mul [BytesPerSector]
  mov cx, [SectorsPerCluster]
  mov si, ax
  mov di, dx
; Falls to PrintNumbers
  call MulDI_SI_by_CX ; After the call DI:SI contains bytes for given clusters
;PrintClustersAsBytes endp


; ============== subroutine =================================================

; Prints DI:SI in decimal form, then string, pointed by BX

PrintNumbers:
  push bx
  xor ax, ax
  mov bx, ax
  mov bp, ax
  mov cx, 20h
; I'm too lazy for detailed analysis of this code...

loc_475:    
  shl si, 1
  rcl di, 1
  xchg ax, bp
  call sub_4C8
  xchg ax, bp
  xchg ax, bx
  call sub_4C8
  xchg ax, bx
  adc al, 0
  loop loc_475
  mov cx, 1B10h
  call PrintUpperDecDigitFromAH ; Print decimal digit in upper 4 bits of DL
     ; Received AX is returned in DX
  mov ax, bx
  call PrintUpperDecDigitFromAH ; Print decimal digit in upper 4 bits of DL
     ; Received AX is returned in DX
  mov ax, bp
  call PrintUpperDecDigitFromAH ; Print decimal digit in upper 4 bits of DL
     ; Received AX is returned in DX
  pop dx
  mov ah, 9
  int 21h  ; DOS - PRINT STRING
     ; DS:DX -> string terminated by "$"
  retn
;PrintNumbers endp


; ============== subroutine =================================================

; Print decimal digit in upper 4 bits of DL
; Received AX is returned in DX

PrintUpperDecDigitFromAH:

  push ax
  mov dl, ah
  call PrintUpperDecDigitFromDL ; Print decimal digit in upper 4 bits of DL
  pop dx
;PrintUpperDecDigitFromAH endp


; ============== subroutine =================================================

; Print decimal digit in upper 4 bits of DL

PrintUpperDecDigitFromDL:
  mov dh, dl
  shr dl, 1
  shr dl, 1
  shr dl, 1
  shr dl, 1
  call sub_4B3  ; Print digit in dl, if zero -- fill, according to CX.
     ; The first call receives CX=1B10, which leads to printing zero
     ; May be, other CX can be used for some padding?...
  mov dl, dh
;PrintUpperDecDigitFromDL endp


; ============== subroutine =================================================

; Print digit in dl, if zero -- fill, according to CX.
; The first call receives CX=1B10, which leads to printing zero
; May be, other CX can be used for some padding?...

sub_4B3:
  and dl, 0Fh
  jz short loc_4BA
  mov cl, 0

loc_4BA:    
  dec ch
  and cl, ch
  or dl, 30h
  sub dl, cl
  mov ah, 2
  int 21h  ; DOS - DISPLAY OUTPUT
     ; DL = character to send to standard output
  retn
;sub_4B3  endp


; ============== subroutine =================================================


sub_4C8:
  adc al, al
  daa
  xchg al, ah
  adc al, al
  daa
  xchg al, ah
  retn
;sub_4C8  endp


; ============== subroutine =================================================

; ; Prints file name and extension for entry number CX from
; ; FoundFCBsArea. AX -- number of file FCB in that area.

PrintFileName:
  push cx  ; cx -- scanned file entry number
  dec ax
  mov cx, 2Ch  ; Size of one file area in found FCB's
  mul cx
  mov si, ax
  add si, [ds:FoundFCBsAreaAddr] ; FCB field for file number from CX?
  add si, 8  ; FileName field in FCB
  mov cx, 8
  mov ah, 2
  call PrnNextChar ; Print file name
  mov dl, 20h
  int 21h  ; DOS -
  mov cl, 3
  call PrnNextChar ; Print file extension
  pop cx  ; Restore CX
  retn
;PrintFileName endp


; ============== subroutine =================================================

; At function start:
; AH=02 -- WRITE CHARACTER TO STANDARD OUTPUT
; CX -- chars to print

PrnNextChar:
  lodsb   ; al <= char from [si]
  mov dl, al
  int 21h  ; AH=02h, WRITE CHARACTER TO STANDARD OUTPUT
     ; DL - char to print
  loop PrnNextChar ; At function start:
     ; AH=02 -- WRITE CHARACTER TO STANDARD OUTPUT
     ; CX -- chars to print
  retn
;PrnNextChar endp

; ===========================================================================
extFCB_1 ExtFCB_t 0,"????????", "???", 0,6h,0
;extFCB_1 db 0FFh   
;  db 5 dup(0)  ; Reserved1
;  db 6   ; Attribute
;  db 0   ; Driver
;  db 8 dup('?')           ; FileName
;  db 3 dup('?')           ; FileExt
;  db 2 dup(0)  ; CurBlock
;  db 2 dup(0)  ; RecordSize
;  db 4 dup(0)  ; FileSize
;  db 2 dup(0)  ; FileDate
;  db 2 dup(0)  ; FileTime
;  db 8 dup(0)  ; Reserved2
;  db 0   ; CurRecord
;  db 4 dup(0)  ; DirectRecord

aAnd  db '  and  $'           
aInvalidDriveSp db 'Invalid drive specification$' 
aInvalidParamet db 'Invalid parameter$' 
aAllocationErro db 'Allocation error for file $' 
aDirectoryError db 'Directory error-file :    $' 
     
aFilesCrossLink db 'Files cross-linked: ',0Dh,0Ah 
  db '     $'
aFileSizeErrorF db 'File size error for file  $' 
aDisketteNotIni db 'Diskette not initialized $' 
aBytesDiskSpace db ' bytes disk space freed',0Dh,0Ah 
  db 0Dh,0Ah,'$'
aDiskFiles db ' disk files',0Dh,0Ah,'$' 
aBytesTotalDisk db ' bytes total disk space',0Dh,0Ah,'$' 
aBytesRemainAva db ' bytes remain available',0Dh,0Ah 
  db 0Dh,0Ah,'$'
aBytesTotalMemo db ' bytes total memory',0Dh,0Ah,'$' 
aBytesFree db ' bytes free'        
aCRLF  db  0Dh   
  db  0Ah
  db  24h ; $
TotalClusters dw 0   
BytesPerSector dw 0   
     
SectorsPerCluster dw 0   
HaveChangedFAT db 0   
CurDriveNumber db 0


 Розмір --- 1395 байт. Всього!

Скачати код, разом із лістингом, згенерованим IDA, скомпільованим файлом та, для порівняння, оригінальним CHKDSK.COM, можна тут. Скомпільований код, з точністю до опкодів-синонімів, тотожній оригінальному.


Алгоритм роботи

Як і інші ранні системні програми DOS, спочатку пересуває стек нижче, трішки вище власного коду, щоб вище нього розташувати свої дані. До менеджменту пам'яті система, фактично, ще не доросла.

Наступним кроком перевіряє, чи програмі передано коректний диск, якщо ні -- "Invalid drive specification" і вийти. Потім перевіряє, чи крім імені диску не передано ще щось. Якщо так --- "Invalid parameter" і вийти. Далі зберігає поточний диск, щоб відновити потім. (Як чесно зізнається офіційна документація, якщо програма перерветься передчасно, його не відновить.) Якщо він не є цільовим, робить цільовий диск поточним. Якщо не вдалося --- те ж повідомлення "Invalid drive specification".

За допомогою функції, умовно мною названої GetCurDrvInfo, цільовий диск ще раз вибирається (навіщо? хоча, ця функція потім використовується для переініціалізації і перечитування FAT після змін, може щоб "двічі не ходити"), читається інформація про нього, включаючи адресу байта media ID, кількість кластерів, кількість секторів у кластері, кількість байтів у секторі. Потім поточним знову робиться диск, який був поточним на момент запуску CHKDSK.

Зразу над новим стеком зануляється область розміром "(кількість кластерів)*2" --- по два байти на кластер. Змінні, що зберігають початкові значення загальної кількості кластерів у файлах (змінна, умовно, TotalClustersInFiles), кількість файлів (FoundFilesNumber), кількість пошкоджених файлів (FoundDamadgedFiles), встановлюються рівними нулю. Для кожного знайденого файла створюватиметься FCB, пам'ять для них розташовується зразу після області для опису секторів, описаної вище. Туди ж встановлюється DTA.

Наступним кроком, за допомогою FCB, збереженого у коді програми, шукаються будь-які файли. Якщо щось знайдено, переходиться до перевірки файлів, інакше зразу до всіх решта перевірок.

Якщо файл знайдено, виклик "FIND FIRST MATCHING FILE USING FCB", AH=11h/INT 21h, створює для нього в DTA не відкритий FCB того ж типу, що і вихідний FCB. Тоді, поки ще залишилися файли, DTA пересувається на розмір розширеного FCB , 2Ch байт, та здійснюється новий пошук, за допомогою "FIND NEXT MATCHING FILE USING FCB",  AH=12h/INT 21h. По завершенню цієї процедури, ми маємо в пам'яті FCB для всіх файлів на диску (благо, DOS 1.00 не очікував побачити їх більше, ніж 64 штуки за раз). Повна їх кількість зберігається у змінній, умовно названій FoundFilesNumber.

Адреса цієї області знаходиться в регістрі BP. Після завершення пошуку вона зсувається на 22h. Звідки саме це значення? Нагадаємо, що розширений FCB -- це 7 байт розширення + 1 байт коду диску + копія запису з каталогу FAT. Точніше, частина полів залишку FCB після коду диску співпадають із відповідними полями запису каталогу. Решта вважаються зарезервованими системою. Однак, якщо глянути в код DOS 1.00 (кинув оком самостійно) чи DOS 1.10 (дизасембльований Don Jindra), видно що "FIND FIRST" просто копіює туди знайдений запис з каталогу.   22h - 7h - 1h = 1Ah. А за таким зміщенням в записі знаходиться початковий сектор файлу. Такої ж думки наша жертва, CHKDSK (див. код і коментарі нижче). Опис формату недокументованих полів FCB для DOS 1.00 від Ральфа Брауна стверджує інше, і, очевидно, є помилковим. Таким чином, після цієї процедури BP вказує на запис із номером першого кластеру файлу, що розглядається. Файл аналізується, потім до BP додається 2Ch --- переходиться до FCB наступного знайденого файлу, збільшується кількість опрацьованих файлів, якщо вона менша від визначеної раніше повної, переходиться до наступного.

Розглянемо аналіз одного файлу детальніше. Спочатку перевіряється, чи не є номер першого кластера більшим, ніж кількість кластерів + 1. (Чому саме так? Адже, якщо номер рівний кількості кластерів+1 --- він помилковий, але програма цього не побачить... Чи я щось впускаю?).

Якщо більший, в його FCB зануляються поля першого кластера файлу, файл за допомогою цього FCB видаляється, (адресу згаданого FCB код хитро так знаходить, замість BP-22h використати...). Після цього знову викликається GetCurDrvInfo (для переініціалізації диску?), відновлюється зіпсутий нею DS, CX, AX, перевіряється, чи не було помилки під час видалення. Якщо була ---  виходить із дещо загадковим повідомленням "Diskette not initialized". Інакше збільшується кількість знайдених пошкоджених файлів, виводиться "Directory error-file :" з іменем пошкодженого файлу, (зверніть увагу на функцію PrintFileName -- така мила :),  провалюється до коду, котрий підраховує зайняті кластери (хитрим чином йому передається нуль -- це ж перший кластер файлу зразу був помилковим) та зайняте місце з FCB (теж нуль) і т. д. Цей код розглянемо детальніше на прикладі нормальних, не пошкоджених, файлів.

Якщо ж номер першого кластера не є більшим за дозволений, в таблицю кластерів, виділену над стеком, поміщаємо порядковий  номер файлу (в нашій табличці FCB), якому цей кластер належить, а те що там лежало, перевіряємо на рівність нулю. (Насправді, цьому довгому реченню відповідає одна команда,  xchg    di, [si+77Eh]). Якщо там не нуль --- кластер вже зайнятий. Тоді друкуємо "Files cross-linked:" та ім'я цього і попереднього файлів, які обидва посилаються на цей кластер. Після цього збільшується кількість зайнятих кластерів на ту кількість, яку було відскановано у файлі до того, як зустрівся "cross-link", та переходиться до наступного файлу. Якщо кластер був вільний, збільшуємо на 1 кількість зайнятих кластерів та переходимо до наступного кластеру файлу. (Для цього використовується підпрограма GetNextFileCluster, яка (справедливо) вважає, що FAT знаходиться за адресою ES:BX. Справді, в ранніх версіях DOS, після media ID, йшла копія FAT, потім цей недокументований бардак припинили). Якщо номер наступного кластера нуль --- повідомити "Allocation error for file" з іменем файлу, перед тим вказавши, що цей кластер останній (встановити його запис у FAT --- 0FFFh). Кількість зайнятих кластерів та зайнятого місця, відповідно, коректується. Кількість файлів із помилкою, здається, ні... Якщо номер наступного кластера не нуль, перевіряємо чи він не є більшим або рівним від 0FF8h. Якщо так --- це останній кластер файлу. Якщо не останній --- перевіряємо знову, чи він не завеликий, не більший за "максимальна кількість кластерів + 1". Якщо більший --- обробляємо відповідно, (див. вище опис для невірного першого кластера, тільки тепер додаємо до загального рахунку кластери файлу, оброблені до того, як зустрівся некоректний номер), якщо ж ні, аналізуємо наступний кластер.

Якщо успішно зустріли останній кластер, переходимо до своєрідного "підбиття підсумків". Воно вже згадувалося вище --- на нього ж потрапляємо, зустрівши файл із невірною кількістю кластерів. Додаємо кількість кластерів у файлі до повної кількості зайнятих, а їх розмір -- до розміру зайнятого простору. Далі, із FCB береться розмір файлу, записаний в каталог, переводиться у кількість зайнятих кластерів (відповідний код цікавий сам по собі -- як релікт деяких асемблерних стилів програмування). Якщо цей розмір співпадає із кількістю зайнятих кластерів, визначеною безпосередньо із обходу FAT, переходимо до наступного файлу. Якщо ж ні --- встановлюємо розмір, рівним кількості кластерів помноженій на розмір кластера. (В таких умовах точніше визначити розмір неможливо.) Зрозуміло, що, швидше за все, останній кластер міститиме трішки сміття, але цей розмір буде явно коректнішим, ніж той, що був у (пошкодженому) каталозі. Що цікаво, щоб зберегти цю зміну, адреса FCB цього разу береться оптимально, BP-22h, а не хитрується, як у видаленні некоректного файлу. Для збереження зміни розміру, новий розмір встановлюється в FCB, файл відкривається, записується в нього 0 байт, файл закривається, диск переініціалізовується викликом GetCurDrvInfo. Якщо відкрити файл не вдалося, повідомляється все те ж "Diskette not initialized" і виконання завершується. По завершенню коректування розміру, друкується "File size error for file" та імя файлу.

По завершенню аналізу файлів перевіряється, чи в FAT немає загублених кластерів --- таких, що не є порожніми, але на них не посилається жоден файл. Для цього використовується заповнена раніше табличка відповідності кластерів файлам. Якщо сектор належить файлу, пропускаємо. Якщо ні, дивимося що для нього записано у FAT. Якщо нуль --- не зайнятий, пропускаємо. Якщо не нуль --- він помічений зайнятим, але не належить жодному файлу. Помічаємо його як порожній, збільшуємо кількість звільнених кластерів, переходимо до наступного. Можливість відновлювати ланцюжки загублених кластерів явно з'явилася пізніше... Якщо звільнено щось, пишемо про це: " bytes disk space freed".

Що цікаво, FAT модифікується в пам'яті. Як він потрапляє на диск я так сходу не скажу (а лізти в "дизасембль" DOS --- лінь), можливо за це відповідають власне виклики "SELECT DEFAULT DRIVE", AH=0Eh/INT 21h, але швидше --- виклик "DISK RESET" (AH=0Dh/INT 21h) нижче.

Після завершення перевірок, поточним встановлюється той диск, що був таким на момент запису, друкується кількість файлів мінус кількість пошкоджених файлів, з підписом "disk files", потім кількість байт на диску (кількість кластерів, помножена на розмір кластера) і " bytes total disk space", кількість вільних байт та " bytes remain available", розмір пам'яті (береться з PSP!) + " bytes total memory", кількість вільних байт пам'яті, яка визначається як розмір з PSP мінус адреса початку CHKDSK в пам'яті та " bytes free".


Після цього диск "ресетиться" --- зберігається обробник помилок, INT 24h, замінюється на свій, про котрий нижче, робить "DISK RESET" (AH=0Dh/INT 21h), ймовірно записуючи при цьому модифікований FAT на диск, після чого відновлює обробник INT 24h.

"Наш" обробник INT 24h, "CRITICAL ERROR HANDLER", виконує хитрий аналіз (див. також відповідний запис в Interrupts Ральфа Брауна) --- якщо не встановлено код помилки в DI, якщо код помилки в AH=11b, якщо помилка стосується диску, який ми перевіряли, а FAT на ньому змінювався, то помилка ігнорується. Що природно --- сенсу реагувати на виправлені нами помилки, або результати цього виправлення, немає. Якщо ж помилка стосувалася чогось іншого --- викликаємо старий обробник INT 24h.

Виконавши все це, програма завершується. 

УВАГА!  Код відносно великий, дизасемблювання та написаний вище аналіз робилися шматками протягом дуже тривалого часу -- протягом 3-4 місяців (так як на такі дурні розваги його часто шкода :), тому вище можуть бути помилки, неточності, неуважності і т. д. Знайдете -- пишіть, буду вдячний!

"Документація"

 Як бачимо, офіційна документація трохи занадто лаконічна. CHKDSK перевіряє, для кожного знайденого файлу, ланцюжки у FAT на коректність та чи в каталозі справді є правильний розмір. Кластери, що не належать жодному файлу, перевіряються на зайнятість. Якщо знайдено такі, загублені, кластери, вони помічаються як вільні. Аналогічно, виправляються некоректності ланцюжків файлів --- якщо перший номер кластера невірний, файл видаляється, якщо ж невірні наступні, файл урізається до того шматка, який коректний. Лише якщо два файли вказують на один і той же кластер --- про це повідомляється, але автоматичного виправлення не робиться. Що можна зробити -- описано в процитованому вище додатку.

Якщо в процесі роботи, наприклад під час видалення пошкодженого файлу, відбувається якась помилка, повідомляється, що дискета неформатована, "Diskette not initialized" і виконання завершується. При тому, не зважаючи на дуже дивне повідомлення, офіційна інструкція дає розумну рекомендацію --- перш ніж переформатувати, спробувати вручну врятувати вцілілі файли.

Взагалі, офіційна інструкція документує всі можливі повідомлення і не описує жодного зайвого.

Команда не може приймати жодних аргументів, хоча, якщо такі буде передано після імені диску, через пробіл, просто їх проігнорує.

Про код

Ну, як і для інших проаналізованих програм DOS 1.00, код є жахливим спагеті. Місцями виглядає більш вилизаним, ніж код SYS.COM, але це може бути ілюзією... Місцями трапляються якісь дивні ускладнення -- навіщо так довго і плутано, якщо конкретно тут можна і простіше.  Є декілька цікавих трюків. Підсумовуючи, читати --- одне задоволення, підтримувати таке --- не дуже весело. Хоча, я повторююся. :-)


На разі:

 Дякую за увагу!

Немає коментарів:

Дописати коментар