当前位置: 首页 > news >正文

UNIX环境高级编程-第六章-系统数据文件和信息

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;
}

相关文章:

  • 【初学者入门C语言】之习题篇(二)
  • [架构之路-14]:目标系统 - 硬件平台 - CPU、MPU、NPU、GPU、MCU、DSP、FPGA、SOC的区别
  • Linux下brk、sbrk实现一个简易版本的malloc
  • 一、CSS选择器与权重[基础选择器、结构选择器、属性选择器、伪类选择器]
  • flutter系列之:深入理解布局的基础constraints
  • 【C语言进阶】动态内存管理及柔性数组
  • 网课查题接口系统
  • C语言基础知识入门
  • 闲暇之际敲敲代码,记录Leetcode刷题Day-01
  • 2021年下半年信息安全工程师上午真题及答案解析
  • Dinky,让 Flink SQL 纵享丝滑
  • Docker | docker容器导出以及常见问题的处理
  • 【node进阶】深度解析之Express框架入门
  • 【重温Linux】一、Ubuntu系统一些常识性的东西(这节持续更新)
  • mysql group_concat 与 union 联合查询漏洞,数据列最大长度为341
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • django开发-定时任务的使用
  • github指令
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • js正则,这点儿就够用了
  • js中forEach回调同异步问题
  • MD5加密原理解析及OC版原理实现
  • Spring Cloud(3) - 服务治理: Spring Cloud Eureka
  • 阿里中间件开源组件:Sentinel 0.2.0正式发布
  • 关于字符编码你应该知道的事情
  • ------- 计算机网络基础
  • 前端技术周刊 2019-01-14:客户端存储
  • 前嗅ForeSpider教程:创建模板
  • 如何优雅的使用vue+Dcloud(Hbuild)开发混合app
  • 什么软件可以剪辑音乐?
  • 手写双向链表LinkedList的几个常用功能
  • 物联网链路协议
  • 追踪解析 FutureTask 源码
  • 自定义函数
  • 第二十章:异步和文件I/O.(二十三)
  • ​【已解决】npm install​卡主不动的情况
  • ​LeetCode解法汇总2583. 二叉树中的第 K 大层和
  • ​Z时代时尚SUV新宠:起亚赛图斯值不值得年轻人买?
  • $(document).ready(function(){}), $().ready(function(){})和$(function(){})三者区别
  • $(function(){})与(function($){....})(jQuery)的区别
  • (rabbitmq的高级特性)消息可靠性
  • (二)hibernate配置管理
  • (六)vue-router+UI组件库
  • (十)【Jmeter】线程(Threads(Users))之jp@gc - Stepping Thread Group (deprecated)
  • (十八)三元表达式和列表解析
  • (转)负载均衡,回话保持,cookie
  • .NET项目中存在多个web.config文件时的加载顺序
  • .Net转Java自学之路—基础巩固篇十三(集合)
  • /var/spool/postfix/maildrop 下有大量文件
  • @Mapper作用
  • @Resource和@Autowired的区别
  • @开发者,一文搞懂什么是 C# 计时器!
  • @四年级家长,这条香港优才计划+华侨生联考捷径,一定要看!
  • [ C++ ] STL_vector -- 迭代器失效问题
  • [ IO.File ] FileSystemWatcher