【转】关于mount/samba/字符集的两篇好文

第一篇:字符集和编码II: fat/msdos/vfat(链接至原作者博客)

具体到文件名乱码的问题,需要明确两点

  1. 第一,文件名作为一个字符串,需要被编码后存入文件系统;
  2. 第二,Linux内核无非是个特殊的应用程序,它读取文件名,再把文件名以编码后的形式传递出去。

但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=enc:用来转换短文件名中字节码为128~255的代码页。这一选项指定的是短文件名的解码函数\( \varphi_{enc}^{-1} \)(因为短文件名被codepage编码了),解码的结果是短文件名被用unicode表示;
  • iocharset=enc:用来转换长文件名和解码后的短文件名。也就是说,这一选项指定编码函数\(\varphi_{enc}\),使得内核最终的输出是经过\(\varphi_{enc}\)编码的;
  • utf8:几乎等于iocharset=utf8

这样,参数有如下的具体选择:

  • codepage:这一选项只跟短文件名有关。短文件名可以通过mount -t msdos看到,也可以用windows命令行看到。不在乎短文件名的同学,这个选项完全无所谓;在乎短文件名并且使用简体中文的同学,请写

    codepage=936
    
  • iocharset:分情况讨论。

    • 若locale(不知道什么是locale的同志请自行补课)不是utf8,则选择相应的locale,比如GB系就应该选

      iocharset=cp936
      
    • 若locale是utf8的,可以选择

      iocharset=utf8
      

      但这样做的缺点是会导致vfat模块将允许短文件名使用小写字母,这与windows是不兼容的(使用iocharset=utf8时,内核会出一条警告信息的);

  • utf8:一个解决办法是使用utf8参数,只要iocharset!=utf8,vfat就会处理大小写的问题;而utf8参数则会最终处理字符集的转换。utf8参数只能显式地通过mount调用(不能在编译内核时预先指定,当然也可以写在/etc/fstab里),我的意思是这个选项不能偷懒不写。

最后是结论:

  1. codepage=936,这一选项可以在内核默认设置;

  2. 本地locale是gb系的,参数应为

    mount -t vfat -o iocharset=cp936
    
  3. 本地locale是utf8的,如果能忍受内核每次都要警告,并且不跟windows交互,可以

    mount -t vfat -o iocharset=utf8
    
  4. 要是不能忍3或者交互时出现奇怪的问题,那就

    mount -t vfat -o iocharset=cp936,utf8
    

    实际上只要内核默认的iocharset不是utf8,直接写

    mount -o utf8
    

    就可以,这里iocharset=xxx的作用仅仅是处理大小写,所以怎么填都没关系。

我们来看一下这些选项究竟做了些什么事情,假设一个FAT文件系统在简体中文的windows上用过,而Linux程序的locale设置为UTF-8,文件名为\(s\),其短文件名为\(s’\)

  1. 在文件系统上(也就是在硬盘里),文件名被存为\(s\),短文件名被存为\(\varphi_{cp936} \left( s’ \right)\);
  2. 内核通过codepage=936选项,将映射\(\varphi_{cp936}^{-1}\)作用于\(\varphi_{cp936} \left( s’ \right)\),得到\[\varphi_{cp936}^{-1} \circ \varphi_{cp936} \left( s' \right) = s';\]
  3. 内核通过utf8或iocharset=utf8选项,将映射\(\varphi_{utf8}\)作用于\(s, s’\),最终向应用程序输出\[\varphi_{utf8} \left( s' \right), \varphi_{utf8} \left( s \right);\]
  4. 应用程序(如ls、nautilus)通过locale的设置,将\(\varphi_{utf8}^{-1}\)作用于内核输出,得到我们所看见的字符串\(s,s’\).

第二篇:smbfs/cifs(链接至原作者博客)

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网络共享的设置。

  • 首先,保证服务端的网络传输字符集是Unicode。对于win2k之后的windows操作系统、以及正常配置的samba server,这一点一般不需要操心。(如果有特殊情况或需要而使得samba server的传输字符集不是Unicode,请参考该文)。

  • 其次,确定客户端(也就是本机)所用的字符集。支持中文的字符集有UTF-8,GB2312、GBK、GB18030等等。如果是UTF-8,取NLS=utf8;如果是GB2312、GBK、GB18030,取NLS=cp936。(对于足够新的发行版,基本上utf8一统天下。)

  • 最后的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的符号链接

问题:对于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,啥也不用干。