2012-04-02

在 Linux 讀取 GPT 與 hfsplus 檔案系統

你的 Linux Kernel 不認識新規的 partition table(EFI GPT)?

有一些 USB 外接硬碟或隨身碟接到 Linux 上,會出現不尋常的訊息。以 fdisk(8) 檢視,則有類似以下的輸出:
# fdisk -l /dev/sda

Disk /dev/sda: 40.0 GB, 40007761408 bytes
255 heads, 63 sectors/track, 4863 cylinders
Units = cylinders of 16065 * 512 = 8225280 bytes

Device Boot      Start         End      Blocks       Id  System
/dev/sda1               1        4864    39070079+   ee  EFI GPT
這表示,在這個 disk 上的,是一種新型的 partiton table,它叫做 GPT(Globally unique identifier Partition Table),也是 EFI(Extensible Firmware Interface)的一部份。這也是在告訴我們,
fdisk(8) 已經不適用於這種儲存媒體,它所印出的 partition 編號及數量,也是錯的。
fdisk(8) 之所以尚可印出這一行,是因為這個新的標準制定時,考慮到了代溝的問題,而留下了「一絲相容性」,讓大家辨認得出來,這是個新規的 partition table。同樣地,cfdisk(8)、sfdisk(8)、...等也都不適用,因為這些都只認得大家現在(2012)還很常用的舊型 MBR partition table。

Linux kernels 基本上支援這種新型的 partition table。為了確定這個 feature 是否有 compiled 進去你所正在使用的 kernel,可以執行:
$ grep CONFIG_EFI_PARTITION /boot/config-`uname -r`
如果結果是:
CONFIG_EFI_PARTITION=y
就表示這個 kernel 認得這種新型的 partition table。

看不到 partitions 怎麼 mount?這裡有個 fdisk -l 的臨時替代程式

但是接下來該怎麼辦?要是不知道這個 disk 裡面到底有幾個,以及那一種檔案系統的 partitions 就不能夠 mount 它們啊…(除非你正在使用很完善的桌面跟很新的 automounter)。而我自己既不使用這類桌面,且所使用的系統又是個老舊的 Debian/Etch!上面找不到甚麼可以隨裝即用的程式,例如 gdisk,而 parted(8) 用起來又問題重重。這時,Udev 系統所附帶的小程式
/lib/udev/vol_id
就可以派上用場!這是所有 2.6 起的 Linux kernels 都有的 feature。以下這個小小的 shell-script 就利用 vol_id 來 probe 所有它認得的 partitions。注意,這個 script 會一直試下去,直到碰到了一個它所不認識的,才會停止。把以下的程式碼存成一個檔案叫做 ptprobe(或自己隨便取個名字),放在路徑(PATH)上,再做 chmod 755 ptprobe 就可以用了。
#!/bin/sh
VOL_ID=/lib/udev/vol_id
DEV=$1
if [ -z "$DEV" ]
then
        echo "usage: $0 "
        exit 1
fi

I=1
while true
do
        if ID=`sudo $VOL_ID -t $DEV$I 2> /dev/null`
        then
                echo "$DEV$I: $ID"
                I=`expr $I + 1`
        else
              break
        fi
done
exit 0
我檢查 /dev/sda,執行的結果如下:
$ ptprobe /dev/sda
/dev/sda1: vfat
/dev/sda2: hfsplus
如何 mount 一個 hfsplus 檔案系統?

所以,mount 第一個 partition 不是問題。但是第二個呢?這個硬碟可能來自一個 Mac OS X 的使用者(HFS Plus 檔案系統也使用於 iPod)。所以,先試試看:
# mkdir /mnt/sda2_hfsplus/
# mount -t hfsplus /dev/sda2 /mnt/sda2_hfsplus/
這時,你的系統會尋找所需的模組(kernel module)並自動載入。它典型的名稱為 hfsplus。你也可以用以下指令找找看它到底是放在哪裡:
$ modprobe -l | grep hfsplus
/lib/modules/2.6.27.62/kernel/fs/hfsplus/hfsplus.ko
萬一 Udev 系統出了差錯,並未自動載入這個 module 的話,不妨試試看手動載入:
# modprobe hfsplus
要是你的系統毫無反應,或是給了錯誤訊息:
FATAL: Module hfsplus not found.
那麼很可能你所正在使用的 kernel 並未把 hfsplus 這個檔案系統 compiled 進去。可以再次用以下指令確定一下:
$ grep CONFIG_HFSPLUS_FS /boot/config-`uname -r`
如果其輸出真的是:
# CONFIG_HFSPLUS_FS is not set
那麼,就必須 menuconfig 一下這個 kernel,把這個 feature 設為 M 或是乾脆 Y 之後,再重新編譯。如果 module 已經載入或是 compile 在 Kernel 裡(lsmod 看不到),那麼
$ cat /proc/filesystems
應該可以看到 "hfsplus"。/proc/filesystems 所列出的是系統此刻所支援的所有檔案系統類型。

要是一切都很順利的話,你可能會希望可以寫入這個硬碟,因為預設值為 read only。這時,可用 "force,rw" 這個選項:
# mount -t hfsplus -o force,rw /dev/sda2 ....
要是硬碟裡有 Big5 的漢字,你可能會需要加入 "nls=cp950" 這個選項(使用 "charset=cp950",它會抱怨語法錯誤 )。也就是:
# mount -t hfsplus -o force,rw,nls=cp950 /dev/sda2 ......
如果發現硬碟裡有一些檔案是因沒有權限,而不能讀取的,它們的 uid 很可能是 501, 502, ... 這時,也可以試試:
# mount -t hfsplus -o force,rw,nls=cp950,uid=501,gid=100 /dev/sda2 .....
要是想要放進 /etc/fstab 裡一勞永逸,可以考慮採用以下範例:
/dev/sda2 /sda2_hfsplus hfsplus force,rw,nls=cp950,users,uid=501,gid=100 0 0
請注意:我自己還不清楚強制讀寫模式 (force,rw) 會導致那些不良後果。所以,請小心並自行負責。也請注意,hfsplus 這種檔案系統 mount 到 Linux 上之後,symbolic links 是可以用的。另外必須注意的是,目前必須避免在 Linux 上使用 hfsplus 的 jounaling [4]

我自己有一個自動的 mounting system,掛勾在 udev 系統上,不但可以自行 mount 一個 hfsplus 檔案系統,還可以讓它發出自己喜歡的響聲(參見〈Linux 的 udev 是個有趣又有用的玩具 (5en) : Smallest Working Bourne-Shell Automounter〉[e])。如何撰寫這種程式,請參考我的相關系列文章〈Linux 的 udev 是個有趣又有用的玩具〉 [a] [b] [c] [d] [e]

如何檢查或初始化新的 hfsplus 檔案系統?fsck.hfsplus 跟 mkfs.hfsplus

要是忘了 umount,就拔掉硬碟,下一次插入這個硬碟時,會出現下列訊息:
hfs: Filesystem was not cleanly unmounted, running fsck.hfsplus is recommended. mounting read-only.
可是,fsck.hfsplus(8) 哪裡來?如果用的是 Debian Lenny 或更新的系統版本,那麼
# aptitude install hfsprogs
就會裝上 fsck.hfsplus(8) 跟 mkfs.hfsplus(8)。如果是 Debian Etch 或更舊的系統版本,則必須下載原始碼,自行 complie。我是這麼做的:(亦見 [1]
$ mkdir /usr/local/src/hfsplus/
$ cd /usr/local/src/hfsplus/
$ wget -U opera ftp://ftp.fi.debian.org/pub/gentoo/distfiles/diskdev_cmds-332.14.tar.gz
$ wget ftp://ftp.fi.debian.org/pub/gentoo/distfiles/diskdev_cmds-332.14.patch.bz2
$ gzip -dc diskdev_cmds-332.14.tar.gz | tar xvf -
$ bzip2 -dc diskdev_cmds-332.14.patch.bz2 | patch -p0
$ cd diskdev_cmds-332.14/
$ make -f Makefile.lnx
$ cp -p newfs_hfs.tproj/newfs_hfs /usr/local/sbin/mkfs.hfsplus
$ cp -p fsck_hfs.tproj/fsck_hfs /usr/local/sbin/fsck.hfsplus
$ ln -s /usr/local/sbin/mkfs.hfsplus /usr/local/sbin/mkfs.hfs
$ ln -s /usr/local/sbin/fsck.hfsplus /usr/local/sbin/fsck.hfs
接下來就可以試試剛裝好的 fsck.hfsplus,例如:
$ sudo fsck.hfsplus /dev/sda2
會出現以下訊息:
** /dev/sdg2
** Checking HFS Plus volume.
fsck_hfs: Volume is journaled.  No checking performed.
fsck_hfs: Use the -f option to force checking.
再做一次 force checking:
$ sudo fsck.hfsplus -f /dev/sda2
則會看到:
** /dev/sdg2
** Checking HFS Plus volume.
** Detected a case-sensitive catalog.
** Checking Extents Overflow file.
** Checking Catalog file.
** Checking Catalog hierarchy.
** Checking Extended Attributes file.
     Reserved fields in the catalog record have incorrect data (8, 3)
     Reserved fields in the catalog record have incorrect data (8, 3)
** Checking volume bitmap.
** Checking volume information.
** Repairing volume.
Segmentation fault
哇!"Segmentation fault"!這些程式碼還沒有很乾淨地 port 到 Linux 來。雖然發生了 "Segmentation fault",但後來 "Filesystem was not cleanly unmounted" 這個 message 就沒有再出現,這很可能是因為這個程式 reset 了一個 flag,而我的硬碟拔掉前,沒有什麼寫入的動作,所以「還算」乾淨。如果你有一個 Mac OSX,也可以參考 [3]。如果你有 iPod,[2] 也有很詳細的說明。

如何在 Linux 停止 hfsplus 的 journaling?

但無論如何,請注意要「停掉」hfsplus 檔案系統的 journaling 之後,才適合在 Linux 上對 hfsplus 做安全的寫入動作。由 ubuntu forums [5],我們轉載網友 blink 修改過的 C 程式碼如下:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <byteswap.h>
int main(int argc, char *argv[])
{
        int fd = open(argv[1], O_RDWR);
        if(fd < 0) {
                perror("open");
                return -1;
        }
        unsigned char *buffer = (unsigned char *)mmap(NULL, 2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
        if(buffer == (unsigned char*)0xffffffff) {
                perror("mmap");
                return -1;
        }
        if((buffer[1024] != 'H') && (buffer[1025] != '+')) {
                fprintf(stderr, "%s: HFS+ signature not found -- aborting.\n", argv[0]);
                return -1;
        }
        unsigned long attributes = *(unsigned long *)(&buffer[1028]);
        attributes = bswap_32(attributes);
        printf("attributes = 0x%8.8lx\n", attributes);
        if(!(attributes & 0x00002000)) {
                printf("kHFSVolumeJournaledBit not currently set in the volume attributes field.\n");
        }
        attributes &= 0xffffdfff;
        attributes = bswap_32(attributes);
        *(unsigned long *)(&buffer[1028]) = attributes;
        buffer[1032] = '1';
        buffer[1033] = '0';
        buffer[1034] = '.';
        buffer[1035] = '0';
        buffer[1036] = 0;
        buffer[1037] = 0;
        buffer[1038] = 0;
        buffer[1039] = 0;
        printf("journal has been disabled.\n");
        return 0;
}
把這些碼存成,例如 hfsplus_disable_joural.c,
$ gcc hfsplus_disable_joural.c
$ sudo ./a.out /dev/sda2
看到訊息:
attributes = 0x80002100
journal has been disabled.
如果這個檔案系統本來就沒有 journal,那麼這個程式會輸出:
attributes = 0x80000100
kHFSVolumeJournaledBit not currently set in the volume attributes field.
journal has been disabled.
把 journaling disable 掉之後,再試試看 fsck.hfsplus(8),這次就不需要用 -f 來強迫它去檢查 journaling 的檔案系統。雖然 "Segmentation fault" 依然發生,就看 Debian 更新的版本是不是會把這個 bug 除掉。

Related Articles

[a] 2011-11-04, Linux 的 udev 是個有趣又有用的玩具(1)
     http://kolmogolovi.blogspot.com/2011/11/linux-udev-1.html

[b] 2011-11-11, Linux 的 udev 是個有趣又有用的玩具(2)
     http://kolmogolovi.blogspot.com/2011/11/linux-udev-2.html

[c] 2011-11-11, Linux 的 udev 是個有趣又有用的玩具(3)
     http://kolmogolovi.blogspot.com/2011/11/linux-udev-3.html

[d] 2011-11-21, Linux 的 udev 是個有趣又有用的玩具(4)
     http://kolmogolovi.blogspot.com/2011/11/linux-udev-4.html

[e] 2012-06-13 Linux 的 udev 是個有趣又有用的玩具 (5en) : Smallest Working Bourne-Shell Automounter
     http://kolmogolovi.blogspot.tw/2012/06/linux-udev-5-smallest-working-bourne.html

External Links

[1] Check/Fix HFS+ From Linux # fsck.hfsplus - InsanelyMac Forum
     http://www.insanelymac.com/forum/index.php?showtopic=33982

[2] Mac iPod on Linux
     http://kpweb.homelinux.org/recipes/mac_ipod_on_linux.html

[3] Repair HFS+ Volume on OSX - Blog pro de Jean-Baptiste HEREN
     http://blog.jbheren.com/post/2011/02/09/repair-hfs-volume-on-osx

[4] Hfsplus - Gentoo Linux Wiki
     http://en.gentoo-wiki.com/wiki/HOWTO_hfsplus

[5] Disable HFS+ journaling from within Ubuntu, ubuntu forums
     http://ubuntuforums.org/showthread.php?t=1420673
Bladehawke: It *can* be done. And there's code already written to do it. Please see http://dmunsie.wordpress.com/code/hacks/ and http://loquerasparalinux.blogspot.co...aling-sin.html for the particulars. In short, dmunsie wrote a brief C program for OSX that can change the necessary bits in the partition header and the second link points to a very simple patch to make it compile under linux along with instructions for use.

blink: I tweaked the code to compile cleanly on ubuntu. The two line difference is highlighted in yellow on the pastebin link. http://pastebin.com/W8pfgHRe

沒有留言:

張貼留言