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后,这两个函数就能查看相关项。

#include<pwd.h>
struct passwd *getpwuid(uid_t uid);
struct passwd *getpwnam(const char *name);

getpwuid函数由ls程序使用,它将i节点种的数字用户ID映射为用户登录名。在键入登录名时,getpwnam函数由login程序使用。
这两个函数都返回一个指向passwd结构的指针,该结构已由这两个函数在执行时填入信息。passwd结构通常是函数内部的静态变量,只要调用任一相关函数,其内容就会被重写。
如果要查看的只是登录名或用户ID,那么这两个函数能满足要求,但是也有些程序要查看整个口令文件。下列3个函数可用于此种目的。

#include <pwd.h>
struct passwd *getpwent(void);
void setpwent(void);
void endpwent(void);

调用getpwent时,它返回口令文件中的下一个记录项。如同上面所述的两个POSIX.1函数一样,它返回一个由它填写好的passwd结构的指针。每次调用此函数时都重写该结构。
函数setpwent反绕它所使用的文件,endpwent则关闭这些文件。在使用getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。getpwent知道什么时间应当打开它所使用的文件(第一次被调用时),但是它并不知道何时关闭这些文件。
实例6-2:给出了getpwnam函数的一个实现。

#include <pswd.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关闭它们。

3.阴影口令

加密口令是经单向加密算法处理过的用户口令副本。因为此算法是单向的,所以不能从加密口令猜测到原来的口令。
对于一个加密口令,找不到一种算法可以将其反变换到明文口令。但是可以对口令进行猜测,将猜测的口令经单向算法变换成加密形式,然后将其与用户的加密口令比较。如果用户口令是随机选择的,那么这种方法并不是很有用。但是用户往往以非随机方式选择口令。一个经常重复的实验是先得到一份口令文件,然后试探猜测口令。
为了使企图这样做的人难以获得原始资料,现在,某些系统将加密口令存放在另一个通常称为阴影口令的文件中。该文件至少包含用户名和加密口令。与该口令相关的其他信息也可存放在该文件中(图 6-3)。

只有用户登录名和加密口令这两个字段是必须的。其他的字段控制口令更改的频率,阴影口令文件不应是一般用户可以读取的。仅有少数几个程序需要访问加密口令,如login和passwd,这些程序常常是设置用户ID为root的程序。有了阴影口令后,普通口令文件/etc/passwd可由各用户自由读取。
与访问口令文件的一组函数相类似,有另一组函数而可用于访问阴影口令文件。

#include<shadow.h>
struct spwd *getspnam(cosnt char *name);
struct spwd *getspent(void);
void setspent(void);
void endspent(void);

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个函数。

#include <grp.h>
struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

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个函数。

#include <unistd.h>
#include <grp.h>
#include <unistd.h>
int setgroups(int ngroups,const gid_t grouplist[]);
int initgroups(const char *username,gid_t basegid);
int getgroup(int gidsetsize,gid_t grouplist[])

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结构中的信息通常可用uname命令打印。

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函数

#include <sys/time.h>
int clock_settime(clockid_t clock_id,const struct timespec *tsp);

我们需要适当的特权来更改时钟值,但是有些时钟是不饿能修改的。
图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值。

#include <time.h>
time _t mktime(struct tm *tmptr);

函数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打印包含当前日期和时间的字符串

#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("buffer length 16 is too small\n");elseprintf("%s\n",buf1);if(strftime(buf2,64,"time and date: %r, %a %b %d,%Y",tmp)==0)printf("buffer length of 64 is too small\n");elseprintf("%s\n",buf2);exit(0);
}

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环境高级编程-第六章-系统数据文件和信息相关推荐

  1. 《unix环境高级编程》笔记4——系统数据文件和信息

    文章目录 系统数据文件和信息 口令文件 获取口令文件 getwpuid getpwnam 阴影口令 组文件 附属组ID 查看附属组ID 其他文件 登录账户记录: 系统标识 uname gethostn ...

  2. APUE第6章 系统数据文件和信息

    6.1 引言 UNIX系统的正常运作需要使用大量与系统有关的数据文件,例 如,口令文件/etc/passwd和组文件/etc/group就是经常被多个程序频繁 使用的两个文件.用户每次登录UNIX系统 ...

  3. 《UNIX环境高级编程》六系统数据文件和信息读书笔记

    UNIX系统的正常运作需要使用大量与系统有关的数据文件,例如,口令文件/etc/passwd和组文件/etc/group等. 1.口令文件 口令文件的各字段包含在pwd.h>中定义的passwd ...

  4. Unix环境高级编程-第四章

    1.引言 上一章我们说明了执行IO操作的基本函数,其中的讨论是围绕普通文件IO进行的-打开文件,读文件或写文件.本章将描述文件系统的其他特征和文件的性质.我们将从stat函数开始,逐个说明stat结构 ...

  5. UNIX环境高级编程--第七章

    1进程终止 进程正常终止: view plaincopy to clipboard #include<stdlib.h>    void exit(int status);    void ...

  6. UNIX 环境高级编程(六)—— 程序和进程

    1. 基本概念 1.1 程序(program) 程序是一个存储在磁盘上某个目录中的可执行文件. 内核使用 exec 函数(七个 exec 函数之一),将程序读入内存. 1.2 进程(process) ...

  7. UNIX环境高级编程 第11章 线程

    使用C++调用pthread_cleanup_push( )时,下面的代码是无法编译通过的: pthread_cleanup_push(cleanup, "thread 1 first ha ...

  8. UNIX环境高级编程第三章

    1.对于内核,所有打开或者新创建的文件都通过文件描述符引用.open或者creat获得的文件描述符传递给read或者write做相关函数的第一参数. [1]open函数,除非创建新的函数带第三个参数, ...

  9. UNIX环境高级编程 第12章 线程控制

    c'nblogs 转载于:https://www.cnblogs.com/pluse/p/6889874.html

  10. UNIX 环境高级编程(一) apue.h 文件与apue.3e的安装

    apue:Advanced Programming in the UNIX Environment, 本文关注第三版(3e) 1. apue.3e 的安装 APUE.3e 安装(基于ubuntu12. ...

最新文章

  1. 90 后女科学家,四年完成清华大学硕博连读,解决多个世界级难题
  2. 回归评价指标MSE、RMSE、MAE、R-Squared
  3. python【力扣LeetCode算法题库】10-正则表达式匹配
  4. javascript-for-loop-example--reference
  5. 深度学习RCNN, Fast-RCNN, Faster-RCNN的一些事
  6. Java阶段性测试--第二三大题参考代码
  7. 各种数据库连接jdbc
  8. kisnetflt64.sys怎么删除
  9. How org unit id and type is determined in Genil
  10. flex 换主轴后子元素占满_Chrome72 嵌套 flex 布局修改,你的网站可能会发生布局错乱...
  11. 互联网日报 | 7月15日 星期四 | B站赠送所有用户1天大会员;饿了么投入3亿用于今夏骑手保障;小米智能工厂二期开工...
  12. 研究生阅读管理文献---我阅读科研文献的一些做法
  13. csu 1812: 三角形和矩形 凸包
  14. 修改pip install镜像源
  15. OneProxy中间件生产使用经验视频分享
  16. 排名:开源免费的小程序商城源码-LaiKe
  17. 调用新浪微博开放平台接口
  18. 根据结束时间和开始时间计算天数
  19. python环境搭建.
  20. 深度学习图像处理目标检测图像分割计算机视觉 07--图像检索

热门文章

  1. springboot 删除路径下面所有文件_[原创]springboot 中 resources 资源目录里面的文件夹压缩下载...
  2. FFmpeg 源码导读 —— H264码流格式分析
  3. 编译内核使tilera支持网桥和netfilter功能
  4. ADL SDK V10.2 中文文档
  5. 如何当好一个师长之软件开发篇
  6. Python模块——标准库\开源模块\自定义模块
  7. EMC EMI EMS 介绍
  8. 《神经网络与深度学习》基础篇
  9. java迷宫注释_写下走迷宫游戏的注释
  10. 平均指标指数与平均数指数的区别