具体到文件名乱码的问题,需要明确两点
但Linux内核只能逐字节处理编码流(而Windows NT内核是UCS-2的,逐2字节处理编码流),因此必须采用某种单字节编码(这包括所有的不定长编码)进行输出——这就是Linux内核所谓的NLS。
在对文件名的处理上,fat和vfat的区别在于:fat/msdos只支持短文件名(8.3命名法),而vfat加入了对长文件名和UNICODE的支持。
为了保持与fat的兼容性,在vfat中,每个文件同时拥有“长”文件名和短文件名,其中短文件名不区分大小写(实际上是不允许小写字母出现在文件名中)。可以这么理解,对于vfat,“长文件名”是文件真正的名字,“短文件名”则是提供兼容性的名字。举例来说,文件“真名”为abc.txt,它的短文件名是ABC.TXT;文件“真名”为alongname.txt,它的短文件名则是ALONGN~1.TXT。
无论是fat还是vfat,短文件名按codepage编码存储,长文件名按UNICODE编码存储。因此,如果文件的真名(也就是长文件名)是\(s\),短文件名是\(s’\),则它们在文件系统中分别被存储为\[s,\,\varphi_{enc} \left( s' \right).\]
为了访问fat/vfat文件系统,我们需要用内核的msdos或vfat模块。它们有三个跟字符集有关的内核参数:codepage,iocharset,utf8。我们来确定它们对应着什么样的编码或解码函数。
这样,参数有如下的具体选择:
codepage=936
iocharset=cp936
iocharset=utf8
但这样做的缺点是会导致vfat模块将允许短文件名使用小写字母,这与windows是不兼容的(使用iocharset=utf8时,内核会出一条警告信息的);
最后是结论:
mount -t vfat -o iocharset=cp936
mount -t vfat -o iocharset=utf8
mount -t vfat -o iocharset=cp936,utf8
实际上只要内核默认的iocharset不是utf8,直接写
mount -o utf8
就可以,这里iocharset=xxx的作用仅仅是处理大小写,所以怎么填都没关系。
我们来看一下这些选项究竟做了些什么事情,假设一个FAT文件系统在简体中文的windows上用过,而Linux程序的locale设置为UTF-8,文件名为\(s\),其短文件名为\(s’\)
SMB/CIFS是M$用于网络文件和打印共享的协议,linux下有不少该协议的实现(详情请咨询wikipedia)。server端基本是samba一统天下,client端常用的则有samba和linux的内核vfs模块smbfs/cifs。其中smbfs是依赖于samba的(更具体的,smbfs需要调用samba提供的smbmount等模块),而cifs则是一个独立的实现(尽管samba也提供了一个helper小程序mount.cifs,但这仅仅是一个转发函数而已,真正的mount过程完全由内核实现;也就是说,不需要安装samba就可以直接使用mount -t cifs,mount.cifs可以看成mount -t cifs的shortcut)。
服务器端我们不做讨论,smbconf的文章满天飞。
先说samba,这基本是SMB/CIFS的一个完整实现。大多数应用程序(如mc/nautilus/dolphin/mplayer/cups等)对windows网络共享的访问都依赖于samba所提供的smbclient,并且通常使用smb://server/share/file这种形式的URL。对于简单和偶尔的文件共享,一般来说这足够用了;另外,要用cups进行打印只能用samba。
然而,smb://server/share/file这种形式毕竟用起来不方便,特别是大部分工具并不支持samba。这时候使用内核模块就很方便,因为对所有的应用程序来说都是透明的,而且只要不用windows打印机就完全不需要依赖samba。
前面提到过内核提供了两个vfs模块:smbfs和cifs(注意区分作为协议的CIFS和作为内核vfs模块的cifs)。简单来说,用cifs。
足够新的服务端(windows>98,samba>=3)默认采用Unicode作内部编码以及网络传输,一般不会带来太多字符集/编码上的问题。
smbfs和cifs牵涉到字符集/编码的选项有两个:codepage和iocharset,其含义和MS系的其他文件系统(dosfs, vfat, ntfs, etc.)是一致的,前者指服务器传入字节流的编码,后者指本地所用的编码(具体解释请参见该文)。由于cifs总假定服务器用Unicode传输(这个假定基本上不会有问题,有问题的时候就只好用smbfs了),因此它没有codepage选项。
下面简单说明一下使用内核cifs模块进行mount网络共享的设置。
mount -t cifs -o iocharset=${NLS},user=me,pass=you,file_mode=0644,dir_mode=0755,uid=subi,gid=subi //server/share /smbshare
另外,mount.cifs是samba自带的一个小程序,就是一个简单的wrapper。我印象中mount.cifs主要的好处是它可以interactively地从标准输入接受用户名和密码,而mount -t cifs只能显式在命令行里指定。通常,mount.cifs在samba软件包里(有的发行版也给mount.cifs单独打包),而mount在util-linux软件包里。
问题:对于Samba共享目录中的符号链接(这些link指向共享目录外),Windows客户端能当成普通目录透明访问的。但在smbfs或cifs客户端里,这些符号链接能被认出来,但是链接所指的的目录在本地机器上当然是不存在的(即使存在也是本地机器的内容),因此服务器的相应资源无法访问。而用samba所附的smbclient可以正确访问。
这个问题同时与samba服务器和smbfs、cifs有关。symlink是samba所实现的unix extension的一部分(其他还包括symlink、hardlink、uid、gid等)。samba、smbfs/cifs在某些版本都实现了对unix extension的支持,其中包括symlink。
对于symlink的解释,有两种方式完成:由samba服务器follow symlink,这时候客户端是看不到symlink的存在的(比如Win客户端总是这样);由客户端follow symlink,这时候客户端知道它们是符号链接。
因此,对于windows客户端,symlink总是由服务器处理;而对于linux客户端,特别的,对于smbfs和cifs,symlink的处理依赖于服务器端和客户端的配置。samba服务器的配置文件smb.conf文件里,三个有关选项的默认设置如下:
unix extensions = yes follow symlinks = yes wide links=yes
第一个选项如果选no,则总是由服务器端来处理symlink,这时候linux的客户端和win的客户端表现是相同的——symlink被当作普通目录;而默认设置yes则是启用unix extensioin,在客户端(smbfs/cifs)也开启unix extension支持的情况下,将会看到symlink并由客户端来handle them。
第二、三个选项设置samba服务器处理symlink的方式:是否跟随链接、是否跟随指向共享目录外的链接。
而在客户端,windows就不说了。smbfs总是启用unix extension,而cifs可以通过/proc/fs/cifs/LinuxExtensionsEnabled来控制。
两种处理symlink的方式都有需求。在需要访问共享目录外内容时,由server follow link是唯一的选择;而把共享目录当作一般的网络文件系统,直接支持unix extension更符合linux的需要。如果想用smbfs又想看到共享目录外内容,请阅读Q:[30nov04] smbfs and smbclient behave differently with symlinks (soft links)进行相应的改动(修改服务器端的smb.conf或hack客户端的smbfs kernel code)。否则,最好的选择是cifs:希望由server follow link,只需将/proc/fs/cifs/LinuxExtensionsEnabled设为0后remount,该值默认为1,所以若由client follow link,啥也不用干。