《UNIX環境高階程式設計》 第6章 系統資料檔案和資訊
系統資料檔案和資訊
6.1 引言
UNIX系統的正常運作需要使用大量的與系統有關的資料檔案,例如口令檔案/etc/passwd和組檔案/etc/group就是經常被多個程式頻繁使用的兩個檔案。每次使用者登入系統,以及每次執行ls -l命令都需要使用口令檔案。
由於歷史原因,這些資料檔案都是ASCII檔案檔案,並使用標準IO庫讀取這些檔案。但是對於較大的系統,順序掃描口令檔案很花費時間,我們需要能夠以非ASCII檔案格式存放這些檔案,但仍向使用其他檔案格式的應用程式提供介面。對於這些資料檔案(系統資料檔案)的可移植介面是本章的主題。本章還包括了系統標識函式、時間和日期函式。
6.2 口令檔案
UNIX系統口令檔案包含了以下各欄位,這些欄位包含在pwd.h中的passwd結構中。
說明 | struct passwd成員 |
---|---|
使用者名稱 | char *pw_name |
加密口令 | char *pw_passwd |
數值使用者ID | uid_t pw_uid |
數值組ID | gid_t pw_gid |
註釋欄位 | char *pw_gecos |
初始工作目錄 | char *pw_dir |
初始shell | char *pw_shell |
使用者訪問類 | char *pw_class |
下次更改口令時間 | time_t pw_change |
賬戶有效期時間 | time_t pw_expire |
由於歷史原因,口令檔案/etc/passwd是一個ASCII檔案。每行包含以上表格欄位,欄位之間用冒號分隔。
通過UID和username檢視口令檔案相關項:
#include <pwd.h>
struct passwd *getpwuid(uid_t uid);//通過使用者ID獲取口令檔案結構
struct passwd *getpwnam(char *name);//通過使用者名稱獲得口令檔案結構
檢視整個口令檔案:
#include <pwd.h>
struct passwd *getpwent(void);//獲得一條口令檔案記錄。
void setpwent(void);//反繞口令檔案,用於回到第一條。
void endpwend(void);//關閉口令檔案。
example:實現getpwname函式:
#include <pwd.h>
struct passwd *
getpwnam(const char *name)
{
struct passwd *ptr;
setpwent();//反繞口令檔案,類似於磁帶機的操作。
while((ptr=getpwent())!=NULL)
{
if(strcmp(name,ptr->pw_name)==0)//比較是否是想要的使用者名稱
{
break;
}
}
endpwent();//關閉口令檔案
return(ptr);
}
6.3 陰影口令
加密口令是經單向加密演算法處理過的使用者口令副本。因為此演算法是單向的,所以不能從加密口令猜測到原來的口令。但是可以對口令進行猜測,然後與使用者的加密口令比較來猜測到使用者口令。
為了使有這樣企圖的人難以獲得原始資料(加密口令),系統通常將加密口令儲存在一個稱為陰影口令(shadow password)的檔案中(/etc/shadow)。這個檔案一般使用者不能讀取,只有少數幾個程式需要訪問加密口令,如login和passwd,這些程式常常設定使用者ID為root。有了陰影口令後普通口令檔案/etc/passwd就可以由使用者自由讀取了。
/etc/shadow中的欄位:
說明 | struct spwd成員 |
---|---|
使用者名稱 | char *sp_namp |
加密口令 | char *sp_pwdp |
上次更改口令以來經過的時間 | int sp_lstchg |
經過多少天后執行更改 | int sp_min |
要求更改尚餘天數 | int sp_max |
超期告警天數 | int sp_warn |
賬戶不活動之前尚餘天數 | int sp_inact |
賬戶超期天數 | int sp_expire |
保留 | unsigend int sp_flag |
類似於口令檔案,有一組訪問/etc/shadow的函式:
#include <shadow.h>
struct spwd *getspnam(const char *name);//通過使用者名稱取得shadow結構
struct spwd *getspent(void);//獲取單條記錄
void setspent(void);//反繞,回到首條記錄
void endspent(void);//關閉shadow檔案
6.4 組檔案
UNIX組檔案包含了以下欄位,這些欄位的定義在grp.h檔案中:
說明 | struct group成員 |
---|---|
組名 | char *gr_name |
加密口令 | char *gr_passwd |
數值組ID | int gr_gid |
指向各使用者名稱指標的陣列 | char **gr_mem |
欄位gr_mem是一個數組指標,其中每個指標指向一個屬於該組的使用者名稱,該陣列以NULL指標結尾。
下面兩個檔案通過組名和陣列組ID來獲得組結構資訊,它們都是返回一個靜態變數的指標,每次呼叫時都會重新該靜態變數:
#include <grp.h>
struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);
如果要歷遍整個組檔案,可以使用類似歷遍口令檔案的方法:
#include <grp.h>
struct group *getgrent(void); //從組檔案中讀取下一個記錄
void setgrent(void); //開啟並反繞,回到第一條記錄
void endgrent(void); //關閉組檔案
6.5 附屬組ID
很多時候,一個程序不僅可以屬於口令檔案記錄項中組ID所對應的組,也可以屬於多至16個(具體數量定義在常量NGROUP_MAX中)另外的組。因此訪問許可權的檢查規則改變為:不僅將程序的有效組ID與檔案的組ID相比較,而且也將所有附屬組ID與檔案組ID相比較。
使用附屬組ID的優點是程序不需要顯式地程序更改組,一個使用者會參與多個專案,因此也就要同時屬於多個組。獲得附屬組ID,使用下列3個函式:
#include <unistd.h>
int getgroups(int gidsetsize ,gid_t grouplist[]);
#include <grp.h>
int setgroups(int ngroups,const gid_t grouplist[]);
int initgroups(const char *username ,gid_t basegid);
getgroups將程序所屬使用者的各個附屬組ID填寫到陣列grouplist中,最多填入個數為gissetsize,實際數量有返回值確定。如果gidsetsize為0,則返回實際附屬組ID數量,不修改grouplist。
setgroups使用者設定組ID,grouplist是組ID陣列,ngroups是數量,但不能超過NGROUP_MAX規定的上限。
(以下很重要,比較難理解)
initgroups:讀取整個組檔案(/etc/group)根據username確定屬於哪幾個組,再將這些組ID和basegid一起存到程序附屬組ID表中,完成初始化附屬組ID表的工作。
6.6 實現區別
這裡比較了4中不同UNIX系統平臺下的系統檔案儲存情況,包括FreeBSD 8.0 、Linux 3.2.0 、Mac OS X 10.6.8 和Solaris 10。我們只關心Linux部分。
資訊 | 路徑 |
---|---|
賬戶資訊 | /etc/passwd |
加密口令 | /etc/shadow |
組資訊 | /etc/group |
6.7 其他資料檔案
處理口令檔案和組檔案這兩個系統資料檔案,在日常操作中UNIX系統還有很多其他檔案。
一般情況下,每個資料檔案至少有3個函式:
(1)get函式:讀取下一個記錄,如果需要還會開啟該檔案。這種函式一般返回一個結構指標。當達到檔案尾時返回空指標。大多數get函式返回一個靜態儲存類結構的指標,如果要儲存該其內容,則需要複製它。
(2)set函式:開啟相應的資料檔案(如果尚未開啟),然後反繞該檔案。
(3)end函式:關閉相應資料檔案。
另外,如果資料檔案支援某種形式的搜尋,則也提供搜尋指定欄位的記錄的方法。
以下列出常用的系統引數資料檔案,處理3個處理函式外,另外列出了附加的搜尋函式。
說明 | 資料檔案 | 標頭檔案 | 結構 | 附加搜尋函式 |
---|---|---|---|---|
口令檔案 | /etc/passwd | passwd | getpwnam、getpwuid | |
組檔案 | /etc/group | group | getgrnam、getgrgid | |
加密口令檔案 | /etc/shadow | spwd | getspnam | |
主機 | /etc/hosts | hostent | getnameinfo、getaddrinfo | |
網路 | /etc/networks | netent | getnetbyname、getnetbyaddr | |
協議 | /etc/prptocols | protoent | getprotobyname、getprotobynumber | |
服務 | /etc/services | servent | getservbyname、getservbyport |
6.8 登入賬戶記錄
大多數UNIX系統都提供下列兩個資料檔案:utmp檔案(/var/run/utmp)記錄當前登入到系統的各個使用者;wtmp檔案(/var/log/wtmp)跟蹤各個登入和登出事件。
每次寫入這兩個檔案中的是包含下列結構的一個二進位制記錄:
struct utmp {
char ut_line[8]; //tty line
char ut_name[8]; //login name
long ut_time; //seconds since epoch
};
登入時,login填充此型別結構,然後將其寫入到utmp檔案中,同時也添寫到wtmp檔案中。
登出時,init程序將utmp檔案中的相應記錄擦除,並將一個新記錄寫到wtmp中。在wtmp檔案的登出記錄中,ut_name欄位清除為0。在系統啟動、更改時間等操作前後,都會在wtmp檔案中追加特殊記錄項。
**who程式讀取utmp檔案,並以可讀格式列印其內容。
last程式讀取wtmp檔案並列印記錄。**
6.9 系統標識
POSIX.1定義了uname函式,它返回主機和作業系統有關想資訊。
#include <sys/utsname.h>
int uname(struct utsname *name);
POSIX.1定義了utsname結構體的最小結構,具體的實現會比這更多:
struct utsname{
char sysname[]; //name of the operating system
char nodename[]; //name of this node,網路上的主機
char release[]; //current release of the operating system
char version[]; //current version of this release
char machine[]; //name of hardware type
};
其中每個成員都是字串,其最大長度受系統限制。
hostname函式只返回主機名,該名字通常就是TCP/IP網路上主機的名字(通常是該主機在網路上的完整域名)。
#include <unistd.h>
int gethostname(char *name,int namelen);
shell中hostname命令可以用來獲取或設定主機名。(超級使用者用一個類似sethostname來設定主機名)主機名通常在系統自舉時設定,它由/etc/rc或init取自一個啟動檔案。
6.10 時間和日期例程
UNIX核心提供的基本事件服務是計算從協調世界時間(coordinated universal time,UTC)1970年1月1日00:00:00這一特點時間以來經過的秒數。
這種秒數是以time_t型別定義的,稱為日曆時間。
time函式返回當前時間和日期:
#include <time.h>
time_t time(time_t *calptr); //返回時間值,若引數非空也填充引數
現在作業系統支援多個系統時鐘,時鐘型別通過clockid_t型別來標識:
時鐘型別識別符號 | 選項 | 說明 |
---|---|---|
CLOCK_REALTIME | 實時系統時間 | |
CLOCK_MONOTONIC | _POSIX_CLOCK_MONOTONIC | 不帶負跳數的實時系統時間 |
CLOCK_PROCESS_CPUTIME_ID | _POSIX_CPUTIME | 呼叫程序的CPU時間 |
CLOCK_THREAD_CPUTIME_ID | _POSIX_THREAD_CPUTIME | 呼叫執行緒的CPU時間 |
clock_gettime函式用於獲取指定的時鐘時間,返回到timespec結構中。
#include <sys/time.h>
int clock_gettime(clockid_t clock_id,struct timespec *tsp);
UNIX還有多個時間函式,就不一一看了,可以查閱UNIX環境高階程式設計第6章10節。
6.11 小結
- 所有的UNIX系統都是用口令檔案合組檔案,我們說明了這些檔案的各種函式。我們也介紹了陰影口令,它可以增加系統的安全性。
- 附屬組提供了一個使用者同時參加多個組的方法。
- 系統相關資料檔案訪問函式提供了訪問系統屬性的方法。
- 時間函式提供了訪問時鐘時間的方法。