1. 程式人生 > >《UNIX環境高階程式設計》 第6章 系統資料檔案和資訊

《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系統都是用口令檔案合組檔案,我們說明了這些檔案的各種函式。我們也介紹了陰影口令,它可以增加系統的安全性。
  • 附屬組提供了一個使用者同時參加多個組的方法。
  • 系統相關資料檔案訪問函式提供了訪問系統屬性的方法。
  • 時間函式提供了訪問時鐘時間的方法。