UNIX环境高级编程-第六章-系统数据文件和信息
1.引言
UNIX系統的正常運作需要適用大量與系統有關的數據文件,例如,口令文件/etc/passwd和組文件/etc/group就是經常被多個程序頻繁適用的兩個文件。用戶每次登錄UNIX系統,以及每次執行ls -l命令時都要使用口令文件。
由于歷史原因,這些數據文件都是ASCLL文本文件,并且使用標準IO庫讀這些文件。但是,對于較大的系統,順序掃描口令文件很花費時間,我們需要能夠以非ASCII文本格式存放這些文件,但仍向使用其他文件格式的應用程序提供接口,對于這些數據文件的可移植接口是本章的主題。本章也包括了系統標識函數,時間和日期函數。
2.口令文件
UNIX系統口令文件(POSIX.1則將其稱為用戶數據庫)包含了圖6-1中所示的各字段,這些字段包含了在<pwd.h>中定義的passwd結構中。
由于歷史原因,口令文件是/etc/passwd,而且是一個ASCII文件。每一行包含圖6-1中所示的各字段,字段之間用冒號分隔。例如,在Linux中,該文件中可能有下列4行:
root:x:0:0:root:/root:/bin/bash squid:x:23:23::/var:spool/squid:/dev/null nobody:x:65534:65534:Nobody:/home:/bin/sh sar:x:205:105:Stephen Rago:/home/sar:/bin/bash這些登錄項,請注意下列各點:
(1)通常有一個用戶名為root的登錄項,其用戶ID是0(超級用戶)
(2)加密口令字段包含了一個占位符。較早期的UNIX系統版本中,該字段存放加密口令字。將加密口令字存放在一個人人刻度的文件中是一個安全性漏洞,所以現在將加密口令字存放在另一個文件中。在下一節討論口令字時,我們再詳細解釋
(3)口令文件項中的某些字段可能是空。如果加密口令字段為空,這通常就意味著該用戶沒有口令。squid登錄項有一空白字段:注釋字段,空白注釋字段不產生任何影響。
(4)shell字段包含了一個可執行程序名,它被用作該用戶的登錄shell。若該字段為空,則取系統默認值,通常是/bin/sh。注意,squid登錄項的該字段為/dev/null。顯然,這是一個設備,不是可執行文件,將啟用于此處的目的是,阻止任何人以用戶squid的名義登錄到該系統。
(5)為了阻止一個特定用戶登陸系統,除使用/dev/null外,還有若干種替代方法。常見的一種方法是,將/bin/false用作登錄shell。它簡單地以不成功狀態終止,該shell將此種終止狀態判斷為假。另一種常見方法是,用/bin/true禁止一個賬戶。它所做的一切是以成功狀態終止。某些系統提供nologin命令,它打印可定制的出錯信息,然后以非0狀態終止。
(6)使用nobody用戶名的一個目的是,是任何人都可以登錄至系統,但其用戶ID(65534)和組ID(65534)不提供任何特權。該用戶ID和組ID只能訪問人人皆可讀,寫的文件。(假定用戶ID65534和組ID65534并不擁有任何文件)
(7)提供finger命令的某些UNIX系統支持注釋字段種的附加信息。其中各部分之間用逗號分隔:用戶姓名,辦公室地點,辦公室電話號碼以及家庭電話號碼等。另外,如果注釋字段中的用戶姓名是一個&,則它被替換為登錄名。例如,可以有下列記錄:
sar:x :205:105:Steve Rago, SF 5-121,
555-1111,555-2222:/home/sar:/bin/sh 使用finger命令就可以打印Steve Rago的有關信息
某些系統提供了vipw命令,允許管理員使用該命令編輯口令文件。vipw命令串行化地更改口令文件,并且確保它所作的更改與其他相關文件保持一致。系統也常常經由圖形用戶界面提供類似的功能。
POSIX.1定義了兩個獲取口令文件項的函數。在給出用戶登錄名或數值用戶ID后,這兩個函數就能查看相關項。
getpwuid函數由ls程序使用,它將i節點種的數字用戶ID映射為用戶登錄名。在鍵入登錄名時,getpwnam函數由login程序使用。
這兩個函數都返回一個指向passwd結構的指針,該結構已由這兩個函數在執行時填入信息。passwd結構通常是函數內部的靜態變量,只要調用任一相關函數,其內容就會被重寫。
如果要查看的只是登錄名或用戶ID,那么這兩個函數能滿足要求,但是也有些程序要查看整個口令文件。下列3個函數可用于此種目的。
調用getpwent時,它返回口令文件中的下一個記錄項。如同上面所述的兩個POSIX.1函數一樣,它返回一個由它填寫好的passwd結構的指針。每次調用此函數時都重寫該結構。
函數setpwent反繞它所使用的文件,endpwent則關閉這些文件。在使用getpwent查看完口令文件后,一定要調用endpwent關閉這些文件。getpwent知道什么時間應當打開它所使用的文件(第一次被調用時),但是它并不知道何時關閉這些文件。
實例6-2:給出了getpwnam函數的一個實現。
在函數開始處調用setpwent是自我保護性的措施,以便確保如果調用者再此之前已經調用getpwent打開了有關文件的情況下,反繞有關文件使它們定位到文件開始處。getpwnam和getpwuid完成后不應使有關文件仍處于打開狀態,所以使用endpwent關閉它們。
3.陰影口令
加密口令是經單向加密算法處理過的用戶口令副本。因為此算法是單向的,所以不能從加密口令猜測到原來的口令。
對于一個加密口令,找不到一種算法可以將其反變換到明文口令。但是可以對口令進行猜測,將猜測的口令經單向算法變換成加密形式,然后將其與用戶的加密口令比較。如果用戶口令是隨機選擇的,那么這種方法并不是很有用。但是用戶往往以非隨機方式選擇口令。一個經常重復的實驗是先得到一份口令文件,然后試探猜測口令。
為了使企圖這樣做的人難以獲得原始資料,現在,某些系統將加密口令存放在另一個通常稱為陰影口令的文件中。該文件至少包含用戶名和加密口令。與該口令相關的其他信息也可存放在該文件中(圖 6-3)。
只有用戶登錄名和加密口令這兩個字段是必須的。其他的字段控制口令更改的頻率,陰影口令文件不應是一般用戶可以讀取的。僅有少數幾個程序需要訪問加密口令,如login和passwd,這些程序常常是設置用戶ID為root的程序。有了陰影口令后,普通口令文件/etc/passwd可由各用戶自由讀取。
與訪問口令文件的一組函數相類似,有另一組函數而可用于訪問陰影口令文件。
4.組文件
UNIX組文件包含了圖6-4中所示的字段。這些字段包含在<grp.h>中所定義的group結構中。
字段gr_mem是一個指針數組,其中每個指針指向一個屬于該組的用戶名。該數組以null指針結尾。可以用下列兩個由POSIX.1定義的函數來產看組名或數值組ID。
#include <grp.h> struct group *getgrgid(gid_t gid); struct group *getgrnam(const char *name);如同對口令文件進行操作的函數一樣,這兩個函數通常也返回指向一個靜態變量的指針,在每次調用時都重寫該靜態變量。
如果需要搜索整個組文件,則需使用另外幾個函數。下列3個函數類似于針對口令文件的3個函數。
setgrent函數打開組文件并反繞它。getgrent函數從組文件讀取下一個記錄,如若該文件尚未打開,則先打開它。endgrent函數關閉組我呢見。
5.附屬組ID
在UNIX系統中,對組的使用已經作了些修改。在v7中,每個用戶任何時候都只屬于一個組。當用戶登陸時,系統就按口令文件記錄項中的數值組ID,賦給他實際組ID。可以在任何時候執行newgrp以更改組ID。如果newgrp命令執行成功,則實際組ID就更改為新的組ID,它將被用于后續的文件訪問權限檢查。執行不帶任何參數的newgrp,則可返回到原來的組。
這種組成員形式一致維持到1983年左右。此時,4.2BSD引入了附屬組ID的概念。我們不僅可以屬于口令文件記錄項中組ID所對應的組,也可屬于多至16個另外的組。文件訪問權限檢查相應被修改為:不僅將進程的有效組ID與文件的組ID相比較,而且也將所有附屬組ID與文件組ID進行比較。
使用附屬組ID的優點是不必再顯示地經常更改組。一個用戶會參加多個項目,因此也就要同時屬于多個組,此類情況是常有的。
為了獲取和設置附屬組ID,提供了下列3個函數。
getgroups將進程所屬用戶的各附屬組ID填寫到數組grouplist中,填寫入該數組的附屬組ID數最多為gidsetsize個。實際填寫到數組中的附屬組ID數由函數返回。
作為一種特殊情況,如若gidsetsize為0,則函數只返回附屬組ID數,而對數組grouplist則不做修改。
setgroups可有超級用戶調用以便為調用進程設置附屬組ID表。grouplist是組ID數組,而ngroups說明了數組中的元素數。ngroups的值不能大于NGROUPS_MAX。
通常,只有initgroups函數調用setgroups,initgroups讀整個組文件(用前面說明的函數getgrent,setgrent和endgrent),然后對username確定其組的成員關系。然后它調用setgroups,以便為該用戶初始化附屬組ID表。因為initgroups要調用setgroups,所以只有超級用戶才能能調用initgroups。除了在組文件中找到username是成員的所有組,initgroups也在附屬組ID表中包括了basegid。basegid是username在口令文件中的組ID。
6.其他數據文件
至此討論了兩個系統數據文件-口令文件和組文件。
在磁場操作中,UNIX系統還是用很多其他文件。例如BSD網絡軟件有一個記錄各網絡服務器所提供服務的數據文件(/etc/services),有一個記錄協議信息的數據文件(/etc/protocols),還有一個則是記錄網絡信息的數據文件(/etc/networks)。幸運的是,對于這些數據文件的接口都與上述對口令文件和組文件的相似。
一般情況下,對于每個數據文件至少有3個函數。
(1)get函數:讀下一個記錄,如果需要,還會打開該文件。此種函數通常返回指向一個結構的指針。當已到達文件尾端時返回空指針。大多數get函數返回指向一個靜態存儲類結構的指針,如果要保存其內容,則需要復制它。
(2)set函數:打開相應數據文件(如果尚未打開),然后反繞該文件。如果希望在相應文件起始處開始處理,則調用此函數。
(3)end函數:關閉相應數據文件。如前所述,在結束了對相應數據文件的讀,寫操作后,總應調用此函數以關閉所有相關文件。
另外,如果數據文件支持某種形式的鍵搜索,則也提供搜索具有指定鍵的記錄的例程。例如,對于口令文件,提供了兩個按鍵進行搜索的程序:getpwnam和getpwuid用于指定用戶的記錄。
圖6-6列出了一些這樣的例程。對于圖中列出的所有數據文件都有get,set和end函數。
7.登錄賬戶記錄
大多數UNIX系統都提供了下列兩個數據文件:utmp文件記錄當前登錄到系統的各個用戶;wtmp文件跟蹤各個登錄和注銷事件。在v7中,每次寫入這兩個文件中的是包含下列結構的一個二進制記錄;
struct utmp {char ut_line[8];char ut_name[8];long ut_time; }登陸時,login程序填寫此類結構,然后將其寫入到utmp文件中,同時也將其添寫到wtmp文件中。注銷時,init進程將utmp文件中相應的記錄擦除,并將一個新紀錄添寫到wtmp文件中。在wtmp文件的注銷記錄中,ut_name字段清除為0.在系統再啟動時,以及更改系統事件和日期的前后,都在wtmp文件中追加寫特殊的記錄項。
8.系統標識
POSIX.1定義了uname函數,它返回與主機和操作系統有關的信息。
#include <sys/utsname.h> int uname(struct utsname *name);通過該函數的參數向其傳遞一個utsname結構的地址,然后該函數添寫此結構。POSIX.1只定義了該結構中最少需提供的字段,而每個數組的長度則由實現確定。某些實現在該結構中提供了另外一些字段。
struct utsname {char sysname[];char nodename[];char release[];char version[];char machine[]; }每個字符串都以null字節結尾。本書討論的4中平臺支持的最大名字長度列于圖6-7中。utsname結構中的信息通??捎胾name命令打印。
BSD派生的系統提供gethostname函數,它只返回主機名,該名字通常就是TCP/IP網絡上主機的名字。
#include <unistd.h> int gethostname(char *name,int namelen);namelen參數指定name緩沖區長度,如若提供足夠的空間,則通過name返回的字符串以null字節結尾。如若沒有提供足夠的空間,則沒有說明通過name返回的字符串是否以null結尾。
現在,getihostname函數已在POSIX.1中定義,它指定最大主機名長度是HOST_NAME_MAX。
hostname命令可用來獲取和設置主機名。主機名通常在系統自舉時設置,它由/etc/rc或init取自一個啟動文件。
9.時間和日期例程
由UNIX內核提供的基本時間服務是計算自協調世界時公元1970年1月1日00:00:00這一特定時間以來經過的秒數。1.10節中曾提及這種秒數以數據類型time_t表示的。我們稱它們為日歷時間。日歷時間包括時間和日期。UNIX在這方面與其他操作系統的區別是:
(1)以協調統一時間而非本地時間計時;
(2)可自動進行轉換,如變換到夏令時
(3)將時間和日期作為一個量值保存
time函數返回當前時間和日期
#include <time.h> time_t time(time_t *calptr)時間值作為函數值返回。如果參數非空,則時間值也存放在由calptr指向的單元內。
POSIX的實時擴展增加了對多個系統時鐘的支持。在Single UNIX Specification V4中,控制這些時鐘的接口從可選組被移到基本組。時鐘通過clockid_t類型進行標識。圖6-8給出了標準值。
clock_gettime函數用于獲取指定時鐘的時間,返回的時間在timespec結構中,它把時間表示為秒和納秒。
#include <sys/time.h> int clock_gettime(clockid_t clock_id,struct timespec *tsp);當前時鐘ID設置為CLOCK_REATIME時,clock_gettime函數提供了與time函數類似的功能,不過在系統支持高精度時間值的情況下,clock_gettime可能比time函數得到更高精度的時間值。
#include<sys/time.h> int clock_getres(clockid clock_id,struct timespec *tsp);clock_getres函數把參數tsp指向的timespec結構初始化為與clock_id參數對應的時鐘精度。例如如果精度為一毫秒,則tv_sec字段就是0,tv_nsec字段就是1000000。
要對特定的時鐘設置時間,可以調用clock_settime函數
我們需要適當的特權來更改時鐘值,但是有些時鐘是不餓能修改的。
圖6-9說明了各種時間函數之間的關系。
兩個函數localtime和gmtime將日歷時間轉換成分解的時間,并將這些存放在一個tm結構中。
struct tm{int tm_sec;int tm_min;int tm_hour;int tm_mday;int tm_mon;int tm_year;int tm_wday;int tm_yday;int tm_isdst; }秒可以超過59的理由是可以表示潤秒。注意,除了月日字段,其他字段的值都以0開始。如果夏令時生效,則夏令時標志值為正;如果為非夏令時時間,則該標志值為0;如果此信息不可用,則其值為負。
#include <time.h> struct tm *gmtime(const time_t * calptr); struct tm *localtime(const time_t *calptr);localtime和gmtime之間的區別是:localtime將日歷時間轉換成本地時間(考慮到本地時區和夏令時標志),而gmtime則將日歷時間轉換成協調統一時間的年,月,日,時,分,秒,周日分解結構。
函數mktime以本地時間的年,月,日等作為參數,將其變換成time_t值。
函數strftime是一個類似于printf的時間值函數,它非常復雜,可以通過可用的多個參數來定制產生的字符串。
#include <time.h> size_t strftime(char *restrict buf,size_t maxsize,const char *restrict format.const struct tm*restrict tmptr); size_t strftime_l(char *restrict buf,size_t maxsize,const char *restrict format,cosnt struct tm *restrict tmptr,locale_t locale);strftime_l允許調用者將區域指定為參數,除此之外,strftime和strftime_l函數是相同的。strftime使用通過TZ環境變量指定的區域。
tmptr參數是要格式化的時間值,由一個指向分解時間值tm結構的指針說明。格式化結果存放在一個長度為maxsize個字符的buf數組中,如果buf長度足以存放格式化結果及一個null終止符,則該函數返回在buf中存放的字符數;否則趕回0.
format參數控制時間值的格式。如同printf函數一樣,轉換說明的形式是百分號之后跟一個特定字符。format中的其他字符按原樣輸出。兩個連續的百分號在輸出中產生一個百分號。與pritnf函數的不同之處是,每個轉換說明產生一個不同定長輸出字符串,在format字符串中沒有字段寬度修飾符。圖6-10列出了轉換說明:
圖6-10大多數格式說明的意義很明顯。需要略作解釋的是%U,%V,%W。%U是相應日期在該年中所屬周數,包含該年中第一個星期日的周是第一周。%W也是相應日期在該年所屬的周數,不同的是第一個星期一的周是第一周。%V說明符則與上述兩者有較大區別。如果包含了1月1日的那一周包含了新一年的4天或更多,那么該周是一年中的第一周;否則該周被認為是上一年的最后一周。在這兩種情況下,周一被視作每周的第一天。
實例6-11:
演示了如何使用本章中討論的多個時間函數。特別演示了如何使用strftime打印包含當前日期和時間的字符串
strptime函數是strftime的反過來的版本,把字符串時間轉換成分解時間。
#incldue <time.h> char *strptime(const char *restrict buf,const char *restrict format,struct tm *restrict tmptr);format參數給出了buf參數指向緩沖區的字符串的格式。雖然與strftime函數的說明稍有不同,但是格式說明是類似的。strptime函數轉換成說明符列在圖6-12中
我們曾在前面提及,圖6-9中以虛線表示的3個函數收到環境變量TZ的影響。這3個函數是localtime,mktime和strftime。如果定義了TZ,則這些函數將使用其值代替系統默認失去。如果TZ定義為空串,則使用協調統一時間UTC。TZ的值常常類似于TX=EST5EDT。
總結
以上是生活随笔為你收集整理的UNIX环境高级编程-第六章-系统数据文件和信息的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 通向实在之路暂记002:毕达哥拉斯定理与
- 下一篇: sass心得