UNIX 环境高级编程知识点总结——第六章 系统数据文件和信息

6.1 口令文件

​ UNIX 口令文件(POSIX.1 则将其称为用户数据库)包含了表 6-1 中所示的各字段,这些字段包含在 <pwd.h> 中定义的 passwd 结构中。

​ /etc/passwd 文件中的字段。

在这里插入图片描述

​ 由于历史原因,口令文件是 /etc/passwd,而且是一个文本文件。每一行包含表 6-1 中所示的 7 个字段,字段之间用冒号相分隔。

​ 关于这些登录项,注意以下几点:

  • 通常有一个登录项,其用户名为 root,其用户 ID 是 0(超级用户)。
  • 加密口令字段包含了经单向密码算法处理过的用户口令副本。因为此算法是单向的,所以不能从加密口令猜测到原来的口令。
  • 口令文件中的某些字段可能是空。如果密码口令字段为空,这通常就意味着该用户没有口令(不推荐这样做)。
  • 支持 finger(1) 命令的某些 UNIX 系统支持注释字段中的附加信息。

​ POSIX.1 只定义了两个存取口令文件中信息的函数。在给出用户登录名或数值用户 ID 后,这两个函数就能查看相关记录。

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

​ 有些程序要查看整个口令文件。下列三个函数则可用于此。

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

void setpwent(void);
void endpwent(void);

​ 调用 getpwent 时,它返回口令文件中的下一个记录。如上面所述的两个 POSIX.1 函数一样,它返回一个由它填写好的 password 结构的指针。

​ 函数 setpwent 反绕它所使用的文件,endpwent 则关闭这些文件。在使用 getpwent 查看完口令文件后,一定要调用 endpwent 关闭这些文件。

​ getpwnam 的一个实现。

#include <sys/types.h>
#include <pwd.h>
#include <stddef.h>
#include <string.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);
}

​ 在程序开始处调用 setpwent 是保护性的措施,以便在调用者在此之前已经调用过 getpwent 的情况下,反绕有关文件使它们定位到文件开始处。getpwnam 和 getpwuid 完成后不应使有关文件仍处于打开状态,所以应调用 endpwent 关闭它们。

6.2 阴影口令

​ 对 UNIX 口令通常使用的加密算法是单向算法。给出一个密码口令,找不到一种算法可以将其反变换到普通文本口令。但是可以对口令进行猜测,将猜测的口令经单向算法变换成加密形成,然后将其与用户的加密口令相比较。如果用户口令是随机选择的,那么这种方法并不是很有用。但是用户往往以非随机方式选择口令(配偶的姓名、街名、宠物名等 )。一个经常重复的试验是先得到一份口令文件,然后用试探方法猜测口令。

​ 为使企图这样做的人难以获得原始资料(加密口令),某些系统将加密口令存放在另一个通常称为阴影口令(shadow password)的文件中。该文件至少要包含用户名和加密口令。与该口令相关的其他信息也可存放在该文件中。

6.3 组文件

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

在这里插入图片描述

​ 字段 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 关闭组文件。

6.4 添加组 ID

​ 使用添加组 ID 的优点是不必再显式地经常更改组。一个用户常常会参加多个项目,因此也就要同时属于多个组。

​ 为了存取和设置添加组 ID 提供了下列三个函数:

#include <sys/types.h>
#include <unistd.h>
int getgroups(int gidsetsize, git_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 说明了数组中的元素数。

6.5 其他数据文件

​ 一般情况下每个数据文件至少有三个函数:

  1. get 函数:读下一个记录,如果需要还打开该文件。此种函数通常返回指向一个结构的指针。当已达到文件尾端时返回空指针。大多数 get 函数返回指向一个静态存储类结构的指针, 如果要保存其内容,则需复制它。
  2. set 函数:打开相应数据文件(如果尚末打开),然后反绕该文件。如果希望在相应文件起始处开始处理,则调用此函数。
  3. end 函数:关闭相应数据文件。正如前述,在结束了对相应数据文件的读、写操作后,总应调用此函数以关闭所有相关文件。

​ 另外,如果数据文件支持某种形式的关键字搜索,则也提供搜索具有指定关键字的记录的例程。例如,对于口令文件提供了两个按关键字进行搜索的程序:getpwnam 寻找具有指定用 户名的记录;getpwuid 寻找具有指定用户 ID 的记录。

​ 下表中列出了一些这样的例程。

在这里插入图片描述

6.6 登录会计

​ 大多数 UNIX 系统都提供下列两个数据文件:utmp 文件,它记录当前登录进系统的各个用户;wtmp 文件,它跟踪各个登录和注销事件。

​ V7 中,包含下列结构的一个二进制记录写进这两个文件中:

struct utmp {
	char ut_line[8];	/* tty line: "ttyh0", "ttyd0", "ttyp0", ... */
	char ut_name[8]; 	/* login name */
	char ut_time;		/* seconds since Epoch */
}

​ 登录时,login 程序填写这样一个结构,然后将其写入到 utmp 文件中,同时也将其添加到 wtmp 文件中。注销时,init 进程将 utmp 文件中像一个的记录擦除(每个字节都填 0),并将一个新纪录添写道 wtmp 文件中。读 wtmp 文件中的该注销记录,其 ut_name 字段清除为 0。在系统再启动时,以及更改系统时间和日期的前后,都在 wtmp 文件中添写特殊的记录项。who(1) 程序读 utmp 文件,并以可读格式打印其内容。后来的 UNIX 版本提供 last(1) 命令,它读 wtmp 文件并打印所选择的记录。

6.7 系统标识

​ 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 pf this release */
	char machine[9];	/* name of hardware type */
};

​ utsname 结构中的信息通常可用 uname(1) 命令打印。

​ gethostname 函数只返回主机名,该名字通常就是 TCP/IP 网络上主机的名字。

#include <unistd.h>
int gethostname(char *name, int namelen);
// 返回:若成功则为 0,若出错则为 -1

6.8 时间和日期例程

​ 由 UNIX 内核提供的基本时间服务是国际标准时间公元 1970 年 1 月 1 日 00:00:00 以来经过的秒数。UNIX 在这方面与其他操作系统的区别是:(a) 以国际标准时间而非本地时间计时;(b) 可自动进行变换,例如变换到夏日制;© 将时间和日期作为一个量值保存。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(1) 命令的系统默认输出形式类似:

​ Tue Jan 14 17 : 49 : 03 1992\n\0

#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

​ format 参数控制时间值的格式。如同 printf 函数一样,变换说明的形式是百分号之后跟一个特定字符。下表列出了 21 种 ANSI C 规定的变换说明。
在这里插入图片描述
​ 表中第三列的数据来自于在 SVR4,对应于下列时间和日期,执行 strftime 函数所得的结果为:
​ Tue Jan 14 19 : 40 : 30 MST 1992

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值