apue:系统数据文件和信息

口令文件

    UNIX口令文件包含了下表中所示的各字段,这些字段包含在pwd.h中定义的passwd结构中。

说明structpasswd成员
用户名char*pw_name
加密口令char*pw_passwd
数值用户IDuid_tpw_uid
数值组IDgid_tpw_gid
注释字段char*pw_gecos
初始工作目录char*pw_dir
初始shell(用户程序)char*pw_shell

    关于这些登录项请注意下列各点:
- 通常有一个登录项,其用户名为root,其用户ID是0(超级用户)。
- 加密口令字段包含了经单向密码算法处理过的用户口令副本。
- 口令文件中的某些字段可能是空。
    在给出用户登录名或数值用户ID后,这两个函数就能查看相关记录。

#include <sys/types.h>
#include <pwd.h>
struct passwd* getpwuid(uid_t uid);
struct passwd* getpwnam(const char* name);
/*两个函数返回:若成功则为指针,若出错则为NULL*/

    这两个函数都返回一个指向passwd结构的指针,该结构已由这两个函数在执行时填入了所需的信息。该结构通常是在相关函数内的静态变量,只要调用相关函数,其内容就会被重写。
    下列三个函数则可用查看整个口令文件。

#include <sys/types.h>
#include <pwd.h>
struct passwd* getpwent(void);
/*返回:若成功则为指针,若出错或到达文件尾端则为NULL*/
void setpwent(void);
void endpwent(void);

    调用getpwent时,它返回一个由它填写好的password结构的指针。每次调用此函数时都重写该结构。在第一次调用该函数时,它打开它所使用的各个文件。在使用本函数时,对口令文件中各个记录安排的顺序并无要求。函数setpwent反绕它所使用的文件,endpwent则关闭这些文件。在使用getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。getpwent知道什么时间它应当打开它所使用的文件(第一次被调用时),但是它并不能知道何时关闭这些文件。

阴影口令

    对UNIX口令通常使用的加密算法是单向算法。给出一个密码口令,找不到一种算法可以将其反变换到普通文本口令。但是可以对口令进行猜测,将猜测的口令经单向算法变换成加密形成,然后将其与用户的加密口令相比较。为使企图这样做的人难以获得原始资料(加密口令),某些系统将加密口令存放在另一个通常称为阴影口令(shadow password)的文件中。该文件至少要包含用户名和加密口令。与该口令相关的其他信息也可存放在该文件中。阴影口令文件不应是一般用户可以读取的。仅有少数几个程序需要存取加密口令文件,这些程序常常设置-用户-ID为root。有了阴影口令后,普通口令文件/etc/passwd可由各用户自由读取。

组文件

    UNIX组文件包含了下表中所示字段。这些字段包含在grp.h中所定义的group结构中。

说明structgroup成员
组名char* gr_name
加密口令char* gr_passwd
数字组IDint gr_gid
指向各用户名指针的数组char**gr_mem

    字段gr_mem是一个指针数组,其中的指针各指向一个属于该组的用户名。该数组以null结尾。可以用下列两个函数来查看组名或数值组ID。

#include <sys/types.h>
#include <grp.h>
struct group* getgrgid(gid_t gid);
struct group* getgrnam(const char* name);
/*两个函数返回:若成功则为指针,若出错则为NULL*/

    如同对口令文件进行操作的函数一样,这两个函数通常也返回指向一个静态变量的指针,在每次调用时都重写该静态变量。如果需要搜索整个组文件,则须使用另外几个函数。下列三个函数类似于针对口令文件的三个函数。

#include <sys/types.h>
#include <grp.h>
struct group* getgrent(void);
/*返回:若成功则为指针,若出错或到达文件尾端则为NULL*/
void setgrent(void);
void endgrent(void);

    setgrent打开组文件(如若它尚末被打开)并反绕它。getgrent从组文件中读下一个记录,如若该文件尚未打开则先打开它。endgrent关闭组文件。

添加组ID

    当用户登录时,系统就按与口令文件相关的数字组ID,赋给他实际组ID。可以在任何时候执行newgrp以更改组ID。如果newgrp命令执行成功则实际组ID就更改为新的组ID,它将被用于后续的文件存取许可权检查。执行不带任何参数的newgrp,则可返回到原来的组。引入了添加组ID的概念,不仅可以属于口令记录中组ID所对应的组,也可属于多至16个另外的组。文件存取许可权检查相应被修改为:不仅将进程的有效组ID与文件的组ID相比较,而且也将所有添加组ID与文件的组ID进行比较。
    使用添加组ID的优点是不必再显式地经常更改组。一个用户常常会参加多个项目,因此也就要同时属于多个组。为了存取和设置添加组ID提供了下列三个函数:

#include <sys/types.h>
#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);
/*返回:若成功则为添加的组ID数,若出错则为-1*/
int setgroups(int ngroups, const gid_t grouplist[]);
int initgroups(const char* username, gid_t basegid);
/*两个函数返回:若成功则为0,若出错则为-1*/

    getgroups将进程所属用户的各添加组ID填写到数组grouplist中,填写入该数组的添加组ID数最多为gidsetsize个。实际填写到数组中的添加组ID数由函数返回。如果系统常数NGROUPS_MAX为0,则返回0,这并不表示出错。作为一种特殊情况,如若gidsetsize为0,则函数只返回添加组ID数,而对数组grouplist则不作修改。setgroups可由超级用户调用以便为调用进程设置添加组ID表。grouplist是组ID数组,而ngroups说明了数组中的元素数。通常,只有initgroups函数调用setgroups,initgroups读整个组文件,然后对username确定其组的成员关系。然后,它调用setgroups,以便为该用户初始化添加组ID表。因为initgroups调用setgroups,所以只有超级用户才能调用initgroups。除了在组文件中找到username是成员的组,initgroups也在添加组ID表中包括了basegid,basegid是username在口令文件中的组ID。

其他数据文件

    在日常事务操作中,UNIX系统还使用很多其他文件,对于这些数据文件的界面都与上述对口令文件和组文件的相似。一般情况下每个数据文件至少有三个函数:
1. get函数:读下一个记录,如果需要还打开该文件。此种函数通常返回指向一个结构的指针。当已达到文件尾端时返回空指针。大多数get函数返回指向一个静态存储类结构的指针,如果要保存其内容,则需复制它。
2. set函数:打开相应数据文件,然后反绕该文件。如果希望在相应文件起始处开始处理,则调用此函数。
3. end函数:关闭相应数据文件。正如前述,在结束了对相应数据文件的读、写操作后,总应调用此函数以关闭所有相关文件。
    另外,如果数据文件支持某种形式的关键字搜索,则也提供搜索具有指定关键字的记录的例程。存取系统数据文件的一些例程如下表

说明数据文件头文件结构附加的关键字搜索函数
口令/etc/passwdpwd.hpasswdgetpwnam,getpwuid
/etc/groupgrp.hgroupgetgrnam,getgrgid
主机/etc/hostsnetdb.hhostentgethostbyname,gethostbyaddr
网络/etc/networksnetdb.hnetentgetnetbyname,getnetbyaddr
协议/etc/protocolsnetdb.hprotoentgetprotobyname,getprotobynumber
服务/etc/servicesnetdb.hserventgetservbyname,getservbyport

登录会计

    大多数UNIX系统都提供下列两个数据文件:utmp文件,它记录当前登录进系统的各个用户;wtmp文件,它跟踪各个登录和注销事件。包含下列结构的一个二进制记录写入这两个文件中:
struct utmp {
char ut_line[8]; /tty line:”ttyh0”,”ttyd0”,”ttyp0”,…/
char ut_name[8]; /login name/
long ut_time; /seconds since Epoch/
};
    登录时,login程序填写这样一个结构,然后将其写入到utmp文件中,同时也将其添写到wtmp文件中。注销时,init进程将utmp文件中相应的记录擦除(每个字节都填以0),并将一个新记录添写到wtmp文件中。读wtmp文件中的该注销记录,其ut_name字段清除为0。在系统再启动时,以及更改系统时间和日期的前后,都在wtmp文件中添写特殊的记录项。

系统标示

    uname函数返回与主机和操作系统有关的信息。

#include <sys/utsname.h>
int uname(struct utsname* name);
/*返回:若成功则为非负值,若出错则为-1*/

    通过该函数的参数向其传递一个utsname结构的地址,然后该函数填写此结构。

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 this release */
    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字节)。如果宿主机联接到TCP/IP网络中,则此主机名通常是该主机的完整域名。
    hostname命令可用来存取和设置主机名。主机名通常在系统自举时设置,它由/etc/rc取自一个启动文件。

时间和日期历程

    由UNIX内核提供的基本时间服务是国际标准时间公元1970年1月1日00:00:00以来经过的秒数。我们称它们为日历时间。日历时间包括时间和日期。UNIX在这方面与其他操作系统的区别是:(a)以国际标准时间而非本地时间计时;(b)可自动进行转换,例如变换到夏日制;(c)将时间和日期作为一个量值保存。time函数返回当前时间和日期。

#include <time.h>
time_t time(time_t* calptr);
/*返回:若成功则为时间值,若出错则为-1*/

    时间值作为函数值返回。如果参数非null,则时间值也存放在由calptr指向的单元内。一旦取得这种以秒计的很大的时间值后,通常要调用另一个时间函数将其变换为人们可读的时间和日期。
    各个时间函数之间的关系如下图:

    两个函数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 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的理由是可以表示润秒。注意,除了月日字段,其他字段的值都以0开始。如果夏时制生效,则夏时制标志值为正;如果已非夏时制时间则为0;如果此信息不可用,则为负。

#include <time.h>
struct tm* gmtime(const time_t* calptr);
struct tm* localtime(const time_t* calptr);
/*两个函数返回:指向tm结构的指针*/

    localtime和gmtime之间的区别是:localtime将日历时间变换成本地时间(考虑到本地时区和夏时制标志),而gmtime则将日历时间变换成国际标准时间的年、月、日、时、分、秒、周日。
    函数mktime以本地时间的年、月、日等作为参数,将其变换成time_t值。

#include <time.h>
time_t mktime(struct tm* tmptr);
/*返回:若成功则为日历时间,若出错则为-1*/

    asctime和ctime函数产生形式的26字节字符串,这与date命令的系统默认输出形式类似。

#include <time.h>
char* asctime(const struct tm* tmptr);
char* ctime(const time_t* calptr);
/*两个函数返回:指向null结尾的字符串*/

    asctime的参数是指向年、月、日等字符串的指针,而ctime的参数则是指向日历时间的指针。
    最后一个时间函数是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参数控制时间值的格式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值