UNIX环境高级编程学习之路(四)----系统数据文件和信息
2016-11-09 20:20
555 查看
对于UNIX环境编程,工作中经常会用到相关知识,作为学习UNIX环境编程的经典书籍--UNIX环境高级编程,是每个UNIX编程人员必看的经典书籍之一,为了将相关知识重新进行学习,以系统的整合所学知识,遂以博文形式作为总结。
1、概述
UNIX系统的正常运行需要使用大量与系统有关的数据文件,例如,口令文件/etc/passwd和组文件/etc/group就是经常被多个程序频繁使用的两个文件。用户每次登陆UNIX系统,以及每次执行ls -l命令时都要使用口令文件。
2、口令文件
UNIX口令文件包含了如下图所示的个字段,这些字段包含在<pwd.h>中定义的passwd结构中。
由于历史原因,口令文件是/etc/passwd,而且是一个ASCII文件。每一行包含如下图所示的各字段,用冒号分隔。
关于这些登录项,注意下面各点:
* 通常有一个用户名为root的登录项,其用户ID是0(超级用户)。
* 加密口令字段包含了一个占位符。较早期的系统版本中,该字段存放加密口令字,将加密口令字存放在一个人人可读的文件是一个安全性漏洞。所以将加密口令字存放在另一个文件中。
* 口令文件项中的某些字段可能为空。如果加密口令字段为空,这通常意味着该用户没有口令(不推荐这样做)。aquid登录项有一空白字段:注释字段。空白注释字段不产生任何影响。
* shell字段包含了一个可执行程序名,被用作该用户的登陆shell。若该字段为空,则取系统默认值,通常是/bin/sh。注意,squid登录项的该字段为/dev/null。显然,这是一个设备,不是可执行文件,此时目的是,阻止任何人以用户squid的名义登陆到该系统。
* 为了阻止一个特定用户登陆系统,除了使用/dev/null外,还有若干种替代方法,常见一种方法是:将/bin/false用作登陆shell。他简单的以不成功(非0)状态终止,该shell将这种终止状态判断为假。另一种常见方法是:用/bin/true禁止一个账户,他所做的一切是以成功(0)状态终止。某些系统提供nologin命令,他打印可定制的出错信息,然后以非零状态终止。
* 使用nobody用户名的一个目的是,使任何人都可以登录至系统,但其用户ID和组ID不提供任何特权。该用户ID和组ID只能访问人人皆可读、写的文件。
* 提供finger(l)命令的某些支持注释字段中的附加信息。如:
以下定义了获取口令文件项的函数。在给出用户登录名或数值用户ID后,这两个函数就能查看相关项。
#include <pwd.h> struct passwd *getpuid(uid_t uid); struct passwd *getpwnam(const char *name); 返回值:若成功,返回指针;若出错,返回NULL
这两个函数都返回一个指向passwd结构的指针。该结构已由这两个函数在执行时填入信息。passwd结构通常是函数内部的静态变量,只要调用任一相关函数,其内容就会被重写。若有些程序要查看整个口令文件,则下面函数可用于此目的。
获取root账号的识别码和根目录
#include <pwd.h> #include <sys/types.h> main() { struct passwd *user; user = getpwnam("root"); printf("name:%s\n", user->pw_name); printf("uid:%d\n", user->pw_uid); printf("home:%s\n", user->pw_dir); }
#include <pwd.h> struct passwd *getpwent(void); 返回值:若成功,返回指针;若出错或者到达文件尾端,返回NULL void setpwent(void); void endpwent(void);
调用getpwent时,他返回口令文件的下一个记录项。每次调用此函数都重写passw结构。
函数setpwent反绕它所使用的文件,endpwent则关闭这些文件。在使用getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。
函数getpwnam的一个实现:
#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) breakl; } endpwent(); return ptr; }
3、阴影口令
加密口令是经过单项加密算法处理过的用户口令副本。因为此算法是单向的,所以不能从加密口令猜测到原来的口令。 为了不让人获得原始资料(加密口令),现在,某些系统将加密口令存放在另一个阴影口令(shadow password)文件中。该用户至少包含用户名和加密口令。与该口令相关的其他信息也可以存放在该文件中
只有用户登录名和加密口令这两个字段是必须的,阴影口令文件不应是一般用户可以读取的。
有一组函数可以访问阴影口令文件。
#include <shadow.h> struct spwd *getspnam(const char *name); struct spwd *getspent(void); 两个函数返回值:若成功,返回指针;若出错,返回NULL void setspent(void); void endspent(void);
4、组文件
UNIX组文件的字段包含在 grp.h中定义的group结构中。
字段gr_mem是一个指针数组,其中每个指针指向一个属于该组的用户名。该数组以null指针结尾。
可以用下面函数查看组名或数值组ID。
#include <grp.h> struct group *getgrpid(gid_t gid); struct group *getgrnam(const char *name); 返回值:若成功,返回指针;出错,返回NULL
这两个函数也返回一个静态变量的指针,在每次调用时都重写该静态变量。
若要搜索整个组文件,则使用另外几个函数。
#include <grp.h> struct group *getgrent(void); 返回值:若成功,返回指针;;若出错或者到达文件尾端,返回NULL void setgrent(void); void endgrent(void);
5、附属组
在UNIX系统中,对组的使用已经做了更改。在V7中,每个用户任何时候都只属于一个组。当用户登录时,系统就按口令文件记录项中的数值组ID,付给他实际组ID。可以在任何时候执行newgrp(l)以更改组ID。如果newgrp执行成功,则实际组ID更改为新的组ID,将用于后续的文件访问权限检查。执行不带任何参数的newgrp,则可返回到原来的组。
在使用附属组时,我们不仅可以属于口令文件记录项中组ID所对应的的组,也可属于多至16个另外的组。文件访问权限检查相应的被修改为:不仅将进程的有效组ID与文件的组ID进行比较,而且也将所有附属组ID与文件的组ID进行比较。
使用附属组ID的有点事不必显式的经常更改组。一个组用户会参与多个项目,因此也就要同时属于多个组。
为了获取和设置组ID,提供如下函数
#include <unistd.h> int getgroups(int gidsize, gid_t grouplist[]); 返回值:若成功,返回附属组ID数量;若出错,返回-1 #include <grp.h> int setgroups(int ngroups, const gid_t grouplist[]); #include <grp.h> int initgroups(const char *username, gid_t basegid); 两个函数返回值:若成功,返回0;出错,返回-1
6、其他数据文件
UNIX还有很多其他文件,如记录各网络所提供服务的数据文件(/etc/services),有一个记录协议信息的数据文件(/etc/networks)。 一般情况下,对于每个数据文件至少有3个函数。 (1)get函数:读下一个记录,如果需要,还会打开该文件。这种函数通常返回指向一个结构的指针,如果要保存其内容,则需复制它。 (2)set函数:打开相应数据文件,然后反绕该文件。如果希望在相应文件起始处开始处理,则调用此函数。 (3)end函数:关闭相应数据文件。在结束了对相应数据文件的读、写操作后,总应调用此函数关闭所有相关文件。
如下为访问系统数据文件的一些例程:
7、登陆账户记录
大多数UNUX系统都提供下列两个数据文件:utmp文件记录当前登陆到系统的各个用户:wtmp文件跟踪各个登陆和注销事件。每次写入这两个文件的是包含下列结构的一个二进制记录: struct utmp { char ut_line[8];/* tty line: "ttyh0","ttyd0", "ttyp0,..." */ char ut_name[8]; /*login name*/ long it_time; /*seconds since Epoch*/ };
登录时,login程序填写此类型结构,然后将其写入到utmp文件中,同时也将其添写到wtmp文件中。注销时,init进程将utmp文件中相应记录擦除,并将一个新纪录添写到wtmp文件中。在wtmp文件的注销记录中,ut_name字段清除为0.在系统再启动时,以及更改系统时间和日期的前后,都在wtmp文件中追加特殊的记录项。who(l)程序读取utmp文件,并以可读格式打印其内容。后来的UNIX版本提供last(l)命令,它读wtmp文件并打印所选择的记录。
8、系统标识
uname函数,它返回与主机和操作系统有关的信息。
#include <sys/utsname.h> int uname(struct utsname *name); 返回值:若成功,返回非负值;若出错,返回-1
通过该函数的参数向其传递一个utsname结构的指针,然后该函数填写此结构。
struct ustname {
char sysname[]; /操作系统名称/
char nodename[]; /节点名/
char release[]; /当前发行的操作系统/
char version[]; /系统版本/
char machine[]; /硬件类型名/
}
每个字符串都以null字节结尾。utsname结构中的信息通常用uname(l)命令打印。
gethostname函数,返回主机名,该名字通常是TCP/IP网络上主机的名字。
#include <unistd.h> int gethostname(char *name, int namelen); 返回值:若成功,返回0;若出错,返回-1
namelen参数指定name缓冲区长度,如若提供足够的空间,则通过name返回的字符串以null字节结尾。如若没有提供足够的空间,则没有说明通过name返回的字符串是否以null结尾。
如果宿主主机连接到TCP/IP网络中,则此主机名通常是该主机的完整域名。
hostname(l)命令可用来获取和设置主机名。
9、时间和日期例程
UTC时间是公元1970年1月1日00:00:00这一特定时间以来经过的秒数。这种秒数是以数据类型time_t表示的,我们称它为日历时间。日历时间包括时间和日期。 time函数当前时间和日期。
#include <time.h> time_t time(time_t *calptr); 返回值:若成功,返回时间值;若出错,返回-1
时间值作为函数值返回。如果参数非空,则时间值也存放在由calptr指向的单元内。
clock——gettime函数可用于获取指定时钟的时间。返回的时间在timespec结构中,表示为秒和纳秒。
#include <sys/time.h> int clock_gettime(clockid_t clock_id, struct timespec *tsp); 返回值:若成功,返回0;若出错,返回-1
要对特定的时钟设置时间,可以调用clock_settime函数。
#include <sys/time.h> int clock_settime(clockid_t clock_id, const struct timespec *tsp); 返回值:若成功,返回0;若出错,返回-1
我们需要适当的权限来更改时钟值,打哪会有些时钟是不能修改的。
#include <sys/time.h> int gettimeofday(struct timeval *restrict tp, void *restrict tzp); 返回值:总是返回0
tzp的唯一合法值是NULL,其他值将产生不确定的结果。gettimeofday函数以距特定时间(1970年1月1日00:00:00)的秒数的方式将当前时间存放在tp指向的timeval结构中,而该结构将当前时间表示为秒和微秒。
两个函数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; };
#include <time.h> struct tm *gmtime(const time_t *calptr); struct tm *localtime(const time_t *calptr); 返回值:指向分解的tm结构的指针;若出错,返回NULL
localtime和gtime之间的区别是:localtime将日历时间转换成本地时间,而gmtime则将日历时间转换成协调统一时间的、年、月、日、时、分、秒、周日分解结构。
函数mktime以本地时间的年、月、日等作为参数,将其变换成time_t值。
#include <time.h> time_t mktime(struct tm *tmptr); 返回值:若成功,返回日历时间;若出错,返回-1
函数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, const struct tm *restrict tmptr, locale_t locale); 返回值:若有空间,返回存入数组的字节数;否则,返回0
格式化结果存放在一个长度为maxsize个字符的buf数组中,如果buf长度足以存放格式化结果及一个null终止符,则返回在buf中存放的字节数;否则返回0。
format参数控制时间值的格式。
以下例子演示如何使用strftime打印当前日期和时间的字符串。
#include <stdio.h> #include <stdlib.h> #include <time.h> int main(void) { time_t t; struct tm *tmp; char buf1[16]; char buf2[64]; time(&t); tmp = localtime(&t); if (strftime(buf1, 16, "time and date: %r, %a %b %d, %Y",tmp) == 0) printf("buffer1 length is too small\n"); else printf("%s\n",buf1); if (strftime(buf2, 64, "time and date: %r, %a %b %d, %Y",tmp) == 0) printf("buffer2 length is too small\n"); else printf("%s\n",buf2); return 0; }
执行结果为:
strptime函数是strftime函数反过来的版本,把字符串时间转换成分解时间。
#include <time.h> char *strptime(const char *restrict buf, const char *restrict format, struct tm *restrict tmptr); 返回值:指向上次解析的字符的下一个字符的指针;否则,返回NULL
format参数给出了buf参数指向的缓冲区的字符串的格式。虽然与strftime函数的说明稍有不同,但是格式说明是类似的。
————————————华丽的风格线————————————————
QQ群:西安C/C++开发者,诚邀您的加入
相关文章推荐
- UNIX环境高级编程学习之第六章系统数据文件和信息-修改第四章实现的Shell的“ls -l”功能
- Unix环境高级编程-系统数据文件和信息
- UNIX环境高级编程之第6章:系统数据文件和信息-习题
- UNIX环境高级编程——第六章—系统数据文件和信息
- UNIX环境高级编程 第6章 系统数据文件和信息
- UNIX环境高级编程学习之第六章系统数据文件和信息-取所有组名、GID
- UNIX环境高级编程学习之第六章系统数据文件和信息 用链表的形式读出一个服务器的远程用户登入登出信息
- 标准IO库、系统数据文件和信息 - UNIX环境高级编程-第5、6章
- UNIX环境高级编程学习笔记(七)系统数据文件和信息
- UNIX环境高级编程学习之第六章系统数据文件和信息-取所有用户名和UID, GID
- UNIX环境高级编程学习之第六章系统数据文件和信息-GID To GroupName
- UNIX环境高级编程学习之第六章系统数据文件和信息-实现uid to name
- UNIX环境高级编程之第6章:系统数据文件和信息
- UNIX环境高级编程-第6章- 系统数据文件和信息
- (四) 一起学 Unix 环境高级编程(APUE) 之 系统数据文件和信息
- UNIX环境高级编程-读书笔记-系统文件信息
- Unix环境高级程序设计入门----文件系统的相关编程(下)
- Unix环境高级程序设计入门----文件系统的相关编程(上)
- Unix环境编程-系统文件和信息
- Unix环境高级程序设计入门--文件系统的相关编程(上)