android fsck_msdos 分析(二)

         其實寫完前面的關於FAT文件系統的簇檢查那一部分之後,我一直沒準備寫第二部分關於文件目錄項處理這一部分,因爲這部分都是按照FAT規範來處理的。

  1. handleDirTree(int dosfs, struct bootblock *boot, struct fatEntry *fat)

  2. {

  3.          int mod;

  4.          mod = readDosDirSection(dosfs, boot, fat, rootDir);

  5.          if (mod & FSFATAL)

  6.                    return FSFATAL;

  7.          /*

  8.           * process the directory todo list

  9.           */

  10.          while (pendingDirectories) {

  11.                    struct dosDirEntry *dir = pendingDirectories->dir;

  12.                    struct dirTodoNode *n = pendingDirectories->next;

  13.                    /*

  14.                     * remove TODO entry now, the list might change during

  15.                     * directory reads

  16.                     */

  17.                    freeDirTodo(pendingDirectories);

  18.                    pendingDirectories = n;

  19.                    /*

  20.                     * handle subdirectory

  21.                     */

  22.                    mod |= readDosDirSection(dosfs, boot, fat, dir);

  23.                    if (mod & FSFATAL)

  24.                             return FSFATAL;

  25.          }

  26.          return mod;

  27. }

        這段是處理fat文件系統的目錄項的相關代碼,其中root->dir是構造出來的。因爲FAT的根目錄在磁盤介質中沒有實際的元數據。readDosDirSection讀出一個目錄項下的所有子目錄項(這裏的目錄項是一個統稱,包括文件file和目錄direntory

Name

Offset(byte)

Size(byte)

description

DIR_NAME

0

11

11個字節又分爲兩段,其中前8個字節爲文件名,後三個字節爲文件的擴展屬性,比如sample.txt,文件名爲sample,擴展文件名爲txt

DIR_Attr

11

1

文件屬性:

ATTR_READ_ONLY   0x1

ATTR_HIDDEN      0X2

ATTR_SYSTEM      0X4

ATTR_VOLUME_ID  0X8

ATTR_DIRECTORY   0X10

ATTR_ARCHIVE     0X20

ATTR_LONG_NAME  =ATTR_READ_ONLY|ATTR_HIDDEN|ATTR_SYSTEM

|ATTR_VOLUME_ID

DIR_NTRes

12

1

Windows NT用,現在設置爲0

DIR_CrtTimeTenth

13

1

文件創建時間,微秒段

DIR_CrtTime

14

2

文件創建時間

DIR_CrtDate

16

2

文件創建日期

DIR_LstAccDate

18

2

文件的最後訪問時間

DIR_FstClusHI

20

2

該文件首簇的高16bitFAT12FAT16中設置爲0

DIR_WrtTime

22

2

文件的最後寫的時間

DIR_WrtDate

24

2

文件最後寫的日期

DIR_FstClustLO

26

2

文件首簇的低16bit

DIR_FileSize

28

4

文件的大小

他們是根據DIR_Attr字段來區分的,需要說明的是,在FAT32中,bootsector中指明瞭rootdir所在的位置,一般是cluster = 2的位置(即數據區的第一個簇)。但是在FAT12FAT16中,該字段是0

readDosDirSection讀出一個文件夾下的所有目錄項,將其添加到pendingDirectories鏈表中,這個鏈表中的所有目錄項都是等待被處理的。

readDosDirSection這個函數特別長,我們以閱讀代碼的方式來一段一段的分析。

  1. cl = dir->head;
  2. if (dir->parent && (cl < CLUST_FIRST || cl >= boot->NumClusters)) {
  3. /*
  4. * Already handled somewhere else.
  5. */
  6. return FSOK;
  7. }

我們知道目錄項中的DIR_FstClusHI指定了該文件簇鏈的第一個簇的位置。必須是在數據區的,即在CLUST_FIRSTboot->NumClusters之間。上面這段代碼跳過了這種情況:

(1)              該目錄項是一個文件夾,但是該文件夾是空的,即沒有子文件,那麼其dir->head必然等於0,對於空文件,沒有必要進一步檢查其子目錄項。

         do {

                   if (!(boot->flags & FAT32) && !dir->parent) {

                   //in FAT12 or FAT16,each direntry take over 32 bytes

                            last = boot->RootDirEnts * 32;

                            off = boot->ResSectors + boot->FATs * boot->FATsecs;

                   } else {//FAT32

                            last = boot->SecPerClust * boot->BytesPerSec;

                            //caculate the offset ,because the first data sector is NO.2

                            off = cl * boot->SecPerClust + boot->ClusterOffset;

                   }

這段代碼使用來處理FAT12  FAT16的根目錄的,因爲他們的boot->rootdir字段等於0.所以根據bootsectorFAT表的大小來算rootdir的偏移。

BootsectorResSectors

FAT

根目錄區

數據區

                   off *= boot->BytesPerSec;

                   if (lseek64(f, off, SEEK_SET) != off) {

                        printf("off = %llu\n", off);

                            perror("Unable to lseek64");

                            return FSFATAL;

                }

                if (read(f, buffer, last) != last) {

                            perror("Unable to read");

                            return FSFATAL;

                }

                   last /= 32;

上面這段通過rootdir的首簇的位置,來讀出這段數據,下一步就是開始解析了。其中last是在該sector中最後一個目錄項的下標,因爲不管是長目錄項還是短目錄項,其大小都是32比特。

                   for (p = buffer, i = 0; i < last; i++, p += 32) {

                            if (dir->fsckflags & DIREMPWARN) {

                                     *p = SLOT_EMPTY;

                                     continue;

                            }

如果fsckflags標誌位DIREMPWARN被設置的話,那麼就將剩下的所有目錄項設置爲SLOT_EMPTY,至於爲什麼,下面再仔細講解。

                            if (*p == SLOT_EMPTY || *p == SLOT_DELETED) {

                                     if (*p == SLOT_EMPTY) {

                                               dir->fsckflags |= DIREMPTY;

                                               empty = p;

                                               empcl = cl;

                                     }

                                     continue;

                            }

上面這段文字是MSFAT spec中關於SLOT_EMPTY的解釋。是說如果發現一個目錄項的第一個字節是0x00,那麼就意味着在它之後的所有目錄項同樣是空的。如果一切正常的,那麼就會一直執行上面的代碼,因爲它之後所有目錄項的第一個字節同樣是0x00。但是凡事都有意外,fsck的目的就是修復這些意外的情況。

                            if (dir->fsckflags & DIREMPTY) {

                                     if (!(dir->fsckflags & DIREMPWARN)) {

如果執行到此處,表示出現了意外,因爲在SLOT_EMPTY之後出現了一些正常的目錄項。從下面的打印信息也可以看出來,has entries after end of directory

                                               pwarn("%s has entries after end of directory\n",fullpath(dir));

                                               if (ask(1, "Extend")) {

u_char *q;

                                                        dir->fsckflags &= ~DIREMPTY;

                                                        if (delete(f, boot, fat,empcl, empty - buffer,cl, p - buffer, 1) == FSFATAL)

delete用於刪除屬於該dir中一些目錄項,需要注意的,這些dir可能是跨簇的,而且這些簇可能並不連續。需要從FAT中查詢獲得。準確的說,是正確的利用了第一階段簇檢查的結果。

                                                                 return FSFATAL;

                                                        q = empcl == cl ? empty : buffer;

                                                        for (; q < p; q += 32)

                                                                 *q = SLOT_DELETED;

                                                        mod |= THISMOD|FSDIRMOD;

                                               } else if (ask(1, "Truncate"))

                                                        dir->fsckflags |= DIREMPWARN;

截斷操作,注意這兒設置了DIREMPWARN標誌位,這樣就會出現了最開始出現的關於DIREMPWARN標誌位的判斷。如果DIREMPWARN,那麼就將剩下的所有目錄項的第一個字節設置爲SLOT_EMPTY

                                     }

                                     if (dir->fsckflags & DIREMPWARN) {

                                               *p = SLOT_DELETED;

                                               mod |= THISMOD|FSDIRMOD;

                                               continue;

                                     } else if (dir->fsckflags & DIREMPTY)

                                               mod |= FSERROR;

                                     empty = NULL;

                            }

這樣關於目錄項的檢查就算通過了,下面就是開始解析正常的目錄項了。

                            if (p[11] == ATTR_WIN95) {

// ATTR_WIN95LDIR_Attr中的一個標誌位,用來標識該目錄項是長目錄。

Name

Offset(byte)

Size (bytes)

description

LDIR_Ord

0

1

長目錄項的index

如果0x40,那麼就是長目錄項的最後一個

LDIR_Name1

1

10

長名字的第1-5個字符

LDIR_Attr

11

1

屬性

LDIR_Type

12

1

如果是0,表示該長目錄項是長文件名的一部分

LDIR_Chksum

13

1

校驗和,用於檢驗長文件名的完整性

LDIR_Name2

14

12

長文件名的第6-11個字符

LDIR-FstClustLO

26

2

必須爲0

LDIR_Name3

28

4

長文件名的第12-13個字符

                                     if (*p & LRFIRST) {

//從上面的表格可以看出,長目錄項的第一個字節用來標示該長目錄項的下標,但是凡事都有第一。那麼LRFIRST 0x40)用來表示第一個長目錄項。我們知道要正確的解析一個長文件名的話,需要讀出全部的長目錄項來解析,要不然算出的校驗和就不對。

                                               if (shortSum != -1) {

                                                        if (!invlfn) {

                                                                 invlfn = vallfn;

                                                                 invcl = valcl;

                                                        }

                                               }

                                               memset(longName, 0, sizeof longName);

                                               shortSum = p[13];

                                               vallfn = p;

                                               valcl = cl;

其中vallfnvalcl是用來記錄一個長目錄項的起始位置的,因爲一旦出錯,後面就有可能刪除相關的全部的目錄項

                                     } else if (shortSum != p[13]

                                                  || lidx != (*p & LRNOMASK)) {

                                               if (!invlfn) {

                                                        invlfn = vallfn;

                                                        invcl = valcl;

                                               }

                                               if (!invlfn) {

                                                        invlfn = p;

// 注意上面的vallfn 與這兒的invlfn的差別,invlfn前面的ininvalid的意思,是在出錯情況下用來保存目錄項的位置的,以便將來delete或者truncate

                                                        invcl = cl;

                                               }

                                               vallfn = NULL;

                                     }

下面的代碼我就不貼了,就是具體的分析計算出每一個長目錄項所包含的名字部分。需要注意的是長目錄項中的LDIR-FstClustLO字段必須爲0,因爲這個字段是用來指示文件或者文件夾所對應的第一個簇。但是長目錄項是一個附加項,是在短目錄項不能完整的表達文件名的時候,用來存儲文件名的,除此之外,沒有其餘的用途。

                            /*

                             * This is a standard msdosfs directory entry.

                             */

長目錄項之後就是一個標準的短目錄項,不管是文件還是目錄,都有相應的短目錄項,但是並不一定有相應的長目錄項。

                            memset(&dirent, 0, sizeof dirent);

                            /*

                             * it's a short name record, but we need to know

                             * more, so get the flags first.

                             */

                            dirent.flags = p[11];

                            /*

                             * Translate from 850 to ISO here           XXX

                             */

                            for (j = 0; j < 8; j++)

                                     dirent.name[j] = p[j];

                            dirent.name[8] = '\0';

                            for (k = 7; k >= 0 && dirent.name[k] == ' '; k--)

                                     dirent.name[k] = '\0';

                            if (dirent.name[k] != '\0')

                                     k++;

                            if (dirent.name[0] == SLOT_E5)

                                     dirent.name[0] = 0xe5;

SLOT_E5是一個特別的字符,爲0xE5,前面的SLOT_DELETED正好是0Xe5,所以在需要真正用到0XE5的時候,就要0x5來代替,所以這兒需要進行轉義。有人會問那0X5本身呢。如果你查碼錶的話,會發現0X5是個特殊的字符,在FAT中不用。

                            if (dirent.flags & ATTR_VOLUME) {

                                     if (vallfn || invlfn) {

                                               mod |= removede(f, boot, fat,

                                                                 invlfn ? invlfn : vallfn, p,

                                                                 invlfn ? invcl : valcl, -1, 0,

                                                                 fullpath(dir), 2);

                                               vallfn = NULL;

                                               invlfn = NULL;

                                     }

                                     continue;

                            }

ATTR_VOLUME是一個特殊的標誌位,看看spec的解釋吧。

同時需要注意的是,長目錄項中也設置了該標誌位,但是短目錄項中並沒有。

                            if (vallfn && shortSum != calcShortSum(p)) {

                                     if (!invlfn) {

                                               invlfn = vallfn;

                                               invcl = valcl;

                                     }

                                     vallfn = NULL;

                            }

比較短目錄項中計算出來的校驗和與長目錄項中計算的校驗和是否相等。

                            dirent.parent = dir;

                            dirent.next = dir->child;

設置其父指針,這樣就會在內存中構造一個文件目錄樹。

下面的比較簡單,不一一列出,但是需要注意幾點:

(1)       對於文件夾而言,其dirent.size == 00

(2)       如果一個目錄項代表的是一個文件,那麼需要校驗文件的大小。如果其簇鏈長度超出了文件的長度,那麼就需要截斷。被截斷的內容將來會被放置到LOST.DIR中,因爲它是無主的,即沒有對應的目錄項。

(3)       當一個空文件夾被創建時,會在其下建立兩個特殊的目錄項,就是“.”和“..”。其中dotdir->head等於其本身dirhead,而dotdothead等於dir->parent->head.

(4)       對於一個dir,檢測合格之後會通過下面的代碼

                               n->next = pendingDirectories;

                               n->dir = d;

                               pendingDirectories = n;

添加到鏈表中,以便後面檢測它的子目錄項。但是對於”.””..”,跳過了這一步。因爲如果將其也添加到pendingDirectories隊列中的話,就會陷入死循環中。爲什麼?留給讀者自己去理解。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章