UNIX口令文件包含下表中的各个字段,这些字段包含在<pwd.h>头文件中定义的passwd结构体中。
node
因为历史缘由,口令文件是/bin/passwd,并且是一个文本文件,每一行都包括了上表中的七个字段,字段之间用":"分隔,例如一个文件中可能有如下三行:
root:jheVopR58x9Fx:0:1:The superuser:/:/bin/sh n o b o d y : * : 6 5 5 3 4 : 6 5 5 3 4 : : / : stevens:3hKVD8R58r9Fx:224:20:Richard Stevens:/home/stevens:/bin/ksh
对于这些登录项须要注意如下几点:算法
stevens:3hKVD8R58r9Fx:224:20:Richard &, B232, 555-1111, 555-2222: / h o m e / s t e v e n s : / b i n / k s h
POISX.1只定义了两个存取口令文件中信息的函数:参数为用户登陆名或者是数值ID:
```
#include <sys/types.h>
#include <pwd.h>shell
struct passwd getpwuid(uid_t uid);
struct passwd getpwnam(const char *name);
返回值:成功则为指针,出错为NULL
getpwuid由ls命令使用,用于从i节点中的数值用户ID获取用户登陆名。getpwnam在输入登陆名时由login程序使用。 可用如下三个函数查看整个口令文件:
#include <sys/types.h>
#include <pwd.h>bootstrap
struct passwd *getpwent(void);
返回值:成功返回指针,出错或到达文件尾端返回NULL
void setpwent(void);
void endpwent(void);
```
调用getpwent时,返回口令文件中的下一个记录,它返回一个由它填写的passwd结构的指针,每次调用此函数都重写该结构。
setpwdent函数定位文件到开始处,endpwent关闭这些文件。再用getpwent查看完口令文件后必定要用endpwent关闭这些文件。getpwent函数并不知道什么时候关闭这些文件。数组
有些系统为了避免让黑客获得原始加密口令会将加密口令存放在另外一个一般被称为阴影口令的文件中(如:/etc/shadow)。该文件至少要包含用户名和加密口令。与该口令相关的信息也能够存放在该文件中。例若有些系统会要求用户在必定时间间隔后修改口令,而这个时间间隔长度就存在阴影口令文件中。
阴影口令文件不该是通常用户能够读取的。仅有少数几个程序须要存取加密口令文件,例如login和passwd。这些程序一般设置-用户-ID为root。有了阴影口令后,普通口令文件/etc/passwd可由各用户自由读取。网络
UNIX组文件包含了以下字段,这些字段包含在<grp.h>z中所定义的group结构中。
app
gr_mem是一个指针数组,其中的指针各指向一个属于该组的用户名,该数组以null结尾。
能够由POSIX.1定义的两个函数来查看组名或数值组ID。
```
#include <sys/types.h>
#include <grp.h>函数
struct group getgrgid(gid_t gid);
srruct group getgrnam(const char *name);
返回值:成功返回group指针,出错NULL
如同对口令文件进行操做的函数同样,这两个函数一般也返回指向一个**静态变量**的指针,在每次调用时都重写该静态变量。 如下三个函数相似与针对口令文件的函数,用来查看整个组文件:
#include <sys/types.h>
#include <grp.h>学习
struct group *getgrent(void);
返回值:成功则为指针,出错或到达文件尾端则为NULL
void setgrent(void);
void endgrent(void);
```ui
setgrent打开组文件并定位到文件开始,getgrent从组文件读下一条记录,若是该文件未打开则先打开它,endgrent关闭组文件。
在UNIX中,对组的使用已经作了一些修改。在V7中一个用户任什么时候候只能属于一个组,当用户登陆时,系统就按照口令文件中用户相关联的组ID赋给他实际组ID。能够在任什么时候候执行netgrp更改组。若是newgrp命令执行成功则实际组更改成新的组,它将用于后续文件权限检查。执行不带任何权限的newgrp则可返回到原来的组。
这种组的成员关系一直持续到1983年左右,此时4.2BSD引入了添加组ID的概念,咱们不只能够属于口令记录中组ID所对应的组,还能够属于多至16个另外的组。文件权限检查并修改成:不只将进程的有效组ID与文件的zuID比较,并且也将全部添加组ID与文件的组ID比较
使用添加组ID的一个优点就是不用显式的修改用户组ID。
为了存取和设置添加组ID,一般使用如下三个函数:
```
#include <sys/types.h>
#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);
返回值:成功则为添加的组数量,出错为-1.
int setgroups(int ngroups, const gid_t grouplist[]);
int initgroups(const char *username, gid_t basegid);
两个函数返回值:成功0,出错-1.
```
getgroups将进程所属用户的各添加组填到数组grouplist中,填入该数组的ID数最多gidsetsize。实际填入的数量由该函数返回。若是系统常数NGROUPS_MAX为0,则返回0,这并不表示错误。
setgroups可由root用户调用为调用进程设置添加组ID表,grouplist是组ID数组,ngroups是数组元素数。
一般只有initgroups调用setgroups,initgroups读整个组文件而后对username肯定其组的成员关系。而后它调用setgroups为该用户初始化添加组ID表。由于initgroups调用setgroups,因此只有root用户才能调用initgroups,除了在组文件中找username是成员的组,initgroups也在添加组ID表中包括了basegid。basegid是username在口令文件中的组ID。
除了之上咱们学习的口令文件和组文件,UNIX系统还有其余一下数据文件。对于这些数据文件的界面都与上述对口令文件和组文件的类似。
通常状况下数据文件都有三个函数:
大多数UNIX系统都提供一下两个数据文件:
struct utmp { char ut_line[8]; /* tty line: "ttyh0","ttyd1","ttyp0"...... */ char ut_name[8]; /* login name */ long ut_time; /* second since Epoch */ }
登陆时,login程序填写这样一个结构而后填入utmp文件中,同时填入到wtmp中。注销时,init进程将utmp文件中相应的记录擦除(每一个字节都为0),并将一个新纪录填入到wtmp中。读wtmp中该注销记录,其ut_name清除为0。在系统再启动时,以及更改系统日期和时间的先后,都在wtmp中填写特殊的记录项。who命令读utmp文件,并以可读格式打印其内容。后来的UNIX系统版本提供last命令,它读wtmp文件并打印所选择的记录。
POSIX.1定义了uname函数,返回与主机和操做系统相关的信息。
#include <sys/utsname.h> int uname(struct utsname *name); 返回值: 成功为非负值,出错为-1.
经过参数向该函数传一个struct uname结构的地址,而后该函数填写该参数。POSIX.1只定义了该结构至少须要的字段(都是字符数组),每一个数组的长度由实现来决定。 历史上,V系统为每一个数组分配9个字节,其中最后一个字节是null结束符。
struct utsname{ char sysname[9]; /* name of the operating system */ char nodename[9]; /* name of this node */ char release[9]; /* current release of operating system */ char version[9]; /* current version of operating system */ char machine[9]; /* name of hardware type */ }
utsname结构中的信息一般由uname命令打印。
伯克利类的版本提供了gethostname函数,只返回主机名,该名字一般就是TCP/IP网络上主机的名字。
#include <unistd.h> int gethostname(char *name, int namelen); 返回值:成功0,出错-1.
经过name返回的字符串以null结束(除非没有提供足够的空间)。<sys/param.h>中的常数MAXHOSTNAMELEN规定了此名字的最大长度(一般是64字节)。
hostname命令能够用来存取和设置主机名(root用户用一个相似的函数sethostname来设置主机名),主机名一般在系统自举(bootstrapping)时设置,它由/etc/rc取自一个启动文件。
time函数返回当前日期和时间
#include <time.h> time_t time(time_t *calptr); 返回值:成功为时间值(时间戳),出错为-1.
若是参数不是NULL,则返回的时间值也保存在calptr指向的单元中
一旦取得时间戳后,一般要调用另一个时间函数将其转换为人们可读的时间和日期。下图说明了各时间函数之间的关系。(图中虚线标注的函数一般受环境变量TZ的影响)
两个函数localtime和gmtime将时间戳转换为以年、月、日、时、分、秒、周日表示的时间,并将这些存放在tm结构中。
struct tm { /* a broken down time */ int tm_sec; /* seconds after the minute:[0,61] */ int tm_min; /* minutes after the hour:[0,59] */ int tm_hour; /* hours after the midnight:[0,23] */ int tm_mday; /* day of the month:[1,31] */ int tm_mon; /* month of the year:[0,11] */ int tm_year; /* years since 1900 */ int tm_wday; /* days since Sunday:[0,6] */ int tm_yday; /* days since January 1:[0,365] */ int tm_isdst; /* daylight saving time flag:<0, 0 >0 夏时制标志值 */ }
秒能够超过59的缘由是能够表示润秒。若是夏时制生效,则tm_isdst为正,若是已非夏时制则为0,若是此信息不可用则为负。
#include <time.h> struct tm *gmtime(const time_t *calptr); struct tm *localtime(const time_t *calptr);
localtime和gmtime的区别在于localtime将时间戳转换为本地时间(考虑到本时区和夏时制标志)。而gmtime转为了国际标准时间。
函数mktime以本地时间的年、月、日、时、分、秒等做为参数将其转为时间戳:
#include <time.h> time_t mktime(struct tm *tmptr);
asctime和ctime函数产生形式的26字节字符串,这与date命令的系统默认输出形式相似;
Tue Jan 14 10:15:03 1992\n\0
#include <time.h> char *asctime(const struct tm *tmptr); char *ctime(const time_t *calptr); 返回值:指向null结尾的字符串
最后一个时间函数是strftime,它是很是复杂的printf类的时间值函数
#include <time.h> size_t strftime(char *buf, size_t maxsize, const char *format, const struct tm *tmptr); 返回值:如有空间,则存入数组的字符数,不然为0.
最后一个参数是要格式化的时间指针。格式化结果存放在一个长度为maxsize个字符的buf数组中,若是buf长度足以存放格式化结果及一个null终止符,则该函数返回在buf中存放的字符数(不包括null终止符),不然该函数返回0。
format参数控制时间值的格式,如同printf函数同样,变换格式说明是百分号后面跟一个特定字符。format中其余自负原样输出。两个连续的百分号在输出中产生一个百分号。与printf函数不一样的是,每一个变换说明产生一个定长输出字符串,在format字符串中没有字段宽度修饰符。下表列出了21种ANSI C规定的变换说明
表中第三列的数据来自于SVR4,对应于下列日期与时间,执行strftime函数所得的结果为:
Tue Jan 14 10:15:30 MST 1992
表中%U是相应日期在该年中所属周数,包含该年中第一个星期日的周是第一周。%W也是相应日期在该年中所属的周数,不一样的是包含第一个星期一的周为第一周。 若是定义了环境变量TZ,则localtime、mktime、ctime、strftime(即时间函数关系图中虚线标准的函数)将使用其值代替系统默认时区。若是定义TZ为空串(即TZ=),则使用国际标准时间。TZ的值常相似于TZ=EST5ETD,可是POSIX.1容许更为详细的说明。