Tuesday, March 30, 2010

ext2文件系统

为什么要了解ext2文件系统?

因为想知道quota的工作原理

为什么不是ext3或者是ext4?

相比之下,ext2入门比较简单

ext2的结构

首先碰到两个概念:

1. block

在ext2的逻辑上, block是最小单位。 大小是512*N(N>2)。512是现在常用硬盘的扇区大小。这个大小可以通过mkfs.ext2的-b参数指定。
其实, 现在已经有扇区大小为4K的硬盘了, 见DBA notes的blog: http://www.dbanotes.net/review/hard_drive_4k_sector.html

2. block group

ext2把N多的block划分成group处理, 称为block group。 这个数目可以通过mkfs.ext2的-G参数指定.

block group的具体细节

从图中可以看出, 每个block group有六部分. 其中有二部分是每个block group都是相同的:
1. super block: 
主要存放文件系统整体情况信息, 如
...(被截断)...
340   __le32  s_inodes_count;   /* Inodes count */
341   __le32  s_blocks_count;   /* Blocks count */
342   __le32  s_r_blocks_count; /* Reserved blocks count */
343   __le32  s_free_blocks_count;  /* Free blocks count */
344   __le32  s_free_inodes_count;  /* Free inodes count */
345   __le32  s_first_data_block; /* First Data Block */
346   __le32  s_log_block_size; /* Block size */
347   __le32  s_log_frag_size;  /* Fragment size */
348   __le32  s_blocks_per_group; /* # Blocks per group */
349   __le32  s_frags_per_group;  /* # Fragments per group */
350   __le32  s_inodes_per_group; /* # Inodes per group */
...(被裁断)...

block group数目 = s_blocks_count / s_blocks_per_group
每个block group的开始block位置 = ((group_number - 1)* blocks_per_group)
* 这里的开始block位置不是物理地址! 相当于block的编号

2. group descriptor
这里保存了每个block group的一些重要信息:

106 struct ext2_group_desc
107 {
108   __le32  bg_block_bitmap;    /* Blocks bitmap block */
109   __le32  bg_inode_bitmap;    /* Inodes bitmap block */
110   __le32  bg_inode_table;   /* Inodes table block */
111   __le16  bg_free_blocks_count; /* Free blocks count */
112   __le16  bg_free_inodes_count; /* Free inodes count */
113   __le16  bg_used_dirs_count; /* Directories count */
114   __le16  bg_pad;
115   __le32  bg_reserved[3];
116 };

可以看group descriptor保存了每个block group的使用信息.
group descriptor共使用的block数目 = (sizeof(struct ext2_group_desc) * block group数目) / s_blocks_count
* 存在余数还要加1, 文档里只是这样说的: rounded up if necessary,  见文档

下面部分是每个block group不同的:

3. block bitmap: 记录当前block group的block使用情况, 每个bit表示一个block是否被使用, 1表示被使用, 0表示没有被使用

4. inode bitmap: 和block bitmap的作用一样.

5. inode table:  存放struct ext2_inode数据结构, 每一个struct ext2_inode结构表示一个文件. 文件的属性都存放于这个struct里.

6. data blocks: 主要是存放数据的block.

每个struct ext2_inode是怎么编号, 每个block是怎么对应上物理地址?

上面的信息中, 还有两个问题:
1. 没有提到inode统一分配, 如: block group 2有哪些inode?
2. 没有提到物理地址. 如怎么得到block 1000的4K bytes?

实现上, 这两种信息都是通过计算得到的. 基于两个前提:
1. 逻辑分区的扇区是连续的. 也就是说, block都是连续的. 如: block 1, block 2, ...
2. block group也是连续的. 如: block group 1, block group 2, ...

知道block号码求得block的位置:
The block in that group (offset in blocks) =  block_number %s_blocks_per_group

知道inode号码求得inode的位置:
The inode in that group (offset in blocks)  =  inode_number % s_inodes_per_group
这样就可以知道每个block group有哪些inode, 每个struct ext2_inode结构表示inode

从位置如果得到物理地址?
block group 1的开始物理地址(也是block 1)是可以直接寻址:
The starting point for the file system is the superblock and is always located at an offset of 1024 bytes from the start of the file system.
* 文件系统前1024bytes不使用.
这样就可以计算出每个block group的地址, 然后再求得组出的block的物理地址.

总:

声明: 仅仅是个人的解理, 后果自负




Monday, March 29, 2010

vsftpd的文件名编码

问题的来由

前几天, 朋友被这一个需求烦着:希望freeBSD下的vsftpd不把用户(windows环境)上传的文件名保存为UTF-8,即希望是GBK编码。
在vsftpd的配置文件里, 没有提到这样的配置项。
于是,兴趣一上, 自己就去看vsftpd的代码了。 都是找了一圈,根本没有找到vsftpd是如何转编码的代码。(linux下的软件不都是使用libc的iconv函数的吗?)
于是想了一下,这种编码转换不是由vsftpd处理的, 而是由用户端软件处理的, 也就是说ftp协议本身有这样的定义。

理由:
1. server端根本不知道client系统是使用什么编码的
2. 在vsftpd的代码没有看到一点编码转换的代码。

在这两种情况下, 只能在通信中指定编码的类型。协议中应该有这样的定义。

查到的文档

于是, 去查看RFC文档: http://filezilla-project.org/specs/rfc2640.txt

FTP对FEAT命令的要求:
3.3 Clients compliance
   - Clients MUST support the FEAT command and recognize the "UTF8"
     feature (defined in 3.2 above) to determine if a server supports
     UTF-8 encoding.
* 注意其中的MUST单词。
* client端是一定可以知道服务端是否支持UTF-8的

上面可以看到: client端是一定可以知道服务端是否支持UTF-8的

使用wireshark分析

监听网络是最方便判断这个问题的方法。
如果是client传送了UTF-8字节流, 在client发出的通信数据中, 一定是UTF-8的.如下是使用wireshark监听到的client請求:
7    18.059426    192.168.0.102    192.168.0.102    FTP    Request: MKD \346\226\260\346\226\207\344\273\266\345\244\271 (7)
看到得到一串bytes, 这串bytes就是UTF-8数据:
print(unicode("\346\226\260\346\226\207\344\273\266\345\244\271","utf-8"))
新文件夹

测试FEAT和OPTS命令

关系feat和opts命令, 可以从这里得到信息:http://www.networksorcery.com/enp/rfc/rfc2389.txt
The File Transfer Protocol is, from time to time, extended with new
commands, or facilities. Implementations of the FTP protocol cannot
be assumed to all immediately implement all newly defined mechanisms.
This document provides a mechanism by which clients of the FTP
protocol can discover which new features are supported by a
particular FTP server.


feat和opts不是user interface, 它是FTP协议的命令, 就如HTTP中的session和host一样
所以使用客户端是无法看到的, 比如lftp或者ftp
好在ftp协议是本文协议, 可以使用telnet看清楚。
$ telnet 192.168.0.102 21
Trying 192.168.0.102...
Connected to 192.168.0.102.
Escape character is '^]'.
220 (vsFTPd 2.2.2)
user jessinio
331 Please specify the password.
password
530 Please login with USER and PASS.
feat
211-Features:
 EPRT
 EPSV
 MDTM
 PASV
 REST STREAM
 SIZE
 TVFS
 UTF8
211 End

可以看到支持UTF-8功能。

opts命令是为实现ftp日益增加的命令而存在的。下面测试opts命令:
$ telnet 192.168.0.102 21
Trying 192.168.0.102...
Connected to 192.168.0.102.
Escape character is '^]'.
220 (vsFTPd 2.2.2)
user jessinio
331 Please specify the password.
ivwsydfku
530 Please login with USER and PASS.
opts utf on
501 Option not understood.
opts utf8 on
200 Always in UTF8 mode.


手工禁止vsftpd的功能

现在把vsftpd代码里的feat命令和对opts命令返回信息做一下修改.

把features.c文件中的
vsf_cmdio_write_raw(p_sess, " UTF8\r\n");
去掉
把opts.c文件的handle_opts函数修改成如下:
 14 void
 15 handle_opts(struct vsf_session* p_sess)
 16 {
 17   str_upper(&p_sess->ftp_arg_str);
 18   //if (str_equal_text(&p_sess->ftp_arg_str, "UTF8 ON"))
 19   //{
 20   //  vsf_cmdio_write(p_sess, FTP_OPTSOK, "Always in UTF8 mode.");
 21   //}
 22   //else
 23   //{
 24     vsf_cmdio_write(p_sess, FTP_BADOPTS, "Option not understood.");
 25   //}
 26 }


Wednesday, March 10, 2010

自己的域名与Blogger

通过自己的域名(如本人使用blog.jessinio.info)去访问自己的Blogger内容, 早就是这样使用的了。
但是没有理会其中的具体细节。

今天在看DNS方面的知识时, 突然就对这个问题感兴趣了。提出这一个问题:为什么使用blog.jessinio.info可以访问blogger的内容?

下面做一下测试, 随便玩一下。
本人的blog.jessinio.info通过browser是可以访问的。这也是Blogger的内容。看一下是什么:
jessinio@jessinio-laptop:~$ ping blog.jessinio.info
PING ghs.you8g.com (69.164.192.240) 56(84) bytes of data.
64 bytes from li107-240.members.linode.com (69.164.192.240): icmp_seq=1 ttl=49 time=303 ms
64 bytes from li107-240.members.linode.com (69.164.192.240): icmp_seq=2 ttl=49 time=304 ms

网络是通的, 也很正常, 就是对应着一个IP地址。但是一个很神奇的事情:在browser的地址栏中直接访问上面的IP地址是得不到blogger的内容的:

Not Found

The requested URL / was not found on this server.

* 可以亲自试一试: http://69.164.192.240/

先看一下ping命令的結果是什么意思:

0. blog.jessinio.info,本人创建的一个CNAME记录
1. ghs.you8g.com, 也是一个CNAME记录,指向的ANAME记录
2. li107-240.members.linode.com,所谓的正规主机名,也为ANAME
* http://tools.ietf.org/html/rfc1034并没有明文规定禁止CNAME指向CNAME。只是建议避免这种情况

这是为什么直指访问IP地址得不到blogger的内容呢? 难道是因为blog.jessinio.info是一个CNAME的问题?其实这一想法是被pass掉的。browser发出的請求怎么可能包括CNAME之类的信息的嘛

browser的请求明显还包括一个信息: Host

为了求证这一点, 自己写了段请求代码, 因为为了看清請求的具体情况, 代码是直接使用socket写的:
import socket
import re
import os
import sys 


if __name__ == "__main__":
  blog_ip = socket.gethostbyname("blog.jessinio.info")
  s = socket.socket()
  s.connect((blog_ip,80))
  s.sendall("""GET / HTTP/1.1
Host: blog.jessinio.info
User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.8) Gecko/20100214 Ubuntu/9.10 (karmic) Firefox/3.5.8
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7



""")
  data = ''
  while True:
    retval = s.recv(1024)
    if not retval: break
    data += retval
  s.close()
  # 第一个空行开始的内容都是browser需要的内容
  d = re.search(r'(\r\n|\n)\1(.*)', data, re.S).group(2)
  f = open("/tmp/blog.gz", "w")
  f.write(d)
  f.close()
* 得到的"/tmp/blog.gz"是一个gzip文件, 可以使用gunzip解压一看就明白

从代码可以看到, 访问IP之所以不行, 是因为ghs.google.com需要得到請求中Host参数

注意: 要想使用自己的域名去访问blogger是需要在blogger后台里配置的, 这一配置才能使ghs.google.com根据不同的Host值得到不出的blogger帐号