Sunday, July 18, 2010

IO笔记3

read(2)和write(2)都有一个特点: 期待被处理的数据与已经被处理的数据量可以不一致.
比如:
char buffer[5000];
size_t count = read(1, buffer, 4096);

调用read读4096个bytes, 但是是不是真的已经读了4096个bytes, 这是没有必然要求的.

socket的I/O是最明显的.

如果不一致, 就要求程序员去处理. 不过, 这都是常事了, 都有满足这样需求的函数.

但是, 这就需要程序员可以自己分清这两种情况的函数.

不要认为sys.stdin.read(4096)和os.read(sys.stdin.fileno(), 4096)是一样的.

$ (echo -e "12345\n"; sleep 10) | python -c '
import sys
print sys.stdin.read(4096)'

上面的python程序是被block了10秒才运行print的. 但是下面的是马上收到数据的:
$ (echo -e "12345\n"; sleep 10) | python -c '
import sys
import os
print os.read(sys.stdin.fileno(), 4096)'

C的standard I/O是一组保证可以返回指定数据(量)的函数

standard I/O增加了buffer, 以减少read, write的次数. 原则就是每次调用read的时候读入BUF_SIZE的数据到buffer空间中, 下面是read函数的原型:

ssize_t read(int fd, void *buf, size_t count);

count是会根据file descriptor的不同变化, 如:
1. terminate device时为1024
2. 不是terminate device时为是page size, 或者程序员使用setvbuf指定

standard I/O有三个种非格式化函数:
1. character-at-a-time I/O
2. line-at-a-time I/O
3. direct I/O

无论使用哪种函数, 它都是如下的方式:

read(fd, buffer_ptr, BUF_SIZE);

那怕你只是想使用fgetc得到一个字符. 都是调用上面的函数. 一般情况BUF_SIZE >= 1024.

standard I/O除了缓存外. 还保证了返回的数据是想得到的, 如:
1. character-at-a-time I/O时, 保证返回一个字符
2. line-at-a-time I/O时, 保证返回一行数据, 以NULL结尾.
3. direct I/O时, 保证返回指定的数据结构.

否则, 会多次调用上面的read函数, 直到取得的数据满足要求, 过程中会出来下面的情况:
1. block;
2. EOF;
3. Error

Friday, July 16, 2010

IO笔记2

两层buffer的存在

 23 #include <stdio.h>
 24
 25 int main(int argc, char *argv[]){
 26     int retval ;
 27     while(1){
 28         char c[BUFSIZ];
 29         retval = read(0, c, 1);
 30         printf("%c\n", *c);
 31     }
 32     return 0;
 33 }

上面的代码, 用户输入一次, read函数被调用N次(数据的长度)。也就是说用户输入的数据已经被缓存了。
再如下:

 23 #include <stdio.h>
 24
 25 int main(int argc, char *argv[]){
 26     int retval ;
 27     int i;
 28     int c;
 29     printf("%d\n", BUFSIZ);
 30     for(i = 0; i < BUFSIZ / 2; i++){
 31         c = fgetc(stdin);
 32     }
 33     return 0;
 34 }

上面的代码fgetc被调用N次(数据的长度), 但是read的调用情况如下:
1. 当stdin不是terminate device时, 被调用一次, read的count参数为4096
2. 当stdin是terminate device时, 被调用两次, read的count参数为1024
* 至少怎么得到这种数据, 答案是使用strace凶器。

libc会根据stdin是什么使用setvbuf对FILE对象设置buffer.

从上面两个例子可以知道:
1. 使用standard I/O从terminate device读数据时, 其实经过了两个buffer机制。
2. standard I/O读不同file descriptor时的buffer大小会变化。 加上从APUE中知道的,情况会是这样
 * 当stdin, stdout是terminate device时, standard I/O会使用行缓存, buffer大小为1024.
 * 当stdin, stdout不是terminate device时, standard I/O会使用full buffer, buffer大小为4096.

好, 知道这两种buffer机制与buffer大小了, 移步看这一篇文章会有很好的收获:
 * http://www.pixelbeat.org/programming/stdio_buffering/

这文章从一个例子开始, 如下:
# tail -f access.log | cut -d' '|uniq
为什么上面的命令没有输出? 呵呵, 其中就是IO缓存的机制.

预读数据, 减少read, write

使用standard I/O都是会预读一定数量的数据, 用于减少调用read, write的次数.
使用standard I/O的好处就是程序员无需操心这个预读数据的大小, 只需要知道它是预读的即可.
如果不知道会预读的话, 上面的URL的例子就无法明白.


Sunday, July 11, 2010

IO笔记

IO部分总是很乱。不知道是因为API多还是其它原因:
 0. 阻塞的存在
 1. glibc的section3手册和linux的section2有同名函数。
 2. posix, ISO都有要求
 3. terminal IO与其它IO有不同的表现
 4. IO缓存的机制
 5. 还有众多古老的名词....

EOF        
EOF是什么? 什么时候才会产生? 还是和ctrl_c一样是signal ?

如下代码的运行結果是 -1
 19 #include <stdio.h>
 20
 21 int
 22 main(int argc, char *argv[])
 23 {
 24     printf("%d\n", EOF);
 25     return 0;
 26     
 27 } 

表示EOF是 -1 ?

当某一次使用 ssize_t read(int fd, void *buf, size_t count); 函数从一个file descriptor中读数据时(count>0), 函数得到0个字节表示读到文件的尾部了. 这时read函数返回0.

可读端的pipe关闭后, 只读端pipe在读完后会产生EOF:
  2 import os
  3 import sys
  4 import time
  5
  6 read_end, write_end = os.pipe()
  7
  8 pid = os.fork()
  9 if pid < 0:
 10     print "Error"
 11     sys.exit(1)
 12 if pid == 0:
 13     # parent
 14     os.close(write_end)
 15     try_time = 1
 16     # 因为无法得到child process会发多少数据, 只能使用死循环去读数据
 17     while True:
 18         print "%s time(s) call read" % try_time
 19         content = os.read(read_end, 1)
 20         if not content:
 21             break
 22         os.write(1, content)
 23         try_time += 1
 24     sys.exit(0)
 25
 26 else:
 27     # child
 28     os.close(read_end)
 29     os.write(write_end, "#" * 100)
 30     # 关了parent就能读到EOF
 31     os.close(write_end)
 32     sys.exit(0)



文件系统中的文件是有固定大小(某一状态), 但是在terminal IO里,是没有一个固定的尾部的, 需要输入者指定什么时候才是end-of-file, 所以, ctrl_D出现了. ctrl_D是一个特殊控制符。被tty的驱动处理(默认情况下用户程序不能从read函数得到, 除非要求tty不处理)

在了解ctrl_D前, 应该先要了解IO的缓存机制.

standard IO缓存 与 底层IO缓存
在此这前, 我对getchar这个函数不解, 其实就是不解如下的代码:
 19 #include <stdio.h>
 20
 21 int main(int argc, char *argv[]){
 22     char c ;
 23     while(c = getchar() ){
 24        printf("%c\n", c);
 25     }  
 26 }  

不要以为简单调用getchar函数可以实现vim这种交互.
当调用read去读stdin的数据时, 是行缓存的.上面的代码在第一次调用getchar时把一行的数据读到buffer里(回车键返回), 下次再调用getchar时会从buffer里取. buffer没有数据后再等待用户输入.

标准IO与系统底层IO这两套IO需要分开. 不然会出现事与愿违的情况, 如:
使用setbuf对FILE对象进行了设置后, 然后使用调用printf操作. 結果没有出现想要的设置效果。例如:

 23 #include <stdio.h>
 24 #include <string.h>
 25 #include <unistd.h>
 26
 27
 28 int main(int argc, char *argv[]){
 29     //
 30     FILE *input =  fdopen(1, "r");
 31     FILE *output =  fdopen(1, "w");
 32     setbuf(input, NULL);
 33     setbuf(output, NULL);
 34     char c ;
 35
 36     printf("Hello World");
 37     sleep(10);
 38 }

上面的printf的内容是过了10秒后才打印的, 明显, setbuf没有起作用。 但是如果修改为如下就不同了:
 23 #include <stdio.h>
 24 #include <string.h>
 25 #include <unistd.h>
 26
 27
 28 int main(int argc, char *argv[]){
 29     //
 30     FILE *input =  fdopen(1, "r");
 31     FILE *output =  fdopen(1, "w");
 32     setbuf(input, NULL);
 33     setbuf(output, NULL);
 34     char c ;
 35
 36     fprintf(output, "Hello World");
 37     sleep(10);
 38 }
上面代码的区别在于:printf使用了stdin这个FILE对象(默认是line buffer), fprintf使用了指定的FILE对象。standard IO的缓存是通过修改FILE对象实现的。

read和write会不会缓存, 缓存机制是什么? 这与read, write操作的对象有很大的关系, 例如:

 28 int main(int argc, char *argv[]){
 29
 30     while(1){
 31         char c;
 32         (void *)read(0, &c, 1);
 33         (void *)write(1, &c, 1);
 34     }
 35 }
上面虽然使用了read, write, 还是用户输入还是行缓存的, 无法像vim一样交互操作。这时要想非行缓存, 需要操作到tty的缓存机制。 如下的一段python代码设置了tty的缓存方式:

  1 #coding:utf-8
  2 import os
  3 import sys
  4 import termios
  5
  6 STDIN_FILENO = sys.stdin.fileno()
  7
  8 old_attr = termios.tcgetattr(STDIN_FILENO)
  9 new_attr = termios.tcgetattr(STDIN_FILENO)
 10
 11 new_attr[3] &= ~ (termios.ICANON | termios.ECHO)
 12 termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, new_attr)
 13 try:
 14     print '请使用vim的移动键'
 15     while True:
 16         c= os.read(STDIN_FILENO, 1)
 17         if c == 'j':
 18             print "下"
 19         elif c == 'k':
 20             print "上"
 21         elif c == 'h':
 22             print '左'
 23         elif c == 'l':
 24             print '右'
 25 except KeyboardInterrupt, e:
 26     termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, old_attr)


回到ctrl_D的问题
在terminal IO里, 因为默认是使用缓存的, 赋于ctrl_D两种功能:
 1. 相当于flush操作.  即时把输入的数据返回给read函数, 不用等待'\n'的出现。
 2. 让tty返回0字节给read函数. 表示EOF。 在单独输入ctrl_D后产生这种效果

控制符        
控制符都是tty驱动提供的一种方便功能, 有少量的控制符会产生signal的:
^Z和^C
还有少量的是无法修改的:
\r和\n

如下代码把“a“作为backspace功能:

  1 #coding:utf-8
  2 import os
  3 import sys
  4 import termios
  5
  6 STDIN_FILENO = sys.stdin.fileno()
  7
  8 old_attr = termios.tcgetattr(STDIN_FILENO)
  9 new_attr = termios.tcgetattr(STDIN_FILENO)
 10
 11 new_attr[6][termios.VERASE] = 0x61
 12 termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, new_attr)
 13 try:
 14     print "请使用a键删除数据"
 15     os.read(0,100)
 16 except KeyboardInterrupt, e:
 17     termios.tcsetattr(STDIN_FILENO, termios.TCSADRAIN, old_attr)


Wednesday, June 23, 2010

无线网络配置

总是有一种怪感觉: 不能太依赖GUI程序。
在我的记忆中, 还是存在着一种场面:系统无法正常进入X而无法正常使用linux的痛苦记忆。总是感觉GUI是学习使用linux的拦路虎。
所以一般情况下, 我都是CLI程序行先的。这也是防止出现我很不喜欢的感觉。

网络照样是CLI行先。之前我的router是配置成WEP的, 都是使用wireless-tools工具集可以很好处理。 但是慢慢地, 这种加密网络已经被淘汰了。 WEP逐渐出的时, WPA流行起来,同时linux也产生了新的一套wireless配置机制:nl80211.  这是为什么wireless-tools工具集无法使用WPA网络的原因
使用WPA无线现在一般是使用wpa_supplicant工具

使用CLI工具最烦就是需要多手动。 复杂的工具还需要配置文件。

其实这都是一次性的, 因为有脚本的存在。

我也是一个懒人~~~谁都不想每次都搞十几钟才上得了网。命令还是比较好办, 就麻烦的是配置文件, wpa_supplicant的配置文件也不是省油的。

特别是在没网络,不能google的时候更是无奈。 对!这种“无奈”是我最不喜欢看到的! 我很讨厌这种感觉。

相信解决的方法是一定会存在的~~~~

“就地出材”是一条出路, 即:不联网也可配置wpa_supplicant!
copy是方法, How ? 从wpa_supplicant的man里copy出可使用的配置文件来。
在man 5 wpa_supplicant里看到: Catch all example that allows more or less all configuration modes.

这个问题已经解决了, 不存在是否有google。也不存在是否有Networking-manager。 (可以直奔可爱的gentoo世界)
把配置copy出来, 按实现情况修改, 去掉不用的, psk使用wpa_passphrase得到。 一切都是没有问题的
* 如果这一步无法做到, 还是去使用GUI吧, 或者是思考一下问题出在哪里。

============= 华丽的分隔线 ===================

下面是N久在docs里存放的文字, 随便一起放出:

计算机的网络是最常见的配置了。来到北京这边。朋友的宿舍里的网络和广东的不一样:
1. 一个宿舍只有几台计算机上网。
2. 可以上网的机器都被登记MAC地址。
3. 可以上网的机器都分有固定的IP, netmask, DNS, gateway
4. 需要web登陆认证方可使用网络。
5. WEP方式

其实就是MAC的限制 + web登陆认证。

还是很管用的。 至少市场卖的路由没有针对这一系统的web登陆。

为了自己的机器可以上网。只需要满足上面的条件即可。 
针对于有线的情况:

ifconfig eth1 hw ether 00:1e:65:18:e2:a8
ifconfig eth1 10.3.52.132 netmask 255.255.255.0
route add default gw 10.3.52.1

然后使用browser访问web认证即可。

有线的倒很清楚怎么解决它。 无线呢? 也很简单

针对于无线的情况(是WEP网络):
$ sudo ifconfig wlan0 hw ether xxxxxxxx
$ sudo iwlist wlan0 scan
可以得到ssid名~~
$ sudo iwconfig wlan0 ssid 'who' key 'password'
$ sudo route add default gw 10.3.52.1










Monday, June 21, 2010

由setrlimit引发的学习

 
POSIX标准中有这样一个函数: sysconf(3).  自己写了小段代码:

 19 #include <unistd.h>
 20 #include <stdio.h>
 21
 22 int main(int argc, char *argv[]){
 23     int fd_max_number = sysconf(_SC_OPEN_MAX);
 24     printf("%d\n", fd_max_number);
 25
 26 }

使用strace可以知道它其实是调用了getrlimit(2),
什么limit? 全称为 resource limit. 这是kernel分给每个进程独立的一组数据。代表了进程可以使用的最大resource上限。具体有哪些, 可以见getrlimit(2)的manual

kernel也有一堆limit参数, 使用sysctl(2)来修改, kernel的limit应该被称为"limit of limits"。

下面的有关于文件描述符的limit:
 * http://www.karakas-online.de/forum/viewtopic.php?t=9834
 * 其中提到一点很有意义:file descriptor与open file

在了解setrlimit(2)配置file descriptor时, 有一个文件引起了我的高度注意:
/etc/security/limits.conf

疑问:
1. setrlimit是system call, 为什么会使用这样一个配置文件的?
2. limits.conf是pam_limits module的配置文件, 如果用户不加载此module时,kernel的setrlimit又是处理的?

不解的情况下, 在kernel的源代码堆里纠结时, 发现了一个常常会看到的一个词:SELinux
FAQ了一把, 得知SELinux是在kernel里加入复杂的access control。比如role-based access control

疑问又来了: Linux是一个可高度裁剪(scale)的系统, 如何做到去掉Linux Security Modules可以不影响现有的system call的呢?

这其中的机制比较吸引我。 google了一把, 得到一篇好文, 分享:
 * http://www.ibm.com/developerworks/linux/library/l-selinux/





Wednesday, June 9, 2010

系统里的hostname

1. 修改host name需要reboot吗?
2. host name与/etc/hosts有什么关系呢?
3. gethostname为什么不受/etc/nsswitch.conf的配置?

这三个问题在不了解host name是什么的时候, 比较纠结.

是否要reboot这个问题是最直接让我产生dig这个host name的动力, 还有就是工作中, 碰到一台机器无法使用sudo命令, 于是花时间学习学习.

平时看hostname(3)时, 发现hostname(3)与/etc/nsswitch.conf扯在一起, 这使host name与DNS中的记录纠结在一起. 更是希望了解之间的关系

在找资料的过程中, 本人发现了一个很有意思的方法:
 * Linux kernel与GNU分开.
什么意思呢? 分界是为了把不同时期的产物, 概念之类的分开. 比如: 当你认host name是kernel的一个变量时, 那么它就与DNS中的记录有明显的不同.

gethostname是system call, 它不是glibc的东西, 所以它与glibc中的DNS查询机制(/etc/nsswitch.conf)是扯不上关系的. gethostbyname, gethostbyaddr都是glibc的东西

就算没有glibc的存在, kernel中的host name照样存在!

按上面的方法, 已经可以很清楚地知道host name与/etc/hosts, /etc/nsswitch.conf是没有关系的.

linux kernel提供了实时修改kernel参数的方法, 是无需要reboot内核就可以生效的.  如sysctl(2), 也有CLI的sysctl(3). 也可以修改/proc/sys/kernel下的文件.

在实际中, GNU软件又常常会使用到host name!

在工作中碰到一个这样的问题: 每当运行sudo时shell就hang住!
它被block了吗?
于是使用strace一查, sudo是在等socket的IO. 发现这台机器的DNS請求是发不出去的

为什么sudo需要使用网络呢?
这其实就是程序期望得到FQDN格式的host name的結果.
得到FQDN格式的host name就使得host  name与DNS查询纠结在一起.

通过学习hostname命令的代码, 可以得到hostname -f其实是等同于下面的代码
#include <unistd.h>
#include <stdio.h>
#include <netdb.h>

int main(int argc, char *argv[]){
    char hostname[64];
    if ( gethostname( hostname, sizeof(hostname) ) != 0){
        perror("1");
    }   
    struct hostent * host_struct;
    if ( (host_struct = gethostbyname( hostname )) == NULL ){
        perror("2");
    }   
    printf("%s\n", host_struct->h_name);

}

可以得到这样的信息: 得到FQDN格式的host name, 是需要使用到
1. host name
2. DNS
这就与linux下DNS查询顺序策略有关系(/etc/nsswitch.conf)


python里有一段很明了的代码(来自socket.py):
def getfqdn(name=''):
    """Get fully qualified domain name from name.

    An empty argument is interpreted as meaning the local host.

    First the hostname returned by gethostbyaddr() is checked, then
    possibly existing aliases. In case no FQDN is available, hostname
    from gethostname() is returned.
    """
    name = name.strip()
    if not name or name == '0.0.0.0':
        name = gethostname()
    try:
        hostname, aliases, ipaddrs = gethostbyaddr(name)
    except error:
        pass
    else:
        aliases.insert(0, hostname)
        for name in aliases:
            if '.' in name:
                break
        else:
            name = hostname
    return name

这样还可以解开一个疑问: 为什么/etc/hosts里常常会出现hostname和它的FQDN记录
增加这样的信息在这里, 查hostname的FQDN时就无须通过网络得到, 当然, 的确有需求的话, 也是可以的, 如果牛B的话, 就每台机器有一个全球唯一的域名也没有人说


Sunday, May 30, 2010

帐号

1. 问题的来源
一直以来, 都有一种错误的认为: 使用useradd增加帐号和配置好ssh-key就可以使用此帐号登陆 .
实现上不如此. 这是需要usePAM的支持. PAM日后再细细学习, 现在先学习学习一般认证

于是, 把login和su命令的代码翻出来看看. 它们都使用如下的方式:

login(不扯PAM)的代码:
 798     pp = getpass(_("Password: "));
 799
 800 #  ifdef CRYPTOCARD
 801     if (strncmp(pp, "CRYPTO", 6) == 0) {
 802         if (pwd && cryptocard()) break;
 803     }
 804 #  endif /* CRYPTOCARD */
 805
 806     p = crypt(pp, salt);
 807     setpriority(PRIO_PROCESS, 0, 0);
.......
 835     if (pwd && !strcmp(p, pwd->pw_passwd))
 836       break;
 837
 838     printf(_("Login incorrect\n"));

su的代码:
265   unencrypted = getpass (_("Password:"));
266   if (!unencrypted)
267     {
268       error (0, 0, _("getpass: cannot open /dev/tty"));
269       return false;
270     }
271   encrypted = crypt (unencrypted, correct);
272   memset (unencrypted, 0, strlen (unencrypted));
273   return STREQ (encrypted, correct);
274 }

都是:
1. 使用了crypt函数
2. 最后都是字符串比较
3. 需要/etc/shadow里的加密后字符串
* 比红色部分可以看出getpass是在stdin被定重向后会报错(hard wire型的程序), 因为它使用了不回显功能(只有tty才有, pipe没有)

在python官网里看到可爱的python代码是这样的:
crypt.crypt(cleartext, cryptedpasswd) == cryptedpasswd

* 可以知道crypt函数的第二个参数可以是salt, 也可以不是salt, 而是加密后的字符串
* 自来http://docs.python.org/library/crypt.html?highlight=crypt#crypt.crypt



2. Lock 与 Unlock 是怎么一个概念?

在man 5 shadow得到的信息:

If the password field contains some string that is not valid result of crypt(3), for instance ! or *, the user will not be able to use a unix password to log in, subject to pam(7).

从上面的C代码和crypt函数的特性可以猜测: Lock无疑就是让方程两边永远不相等, 代码没有其它测试的必要!

具体到ssh的实例, 当使用一指定用户登陆ssh server时, server并不去分别Lock与不Lock的帐号,
虽然没有看过sshd的代码, 但是从表现上: 不会马上打印: Lock user或者马上disconnect, 如使用全世界都公认的halt默认帐号也是提示输入密码:
$ ssh halt@localhost
halt@localhost's password:

3. 其它
su切换用户
FAIL : 以为切换什么用户都是输入root密码
大概看了一把su的代码, 才知道不是这样的~~~~
知道了下面的真相:
1. 需要输入切换到用户的密码
2. restricted shell : 凡不在/etc/shells里登陆的shell都是受限shell. 为了安全的

nologin
平时nologin默认打印信息是: This account is currently not available.
你也是可以修改的: /etc/nologin.txt, 例如下面的例子:

$ sudo su rpc
我顶~ 乱来~ 去问你妈要密码, 不然见一次骂一次
 * 这个例子有点geek~