想找个简单的代码来看,学习代码的架构设计,就找到了busybox。先从最早的版本开始看。
whoami命令是获取当前终端的用户名。/etc/passwd文件存储了所有用户名的清单。要注意的是/etc存储的配置文件大多是系统级的配置文件。而whoami想要达到目的,就需要与/etc/passwd文件打交道。
首先来看whoami.c的主体程序:
1 extern int whoami_main(int argc, char **argv)
2 {
3 char user[9];
4 uid_t uid = geteuid();
5
6 if (argc > 1)
7 show_usage();
8
9 my_getpwuid(user, uid);
10 if (*user) {
11 puts(user);
12 return EXIT_SUCCESS;
13 }
14 error_msg_and_die("cannot find username for UID %u", (unsigned) uid);
15 }
首先通过geteuid()系统调用获得uid,然后,通过my_getpwuid(user,uid)获得username。
再看my_getpwuid函数。
1 void my_getpwuid(char *name, long uid)
2 {
3 struct passwd *myuser;
4
5 myuser = getpwuid(uid);
6 if (myuser==NULL)
7 sprintf(name, "%-8ld ", (long)uid);
8 else
9 strcpy(name, myuser->pw_name);
10 }
/etc/passwd中的每条记录都有相同的格式:
name:password:uid:gid:comment:home:shell
每项的具体内容可以查看这里。
struct passwd 结构就对应了这个记录:
1 struct passwd
2 {
3 char *pw_name; /* Username. */
4 char *pw_passwd; /* Password. */
5 uid_t pw_uid; /* User ID. */
6 gid_t pw_gid; /* Group ID. */
7 char *pw_gecos; /* Real name. */
8 char *pw_dir; /* Home directory. */
9 char *pw_shell; /* Shell program. */
10 };
我们无法单独得到username,所有,我们必须先得到struct passwd结构。my_getpwuid函数中的getpwuid函数就实现这个功能。
1 struct passwd *getpwuid(uid_t uid)
2 {
3 int passwd_fd;
4 struct passwd *passwd;
5
6 if ((passwd_fd = open("/etc/passwd", O_RDONLY)) < 0)
7 return NULL;
8
9 while ((passwd = __getpwent(passwd_fd)) != NULL)
10 if (passwd->pw_uid == uid) {
11 close(passwd_fd);
12 return passwd;
13 }
14
15 close(passwd_fd);
16 return NULL;
17 }
第9行while不断读取/etc/passwd中的条目,找到目标就return。进入到__getpwent中。
1 if ((line_len = read(pwd_fd, line_buff, PWD_BUFFER_SIZE)) <= 0) //
2 return NULL; //当read之后,pwd_fd所指向的文件的offet到了line_buff的末尾。这对后面的处理很重要。
3 field_begin = strchr(line_buff, '\n');
4 if (field_begin != NULL)
5 lseek(pwd_fd, (long) (1 + field_begin - (line_buff + line_len)),
6 SEEK_CUR); //找到一个'\n'后,就需要进行parse。然后就要将offset调到当前'\n'的后一位,就是这个语句的作用了。
7 else {
8
9 do {
10 if ((line_len = read(pwd_fd, line_buff, PWD_BUFFER_SIZE)) <= 0)
11 return NULL;
12 } while (!(field_begin = strchr(line_buff, '\n')));
13 lseek(pwd_fd, (long) (field_begin - line_buff) - line_len + 1,
14 SEEK_CUR);
15 goto restart;
16 }
得到一个条目的首地址line_buff后,就可以parse了。过程很简单。
1 for (i = 0; i < 7; i++) {
2 switch (i) {
3 case 0:
4 passwd.pw_name = field_begin;
5 break;
6 case 1:
7 passwd.pw_passwd = field_begin;
8 break;
9 case 2:
10 uid_ptr = field_begin;
11 break;
12 case 3:
13 gid_ptr = field_begin;
14 break;
15 case 4:
16 passwd.pw_gecos = field_begin;
17 break;
18 case 5:
19 passwd.pw_dir = field_begin;
20 break;
21 case 6:
22 passwd.pw_shell = field_begin;
23 break;
24 }
25 if (i < 6) {
26 field_begin = strchr(field_begin, ':');
27 if (field_begin == NULL)
28 goto restart;
29 *field_begin++ = '\0';
30 }
31}
找到符合uid的条目后,my_getpwuid函数得到username,输出就可以了。