想找個簡單的代碼來看,學習代碼的架構設計,就找到了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,輸出就可以了。