系统数据文件和信息

  • 口令文件

UNIX系统口令文件是etc/passwd,包含了图1所示的各字段,这些字段包含在<pwd.h>中定义的passwd结构中。

图1 各系统etc/passwd文件中的字段

Ubuntu系统的passwd结构(在/usr/include/pwd.h中)如下:

图2 Ubuntu中的passwd结构

Ubuntu系统的口令文件(etc/passwd)实例如图3所示,各字段用:分隔。

图3 Ubuntu系统口令文件实例

用户名为root的登陆项,其用户ID是0(超级用户)。

加密口令字段包含了一个占位符X,现在加密口令字存放在另一个文件中。

shell字段包含了一个可执行程序,这个程序被用作该用户的登陆shell。用bin/false用作登陆shell,是为了防止特定用户登陆系统。

使用nobody用户名的目的是,使任何人都可登陆至系统,但其用户ID(65534)和组ID(65534)不提供任何特权,只可访问人人可读可写的文件。

finger命令支持注释字段中的附加信息,各部分用逗号分隔,如Ader,,,中Ader为姓名,其余为空。

图4 finger实例

使用vipw命令,允许管理员使用该命令编辑口令文件,并且确保所作的更改与其他相关文件保持一致。

 

获取口令文件项的函数有getpwuid和getpwnam:

#include <pwd.h>

struct passwd *getpwuid(uid_t uid);
struct passwd *getpwname(const char *name);

 getpwuid由ls(1)程序使用,getpwnam函数由login(1)函数使用。

 

如果要查看整个口令文件:

#include <pwd.h>

struct passwd *getpwent(void);

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

调用getpwent,返回口令文件的下一个记录项passwd结构的指针,每次调用此函数都重写该结构。

函数setpwent用来将getpwent的读写地址指向密码文件开头,但getpwent对口令文件中各个记录项的安排顺序并无要求(有些是hash算法)。

endpwent则关闭这些文件,getpwent查看完口令文件后,一定要调用endpwent关闭这些文件。

 

  • 阴影口令

加密口令是经单向加密算法处理过的用户口令副本,算法是单向的,不能从加密口令猜测到原来的口令。

为了防止对口令进行猜测,系统将口令存放在另一个通常称为阴影口令(etc/shadow)的文件中,而不是在etc/passwd文件中。

对口令加密将产生更长的加密口令字符串,因此口令文件(etc/passwd)中的加密口令字段,用单个字符(X)可以确保任一加密口令都不会与其匹配。

阴影口令文件至少要包含用户名和加密口令

图5 阴影口令文件(etc/shadow)中的字段

阴影口令文件不是一般用户可以读取的:

图6 阴影口令文件读取

如图6所示,需要sudo才能读取etc/shadow文件。

login和passwd命令,是设置用户ID为root的程序,可以访问shadow文件。

 

在Linux 3.2.0和Solaris 10中,与反问口令文件的一组函数相似,有另一组函数可用于访问阴影口令文件。

#include <shadow.h>

struct spwd *getspnam(const char *name);
struct spwd *getspent(void);

void setspent(void);
void endspent(void);

 

  • 组文件

UNIX组文件包含了图7中所示字段,这些字段在grp.h中定义。

图7 组文件中的字段

Ubuntu中grp.h中gourp结构的定义,如图8所示:

图8 group结构的定义

Ubuntu中的组文件etc/group,如图9所示:

图9 组文件etc/group

获取组文件项的函数有getgrgid和getgrnam:

#include <grp.h>

struct group *getgrgid(gid_t gid);
struct group *getgrnam(const char *name);

 

如果要搜索整个组文件:

#include <grp.h>

struct group *getgrent(void);
void setgrent(void);
void endgrent(void);

与口令文件相关函数功能相似。

 

  • 附属组ID

在口令文件中,用户对应的组ID是有效组ID,如图3所示,ader用户对应的有效组ID为1000。

在组文件中,如图9所示,可以看到有多个组包含了ader用户,adm/cdrom/sudo/dip/plugdev等,这些都是ader的附属组ID。

获取和设置附属组ID,有三个函数:

#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);

#include <grp.h> //on Linux
#include <unistd.h> //on FreeBSD, Mac OS X, and Solaris
int setgroups(int ngroups, const gid_t grouplist[]);

#include <grp.h> //on Linux
#include <unistd.h> //on FreeBSD, Mac OS X
int initgroups(const char *username, gid_t basegid)

 通常只有initgroups函数调用setgroups,initgroups读整个组文件(etc/group),然后对username确定其组的成员关系。

最后调用setgroups初始化附属组ID表,initgroups也在附属组ID表中包括了basegid。

 

  • 其他数据文件

图10 访问系统数据文件例子

etc/services记录各网络服务器所提供服务。

etc/protocols记录协议信息。

etc/networks记录网络信息。

对这些数据文件至少有3个函数,get/set/end函数。另外还提供搜索指定键的函数。

 

  • 登录账户记录

utmp文件记录当前登录到系统的各个用户,wtmp文件跟踪各个登录和注销事件。

图11 登录记录结构

登录时,login填写此结构,写入到utmp文件中,同时也添加到wtmp文件中。

注销时,init进程将utmp文件相应记录擦除,添加一个新的记录到wtmp。

系统再启动/系统时间和日期更改,都将在wtmp文件中追写特殊的记录项。

相应的文件:var/run/utmp和var/log/wtmp

 

  • 系统标识

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

图12 uname函数

函数将填写utsname结构,结构最少需要提供的字段如下:

struct utsname {
    char sysname[];
    char nodename[];
    char release[];
    char version[];
    char machine[];
}

 utsname结构中的信息通常可用uname命令打印,uname的用法如图13所示。

图13 uname命令用法

gethostname函数,可用来获取主机名,该名字通常是TCP/IP网络上主机的名字。

#include <unistd.h>

int gethostname(char *name, int namelen);

命令hostname可用来获取和设置主机名,获取主机名用法如图14所示。

图14 hostname获取主机名

 

  • 时间和日期Routines

UNIX内核计算的是继协调时间(1970年1月1日)以来经过的秒数,这种秒数用time_t表示,称为日历时间。

获取到日历时间后,需要转换为分解时间,生成人可读的时间和日期。

时间相关的处理函数如图15所示:

图15 时间和日期相关函数

  • 日历时间

从内核中获取日历时间,有三个函数time/gettimeofday/clock_gettime

time函数返回的是time_t结构,精度为秒;

clock_gettime函数返回的是timespec结构,在系统支持高精度时间值的情况下,可获得纳秒精度时间值;

gettimeofday函数返回的是timeval结构,可获得微秒级的精度时间;【SUSv4指定gettimeofday函数为弃用,不过一些程序仍然使用这个函数】

函数原型如下:

#include <time.h>
time_t time(time_t *calptr);

#include <sys/time.h>
int clock_gettime(clockid_t clock_id, struct timespec *tsp);
//获取指定时钟的时间
//ID为CLOCK——REALTIME时,与time函数类似功能

#include <sys/time.h>
int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
//tzp的唯一合法值为NULL

timespec和timeval结构,截取其中的tv_sec就能转换为time_t。

 

如果想要知道某个时钟的精度,可调用clock_getres函数来获取

#include <sys/time.h>
int clock_getres(clockid_t clock_id, struct timespec *tsp);
//tsp指向的timespec结构,被初始化为与clock_id参数对应的时钟精度。
//如精度为1毫秒,tv_sec字段为0,tv_nsec字段为1000000

 

  • 分解时间

分解时间结构为tm,如下:

struct tm { /* a broken-down time */
    int tm_sec; /* seconds after the minute: [0 - 60] */
    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; /* months since January: [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 */
};

将日历时间转化为分解时间,有gmtime/localtime函数。

localtime函数将日历时间转为为本地时间(考虑到本地时区和夏令时标志)。如果定义了TZ,将使用该值替代系统默认时区。

gmtime将日历时间转为为协调统一时间。

#include <time.h>
struct tm *gmtime(const time_t *calptr);
struct tm *localtime(const time_t *calptr);

 

将分解时间转为为日历时间,有函数mktime。

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

mktime和localtime一样,受变量TZ的影响。

 

  • 格式化时间

strftime/strftime_l函数将输出格式化的字符串时间。

#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);

tmptr指向的分解时间,按format的格式要求,输出相应的字符串到buf

strftime_l可通过参数locale指定时区,strftime通过TZ环境变量指定时区

format控制时间值格式,如下:

图16 strftime的格式转换说明

 

strptime函数是strftime的逆过程,把字符串时间转为分解时间。

#include <time.h>
char *strptime(const char *restrict buf, const char *restrict format,
               struct tm *restrict tmptr);

根据format指定的格式,将buf指向的字符串时间,转为分解时间tmptr

格式说明如图17:

图17 strptime函数的格式转换说明

posted @ 2019-04-05 18:01  我是老邱  阅读(590)  评论(0编辑  收藏  举报