Ch 34 硬碟 (3) 檔案離散


簡介

上一章提到邏輯磁碟的組成,但是沒有程式實作,顯得虛浮,這一章將利用上一章的內容撰寫一個檢查某個檔案或子目錄堛漫狾鹿仵蚻O否存放在連續的磁區小木偶稱此程式為 DFC.EXE ( Dose File Continue? 之意 )。

何謂『檔案離散』?

眾所周知,DOS 與 Windows 9x/Me 在把新檔案存入硬碟或軟碟時,是由硬碟或軟碟前面磁叢開始搜尋,只要找到沒有使用的磁叢就把資料寫到該磁叢,而不去理會這個沒有用的磁叢是否塞得下檔案,如果塞不下,系統又會再去尋找另一個沒有使用的磁叢,像這種檔案不是存放在連續磁叢的情形稱之為檔案離散 ( fragment )。如果硬碟經過長時間使用,經年累月的新增檔案與刪除檔案,這種現象就會更嚴重。一般的說法是,如果磁碟有許多檔案離散的話,存取檔案時,磁頭就會隨著離散的地方移動而降低效率,所以應該定期用『磁碟重組程式』把存放於散落磁叢的檔案搬移到連續的磁叢。

DFC.EXE

這一章小木偶實作一個能觀察檔案是否存於連續磁叢的程式,DFC.EXE。這個程式不能搬移磁碟上的資料而使之成為存於連續的磁叢,因為要加上這個功能,會使得程式太長,所以小木偶也懶得寫 ( 另外還有一個原因,很容易就把系統搞壞 ),小木偶只寫能觀察檔案資料及是否連續,如果不連續,那麼會顯示檔案被分成幾個連續的磁叢存放。

DFC.EXE 必須在 Win 9x/Me 或 DOS 作業系統下使用,其用法是:

dfc 磁碟機名:\路徑名

舉個例子來說,如果您想檢查 c:\masm32\example1\3dframes 子目錄堛瑰仵蚻O否連續,可在 DOS 模式下輸入『dfc c:\masm32\example1\3dframes』及 Enter 鍵後,dfc 執行畫面如下:

DFC.EXE 執行畫面

DFC.EXE 會自動把小寫變大寫,但是沒有做錯誤檢查,也不支援長檔名,所以路徑內的子目錄名稱得用 8.3 格式,而顯示的檔名也都是短檔名格式,上圖中的建檔日期是採用民國紀元,建檔時間是 24 時制,所有數值皆以十進位顯示,符合人類習慣,但是硬碟容量是 1MB = 1048576 位元組計算出來的。


原理

如何判斷檔案是否連續?

檢查檔案是否離散,只需要檢查存放這個檔案的磁叢編號是否連續。每個檔案的第零個磁叢編號存放在 FDB 堙A而後該檔案的第一個、第二個……到最末一個磁叢編號則存放在 FAT 相對應欄位堙C程式所要做的是檢查該檔案的第一個磁叢編號是否比 FDB 磁叢指示器多一,第二個磁叢編號是否比第一個磁叢編號多一,第三個磁叢編號是否比第二個磁叢編號多一……直到最末一個磁叢標號是否比倒數第二個磁叢標號多一為止,如果都是多一的話,那麼這個檔案就是連續的。有關如何取得編號 N 的 FAT 欄位可以參考前一章取得編號 N 的 FAT 欄位內容

程式流程

那麼,要怎麼才能取得第零個磁叢呢?第零個磁叢位於子目錄磁區的 FDB 內,因此小木偶的做法是由根目錄開始,依據使用者所輸入的路徑,依次尋找符合的子目錄,然後切換到這個子目錄,再搜尋這個子目錄符合的子目錄名稱或檔案,直到到達目標子目錄為止。若中間有不符合的子目錄名,表示使用者所輸入的路徑不存在,則於螢幕中印出『檔案或子目錄不存在』訊息。

例如上例,要檢查『c:\masm32\example1\3dframes』子目錄堛瑰仵蚻O否連續,程式先載入根目錄,在根目錄磁叢內眾多的 FDB 中搜尋『masm32』子目錄,如果未找著則印出『檔案或子目錄不存在』字串並結束程式。如果找著了,便依據其 FDB,載入 masm32 子目錄磁叢,然後在此磁叢尋找含有 example1 子目錄的 FDB……如此一直到找到 3dframes 目標子目錄為止。

在搜尋符合的子目錄名稱的過程中,如果 FDB 的第零個字元為 00,表示以後的 FDB 都是未使用,可停止搜尋,這也表示使用者輸入的子目錄不存在。但如果子目錄檔案很多時,其磁叢有可能含有兩個以上,所以如果搜尋完一個磁叢而沒找到符合的子目錄名稱,並不表示使用者輸入的子目錄不存在,而是這個子目錄還有另一個磁叢。這時,小木偶設一個變數,rd_flag,來記錄這種情形,rd_flag 的位元零為 1 時,表示此目錄還有下一磁叢。

讀入之磁區存放處

分析整個程式之後,會發現整個程式會讀取的部份是啟動磁區、根目錄磁區或子目錄磁區和 FAT 區域的磁區三種情形:

  1. 當程式一執行時,便應該要載入啟動磁區,以確定檔案系統、保留區域磁區數、FAT 區域的磁區數、……等邏輯磁碟的資料,其中還有一樣很重要的,就是計算根目錄在一個邏輯磁區以及根目錄大小。啟動磁區只有一個磁區,所佔據記憶體只 512 位元組,不會造成什麼影響。而且程式所需要的資料只是 BPB,共 53 位元組而已,之後就再也不須讀取啟動磁區了,所以存放啟動磁區的記憶體可以重複使用。

  2. 接下來是讀取根目錄。對 FAT32 而言,它把根目錄看成是特殊的子目錄,因此我們可以用處理子目錄的方式處理它。但是對 FAT12/16 這兩種檔案系統而言,根目錄所佔磁區不是一個磁叢大小,通常比較大,其計算方是在前一章曾提及。

    如果使用者所要尋找的路徑並非根目錄,那麼就必須一層一層的往下搜尋符合的子目錄,而只要找到符合的子目錄就可以讀取子目錄所在的磁叢,此時上一層子目錄的資料就再也用不到了,可以廢棄不用。

  3. 到了找著目標子目錄時,就要讀取在這目標子目錄堛漕C個檔案所擁有的 FAT 欄位,以決定存放這個檔案的磁叢是否連續,然後在螢幕上印出來。這時候必須保存目標子目錄的磁區內容,以便處理下一個檔案。有兩種方式可以保存目標子目錄的磁區內容,一是讀取 FAT 區域時避開存放子目錄磁叢的記憶體,另一種方式是保存子目錄 FAT 欄位,即使稍後讀取 FAT 區域時覆蓋了原來子目錄磁叢的記憶體,但在在螢幕上印出檔案是否連續之後,還可以取回子目錄的磁叢所在。在 DFC 程式堣p木偶採用前者。

綜合上面的敘述,小木偶把讀入的磁區存放在 FS 暫存器所指的區段堙A而 FS 是資料段、程式碼段、堆疊段三個區段中最高位址再加上 1000H。而 FS 區段分成兩部份,由 0000H∼buff_offset 這塊區域是供讀取啟動磁區與子目錄磁叢所存放之處,前面提到,啟動磁區僅剛開始時需要用到,後面就不用了,所以它們可重疊。至於 buff_offset 是多大呢?這得根據根目錄磁區與磁叢的大小決定。


DFC 原始碼

;Dose File Continue?檢查磁碟中某一目錄堛瑰仵蚻O否連續?
;用法:DFC 磁碟:\路徑名
        .model  small,stdcall
        .386

include         myasminc.inc

;***********************************************************
stack   segment stack
        dw      80h dup (?)
stack   ends
;***********************************************************
data    segment para    public  'data'  use16
total_sector    dd      ?       ;總磁區數
fat_region      dd      ?       ;兩份 FAT 總磁區數
root_region     dd      ?       ;根目錄磁區數
data_region     dd      ?       ;資料區域的磁區數
lst_data_sector dd      ?       ;資料區域的第一個邏輯磁區編號
n_cluster       dd      ?       ;子目錄所佔磁叢編號
byte_rt_reg     dw      ?       ;根目錄區域所佔位元組數
byte_for_read   dw      ?       ;讀入位元組數
byte_p_cluster  dw      ?       ;每一磁叢位元組數
buff_offset     dw      ?       ;FAT 存放於 FS:buff_offset 處
read_para       packet  <0,1,0,?>
drv_bpb         bpb     <?>     ;邏輯磁碟的 BPB
driver          db      ?       ;磁碟機名
fat_type        db      ?       ;FAT12 或 FAT16 或 FAT32
len_pathname    dw      ?
pathname        db      7eh dup (' ')           ;路徑名,如『c:\masm32\…』
dir_pointer     dw      offset pathname+2       ;指向每段子目錄字串位址
sub_dir_name    db      0bh dup (' ')
rd_flag         db      0       ;rd_flag 是一個旗標,只有 bit0 與 bit1 有用
                                ;bit0:目錄是否佔用一個以上磁叢,是:1,否:0
                                ;bit1:是否讀取根目錄,是:1,否:0
file_counter    db      ?       ;計算檔案數是否填滿一頁螢幕
dfc_str         db      cr,lf,13 dup (' '),'yy/mm/dd hh:mm:ss 共 ',46 dup (?)
msg00   db      cr,lf,'Dose File Continue? -- 檢查目錄堛瑰仵蚻O否連續?'
        db      cr,lf,cr,lf,'用法:DFC 磁碟機:\路徑名$'
msg01   db      cr,lf,'記憶體不足$'
msg02   db      cr,lf,'讀取錯誤$'
msg03   db      cr,lf,' : 檔案系統為 FAT12,共有 00000000000 磁區 ( '
        db      '相當於 00000000000 MB ),'
        db      cr,lf,'  其中保留區域佔 00000000000 磁區,'
        db      cr,lf,'    FAT 區域佔 00000000000 磁區,'
        db      cr,lf,'    根目錄佔 00000000000 磁區,'
        db      cr,lf,'    資料區域佔 00000000000 磁區。( 每磁區佔 '
        db      '00000 位元組 )$'
msg04   db      cr,lf,'按任一鍵繼續……$'
msg05   db      cr,lf,'不正確的路徑名$'
msg06   db      cr,lf,'檔案或子目錄不存在$'
msg07   db      ',所佔磁區連續$'
msg08   db      '被分成 00000 個部份$'
msg09   db      ',不佔磁區$'
data    ends
;***********************************************************
code    segment para    public  'code'  use16
        assume  cs:code,ds:data
;-----------------------------------------------------------
abs_sector_read proc    near
        mov     ax,7305h
        mov     cx,0ffffh
        mov     dl,driver
        sub     si,si
        mov     bx,offset read_para
        int     21h     ;絕對磁區讀取
        ret
abs_sector_read endp
;-----------------------------------------------------------
;由 bpb_drv 取得以下資料:
;輸出:fat_region、root_region、data_region、total_sector、
;      fat_type、lst_data_sector、byte_p_cluster、buff_offset
get_driver_info proc    near
        sub     edx,edx
        movzx   eax,drv_bpb.BPB_FATSz16
        or      ax,ax
        jnz     fat_ok
        mov     eax,drv_bpb.BPB_FATSz32
fat_ok: movzx   ecx,drv_bpb.BPB_NumFATs
        mul     ecx
        mov     fat_region,eax  ;EAX=所有 FAT 所佔磁區數
        mov     lst_data_sector,eax
        movzx   eax,drv_bpb.BPB_RootEntCnt
        movzx   ecx,drv_bpb.BPB_BytsPerSec
        shl     eax,5
        add     eax,ecx
        sub     edx,edx
        dec     eax
        div     ecx
        mov     root_region,eax ;EAX=根目錄所佔磁區數
        add     lst_data_sector,eax
        sub     dx,dx
        mov     dx,drv_bpb.BPB_TotSec16
        or      dx,dx
        jnz     total_sector_in_edx
        mov     edx,drv_bpb.BPB_TotSec32

total_sector_in_edx:
        mov     total_sector,edx;EDX=邏輯磁區總數
        sub     edx,eax
        sub     edx,fat_region
        movzx   eax,drv_bpb.BPB_RsvdSecCnt      ;EAX=保留磁區數
        add     lst_data_sector,eax
        sub     edx,eax
        mov     data_region,edx ;EDX=資料區域的磁區數
        sub     eax,eax
        xchg    eax,edx
        movzx   ecx,drv_bpb.BPB_SecPerClu
        div     ecx             ;EAX=磁叢總數
        mov     dl,32h
        cmp     eax,65525
        jae     got_it
        mov     dl,16h
        cmp     eax,4085
        jae     got_it
        mov     dl,12h          ;若為 FAT12,則 DL=12H;若為 FAT16,則
got_it: mov     fat_type,dl     ;DL=16H;若為 FAT32,則 DL=32H
        mov     ax,drv_bpb.BPB_BytsPerSec
        mul     cx
        mov     byte_p_cluster,ax       ;每磁叢位元組數
        mov     buff_offset,ax
        mov     eax,root_region
        movzx   ecx,drv_bpb.BPB_BytsPerSec
        mul     ecx
        mov     byte_rt_reg,ax  ;根目錄所佔位元組數
        cmp     buff_offset,ax  ;讀入的 FAT 存放在 FS:buff_offset 處,
        ja      buff_ok         ;buff_offset 是 byte_rt_reg、byte_p_cluster
        mov     buff_offset,ax  ;兩者較大者
buff_ok:
        ret
get_driver_info endp
;-----------------------------------------------------------
;由磁叢編號,計算該磁叢的第一個邏輯磁區編號
;輸入:EAX-磁叢編號
;輸出:EAX-邏輯磁區編號
cluster_to_sector       proc    near
        dec     eax
        sub     ecx,ecx
        dec     eax
        mov     cl,drv_bpb.BPB_SecPerClu
        mul     ecx
        add     eax,lst_data_sector
        ret
cluster_to_sector       endp
;-----------------------------------------------------------
;取得第 N 個磁叢編號之內容,此內容可能是下一磁叢編號,也可能是結束記號
;若為結束記號,則 EAX=0FFFFFFFFH
;要用這個副程式的時機有二,一是子目錄所佔磁叢不只一個,此時由磁碟讀取的
;資料存放於 FS:0000 處;二是檔案所佔磁叢不只一個,此時由磁碟讀取的資料
;存放於 FS:buffer_offset 處
;用法: invoke  get_next_cluster,緩衝區位址,N
;輸入:EAX-磁叢編號
;輸出:EAX-這個磁叢編號之內容
get_next_cluster        proc    near ,buffer_offset:word,cluster:dword
        mov     eax,cluster
;計算 cluster 離 FAT 區域有幾個位元組
.if fat_type==12h
        mov     ecx,eax
        shr     ecx,1
        add     eax,ecx
.elseif fat_type==16h
        shl     eax,1
.elseif fat_type==32h
        shl     eax,2
.endif
        sub     edx,edx
        movzx   ecx,drv_bpb.BPB_BytsPerSec
        div     ecx     ;除以 512,得到 cluster 在第幾個 FAT 磁區
        movzx   ecx,drv_bpb.BPB_RsvdSecCnt
        push    dx      ;餘數保存於堆疊
        add     eax,ecx ;EAX = cluster 在第幾個邏輯磁區
        mov     dx,buffer_offset        ;讀取之磁區放在 FS:buffer_offset
        mov     read_para.sector_sn,eax
        mov     read_para.n_sector,1
        mov     read_para.tran_offset,dx
        call    abs_sector_read
        pop     bx
        add     bx,buffer_offset
        movzx   eax,word ptr fs:[bx]
.if fat_type==12h
        test    cluster,1
        jz      even_n
        shr     eax,4
even_n: and     ax,0fffh
   .if ax>=0ff8h
        or      eax,0fffffffh   ;如果是結束記號,使其與 FAT32 結束記號相同
   .endif
.elseif fat_type==16h
   .if ax>=0ffffh
        or      eax,0fffffffh   ;如果是結束記號,使其與 FAT32 結束記號相同
   .endif
.elseif fat_type==32h
        mov     eax,dword ptr fs:[bx]
        and     eax,0fffffffh
   .if eax>=0ffffff8h
        or      eax,0fffffffh
   .endif
.endif
        ret
get_next_cluster        endp
;-----------------------------------------------------------
;使 EAX 之值,變成十進位的 ASCII 字元,存於 DI 所指位址
;用法: invoke  eax2dec_ascii,digit_len
;輸入:EAX-要轉換成十進位的十六進位數存放於 EAX 
;    :ES:DI-阿拉伯數字 ASCII 碼存放處
;    :digit_len-指定位數 ( 即格式 ),若不足,前面填入『0』
;                 ,若為 0FFH 表示由資料自行決定
;輸出:DI-指向十進位數字的下一位址
eax2dec_ascii   proc    near ,digit_len:byte
        local   temp_byte[10]:byte
        push    di
        mov     cx,10           ;最多有十個位數
        lea     di,temp_byte    ;先預存於 temp_byte 內
        movzx   esi,cx
e2a0:   sub     edx,edx
        div     esi
        add     dl,'0'          ;於 temp_byte 陣列中,由低位址向高位
        mov     ss:[di],dl      ;址分別是個、十、百……等位數
        or      eax,eax         ;若 ZR,表示已經算完,其餘高位數均為零
        jz      e2a1
        inc     di
        loop    e2a0
e2a1:   mov     si,di
        mov     ax,cx
        pop     di
        mov     cx,11
        sub     cx,ax           ;CX=共有幾位數
        cmp     digit_len,0ffh
        je      e2a2
        cmp     cl,digit_len    ;若 digit_len=0FFH 或數字本身位數大於
        jae     e2a2            ;digit_len,則存入所有數字
        push    cx              ;否則數字前必須存入『0』
        mov     ax,cx
        movzx   cx,digit_len
        sub     cx,ax
        mov     al,'0'
        rep     stosb
        pop     cx
e2a2:   mov     al,ss:[si]
        stosb                   ;存於 ES:DI 所指位址
        dec     si
        loop    e2a2
        ret
eax2dec_ascii   endp
;-----------------------------------------------------------
print_drv_info  proc    near
        mov     bl,fat_type
        mov     di,offset msg03+19
        mov     bh,bl
        shr     bl,4
        and     bh,0fh
        add     bx,3030h
        mov     [di],bx ;存入檔案系統
        mov     di,offset msg03+28
        mov     eax,total_sector
        invoke  eax2dec_ascii,0ffh      ;計算總磁區數
        mov     si,offset msg03+39
        mov     cx,15
        rep     movsb                   ;使後面的字串向前移
        mov     eax,total_sector
        sub     edx,edx
        movzx   ecx,drv_bpb.BPB_BytsPerSec
        mul     ecx
        mov     ecx,100000h
        div     ecx
        invoke  eax2dec_ascii,0ffh      ;計算邏輯磁碟容量
        mov     cx,27
        mov     si,offset msg03+65
        rep     movsb
        movzx   eax,drv_bpb.BPB_RsvdSecCnt
        invoke  eax2dec_ascii,0ffh      ;計算保留區域磁區數
        mov     cx,27
        mov     si,offset msg03+103
        rep     movsb
        mov     eax,fat_region
        invoke  eax2dec_ascii,0ffh      ;計算 FAT 區域磁區數
        mov     si,offset msg03+141
        mov     cx,25
        rep     movsb
        mov     eax,root_region
        invoke  eax2dec_ascii,0ffh      ;計算根目錄區域磁區數
        mov     si,offset msg03+177
        mov     cx,27
        rep     movsb
        mov     eax,data_region
        invoke  eax2dec_ascii,0ffh      ;計算資料區域磁區數
        mov     si,offset msg03+215
        mov     cx,9
        rep     movsw
        movzx   eax,drv_bpb.BPB_BytsPerSec
        invoke  eax2dec_ascii,0ffh
        mov     si,offset msg03+238
        mov     cx,5
        rep     movsw
        
        mov     dx,offset msg03
        mov     ah,9
        int     21h
        ret
print_drv_info  endp
;-----------------------------------------------------------
pause   proc    near
        mov     dx,offset msg04
        mov     ah,9
        int     21h
        mov     ah,0
        int     16h
        ret
pause   endp
;-----------------------------------------------------------
;取得目錄磁叢,並計算要讀取得邏輯磁區編號及磁區數,然後讀入記憶體內
;輸出:EAX-欲讀取的邏輯磁區編號
;      CX-欲讀取的磁區數
read_dir        proc    near
        and     rd_flag,0fdh    ;先假設不是讀取根目錄,而是其他子目錄
        mov     bx,dir_pointer
        mov     ax,byte_p_cluster
        cmp     bx,offset pathname+2
        mov     byte_for_read,ax
        jnz     gd1

;若 ZR,表示要讀取第零層目錄,即根目錄;否則為其他子目錄
        or      rd_flag,2       ;讀取根目錄
        cmp     fat_type,32h
        jne     gd0
        mov     eax,drv_bpb.BPB_RootClus
        jmp     short gd2
gd0:    mov     cx,byte_rt_reg
        mov     byte_for_read,cx
        movzx   eax,drv_bpb.BPB_RsvdSecCnt
        add     eax,fat_region
        mov     ecx,root_region
        jmp     short gd3

gd1:    mov     eax,n_cluster
gd2:    call    cluster_to_sector
        movzx   cx,drv_bpb.BPB_SecPerClu
gd3:    mov     read_para.sector_sn,eax
        mov     read_para.n_sector,cx
        mov     read_para.tran_offset,0
        call    abs_sector_read ;讀入目錄磁叢,並存於 FS:0000 處
        ret
read_dir        endp
;-----------------------------------------------------------
;取得子目錄名,使其轉變成與 FDB 格式相同的字串,存於 sub_dir_name 字串
;例如有個子目錄,MASM32,在 FDB 堿O『MASM32     』;若有另一子目錄,
;XYZ.A,在 FDB 堿O『XYZ     A  』
;輸入:DS:SI-使用者輸入之路徑
;輸出:DS:SI-此子目錄結束位址,即『\』或歸位字元所在位址
get_sub_dir_name        proc    near
        mov     di,offset sub_dir_name
        mov     bx,800h ;主檔名最多 8 個字元
gsdn0:  lodsb
        cmp     al,'.'
        je      gsdn2   ;若 ZR,表示子目錄有副檔名
        cmp     al,cr
        je      gsdn5
        cmp     al,'\'
        je      gsdn5
        cmp     al,' '
        je      gsdn5
        cmp     al,'a'  ;若使用者輸入小寫英文字母,則轉換成大寫
        jb      gsdn1
        cmp     al,'z'
        ja      gsdn1
        and     al,0dfh
gsdn1:  stosb
        inc     bl
        jmp     gsdn0

gsdn2:  cmp     bl,bh   ;若子目錄有副檔名
        ja      gsdn4
        movzx   cx,bh
        sub     cl,bl
        jz      gsdn3
        mov     al,' '  ;當主檔名少於 8 個字元或副檔名少於 3 個字元
        rep     stosb   ;時,剩餘字元均填入空白
gsdn3:  mov     bx,300h ;處理子目錄的副檔名,副檔名最多 3 個字元
        jmp     gsdn0

gsdn4:  stc             ;主檔名超過 8 字元或副檔名超過 3 字元,發生錯誤
        jmp     short gsdn9

gsdn5:  movzx   cx,bh
        mov     ax,2020h
        cmp     bh,3
        je      gsdn7
        sub     cl,bl   ;使用者僅輸入主檔名,若不足八字元,均填入空白
        jz      gsdn6
        rep     stosb
gsdn6:  stosw           ;且副檔名也填入空白
        stosb
        jmp     short gsdn8
gsdn7:  sub     cl,bl
        jz      gsdn8
        rep     stosb
gsdn8:  dec     si
        clc
gsdn9:  ret
get_sub_dir_name        endp
;-----------------------------------------------------------
;取得 FDB 所記錄的磁叢編號
;輸入:BX-FDB 位址
;輸出:EAX-磁叢編號
get_fdb_cluster proc    near
        sub     eax,eax
        cmp     fat_type,32h
        jne     @f
        mov     eax,fs:[bx+12h]
;若為 FAT32,必須取得較高的 16 位元,執行後,EAX 的較高 16 位元就是
;FDB 14H 所記錄的磁叢較高位
@@:     mov     ax,fs:[bx+1ah]
        ret
get_fdb_cluster endp
;-----------------------------------------------------------
print_dfc       proc    near uses bx
        local   n_break:word    ;檔案所佔據磁區被分成幾個部分
        local   f_cluster:dword ;檔案所佔據的磁叢編號

;把短檔名存入 dfc_str 字串中
        mov     di,offset dfc_str+2
        mov     si,bx
        mov     cx,800h
p_dfc1: mov     al,fs:[si]
        cmp     al,' '
        je      p_dfc3
        cmp     al,'$'
        jne     p_dfc2
        mov     al,'?'  ;若檔名中有『$』,改成『?』,因『$』為結束字元
p_dfc2: inc     si
        inc     cl
        stosb
        cmp     cl,ch
        jne     p_dfc1
p_dfc3: cmp     ch,3
        je      p_dfc4
        sub     ch,cl
        mov     al,'.'
        stosb
        movzx   ax,ch
        add     si,ax
        mov     dl,cl
        mov     cx,300h
        jmp     p_dfc1
p_dfc4: add     dl,cl   ;檔名不足 12 個字元填入空白
        mov     cx,12
        sub     cl,dl
        jz      p_dfc5
        mov     al,' '
        rep     stosb

;檔案日期及時間
p_dfc5: movzx   eax,word ptr fs:[bx+18h];AX=建檔日期
        shr     eax,9
        add     ax,69
        invoke  eax2dec_ascii,2 ;民國紀元,年份
        mov     ax,fs:[bx+18h]
        shr     ax,5
        inc     di
        and     ax,0fh
        invoke  eax2dec_ascii,2 ;月
        inc     di
        mov     ax,fs:[bx+18h]
        and     ax,1fh
        invoke  eax2dec_ascii,2 ;日
        mov     ax,fs:[bx+16h]
        inc     di
        shr     ax,11
        invoke  eax2dec_ascii,2 ;時
        mov     ax,fs:[bx+16h]
        shr     ax,5
        inc     di
        and     ax,3fh
        invoke  eax2dec_ascii,2 ;分
        mov     ax,fs:[bx+16h]
        inc     di
        and     ax,1fh
        shl     ax,1
        invoke  eax2dec_ascii,2 ;秒
        add     di,4
        mov     eax,fs:[bx+1ch] ;EAX=檔案長度
        invoke  eax2dec_ascii,0ffh
        mov     si,offset msg03+238
        mov     cx,7
        rep     movsb

;計算此檔案是否連續,或被分成幾個不連續的磁叢
        mov     n_break,1       ;先假設檔案沒有破碎
        call    get_fdb_cluster ;取得檔案第一個磁叢
        mov     f_cluster,eax
        or      eax,eax         ;若 EAX=0,表示此檔案長度為 0
        jz      p_dfc7
p_dfc6: invoke  get_next_cluster,buff_offset,f_cluster  ;EAX=下一個磁叢
        cmp     eax,0fffffffh   ;若 ZR,表示結束,即檔案的最後一磁叢
        je      p_dfc8
        mov     ecx,f_cluster   ;ECX=現在的磁叢
        inc     cx              ;若下一個磁叢是現在磁叢加一,表示連續
        mov     f_cluster,eax
        cmp     eax,ecx         ;若 ZR,表示此兩個磁叢是連續的
        je      p_dfc6
        inc     n_break
        jmp     p_dfc6

p_dfc7: mov     si,offset msg09 ;印出『不佔磁區…』字串
        mov     cx,11
        rep     movsb
        jmp     short p_dfca

p_dfc8: mov     si,offset msg07
        mov     cx,5
        rep     movsw
        cmp     n_break,1       ;若 n_break=1,表示檔案所佔磁叢是連續的
        jnz     p_dfc9
        mov     cx,5
        rep     movsb
        jmp     short p_dfca
p_dfc9: mov     si,offset msg08
        mov     cx,7
        rep     movsb
        movzx   eax,n_break
        invoke  eax2dec_ascii,0ffh
        mov     si,offset msg08+12
        mov     cx,4
        rep     movsw
p_dfca: mov     dx,offset dfc_str
        mov     ah,9
        int     21h
        ret
print_dfc       endp
;-----------------------------------------------------------
start:  push    ds
        push    0
        mov     ax,data
        mov     es,ax

;把使用者輸入的參數由 PSP 移到 pathname 內
        mov     cx,80h
        mov     di,offset len_pathname
        mov     si,cx
        rep     movsb

        mov     ds,ax
        and     len_pathname,0ffh       ;路徑長最多 80 字元
        jnz     st_fs0
        mov     dx,offset msg00
        jmp     short exit0
st_fs0: mov     ax,ss   ;比較那一個區段是在最高位址
        mov     bx,cs
        cmp     ax,bx   ;SS 與 CS 比較
        jae     st_fs1
        mov     ax,cs
st_fs1: mov     bx,ds   ;較高的存於 AX
        cmp     ax,bx   ;AX 與 DS 比較,較高的存於 AX
        jae     st_fs2
        mov     ax,ds
st_fs2: add     ax,1000h
        cmp     ax,7000h
        jb      mem_ok
        mov     dx,offset msg01
exit0:  mov     ah,9
        int     21h
        mov     al,01
exit1:  mov     ah,4ch
        int     21h

mem_ok: mov     fs,ax   ;讀取磁區之資料存於 FS:0000 處
        mov     read_para.tran_segment,ax
        mov     cl,pathname     ;取得磁碟機名
        mov     si,offset msg03+2
        and     cl,0dfh
        mov     [si],cl
        sub     cl,40h
        mov     driver,cl

        call    abs_sector_read ;讀取啟動磁區
        jnc     bt_ok
err_rd: mov     dx,offset msg02
        jmp     exit0

;設立 BPB,即『把 FS:0B 所在的 BPB ( 共 53 位元組 ) 移到 drv_bpb 堙z
bt_ok:  mov     si,0bh
        mov     di,offset drv_bpb
        mov     cx,53
@@:     mov     al,fs:[si]
        stosb
        inc     si
        loop    @b

        call    get_driver_info
        call    print_drv_info  ;印出此邏輯磁碟基本資料
        call    pause

fd_sb0: call    read_dir        ;讀取目錄磁區
        jc      err_rd
        test    rd_flag,1       ;若 ZR,表示此目錄不只一個磁叢,現在已處
        jne     fd_sb2          ;理完第一個磁叢,要處理第二、三……磁叢
        mov     si,dir_pointer
        cmp     byte ptr [si],cr;檢查是否已找到目標目錄了?
        je      dfc1            ;若已到達,則跳到 dfc0:
        inc     si
        cmp     byte ptr [si],cr;檢查是否已找到目標目錄了?
        je      dfc1            ;使用者有可能在路經最後面加上『\』
        call    get_sub_dir_name;若尚未到達目標目錄,則繼續找這層子目錄,
        jnc     fd_sb1          ;看看是否找得到使用者在參數列輸入的子目錄
        mov     dx,offset msg05
        jmp     exit0
fd_sb1: mov     dir_pointer,si  ;保存下一層使用者在參數列所輸入子目錄的位址
fd_sb2: sub     bx,bx           ;開始搜尋目錄磁區裡是否有相符的子目錄
fd_sb3: cmp     bx,byte_for_read;若 ZR,表示這個子目錄還有一個磁叢
        je      fd_sb7
        cmp     byte ptr fs:[bx+0bh],0fh;若為長檔名或刪除的的 FDB,
        je      fd_sb5                  ;DFC 均不予比較是否相符
        cmp     byte ptr fs:[bx],0e5h
        je      fd_sb5
        cmp     byte ptr fs:[bx],0      ;若 ZR,表示這個子目錄已全部搜尋完畢
        je      fd_sb6
        mov     si,offset sub_dir_name
        mov     di,bx
        mov     cx,0bh
fd_sb4: push    es
        mov     ax,fs
        mov     es,ax
        repe    cmpsb           ;比對 sub_dir_name 與 BX 所指的 FDB 是否相同
        pop     es
        jne     fd_sb5          ;若不同,再試試看下一個 FDB
        call    get_fdb_cluster ;若相同,表示找著子目錄了,取得子目錄磁叢
        mov     n_cluster,eax
        test    byte ptr fs:[bx+0bh],10h
        jz      dfc5            ;若 ZR,表示使用者輸入的目標是檔案
        and     rd_flag,0feh    ;先假設子目錄只佔一個磁叢
        jmp     short fd_sb0
fd_sb5: add     bx,20h
        jmp     fd_sb3
fd_sb6: mov     dx,offset msg06 ;沒找著子目錄
        jmp     exit0
fd_sb7: invoke  get_next_cluster,0,n_cluster
        mov     n_cluster,eax
        or      rd_flag,1
        jmp     fd_sb0

dfc1:   sub     bx,bx
dfc2:   cmp     bx,byte_for_read
        je      dfc4
        mov     ax,fs:[bx]
        cmp     ax,2e2eh
        je      dfc3
        cmp     ax,202eh
        je      dfc3
        cmp     al,0
        jz      exit2
        cmp     al,0e5h
        je      dfc3
        mov     cl,fs:[bx+0bh]
        test    cl,8    ;若為磁碟卷名、長檔名的 FDB 或子目錄
        jnz     dfc3    ;,則 DFC 不處理,否則呼叫 print_dfc
        cmp     cl,0fh  ;副程式
        je      dfc3
        test    cl,10h
        jnz     dfc3
        call    print_dfc
        inc     file_counter
        cmp     file_counter,23
        jne     dfc3
        call    pause
        mov     file_counter,0
dfc3:   add     bx,20h
        jmp     dfc2

;記載此目錄的磁叢不只一個,載入此目錄的下一磁叢
dfc4:   test    rd_flag,2
        jz      @f
        cmp     fat_type,32h
        jne     exit2
@@:     invoke  get_next_cluster,0,n_cluster
        mov     n_cluster,eax
        jmp     fd_sb0

dfc5:   call    print_dfc

exit2:  mov     al,0
        jmp     exit1
;***********************************************************
        end     start

後記

有關這個程式,我想大概沒有其他的地方要解釋了,如有問題,請來信問吧!

也許有人會問,DFC.EXE 為何不支援長檔名呢?其實 Unicode 和 BIG-5 碼之間的轉換需要很大的資料處理。更進一步的說明,請參考附錄十


回到首頁到第三十三章到第三十五章