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

本文介绍了UNIX系统中用户和组的身份管理机制,包括口令文件、阴影口令、组文件等内容。此外还讲解了时间及日期相关例程,如获取时间、日期,以及登录账户记录等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

6.2 口令文件

1、UNIX系统口令文件包含如下字段,这些字段包含在<pwd.h>中定义的passwd结构中。
这里写图片描述

2、口令文件/etc/passwd文件是一个ASCII文件。字段直接用冒号分隔。

3、关于这些登录项,有下列各点:
(1)通常有一个用户名为root的登录项,其用户ID是0(超级用户)
(2)加密口令字段包含了一个占位符。
(3)口令文件中的某些字段可能是空。
(4)shell字段包含一个可执行程序名
(5)为了阻止一个特定用户登录系统,除使用/dev/null外,还有若干种替代方法:
常见的一种是,将/bin/false用作登录shell。它简单地以不成功(非0)状态终止。
另一种常见方法是,用/bin/true禁止一个账户。它所做的一切都是以成功(0)状态终止。
某些系统提供nologin命令,它打印可定制的出错信息,然后以非0状态终止。

(6)使用nobody用户名的一个目的是,使任何人都可登录至系统。该用户ID和组ID只能访问人人皆可读、写的文件。

4、POSIX.1 定义两个获取口令文件项的函数。在给出用户登录名或数值用户ID后,这两个函数就能查看相关项:

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

(1)getpwuid函数由ls(1)程序使用,它将i节点中的数字用户ID映射为用户登录名。在键入登录名时,getpwnam函数由login(1)程序使用。
(2)这两个函数都返回一个指向passwd结构的指针,passwd结构通常是函数内部的静态变量,只要调用任一相关函数,其内容就会被重写。

3、查看用户名或用户ID、或是查看整个口令文件,可用下列3个函数:

#include <pwd.h>
struct  passwd *getpwent(void);
                                        返回值:若成功,返回指针;若出错或到达文件尾端,返回NULL。
void setpwent(void); 
void endpwent(void);

(1)调用getpwent时,它返回口令文件中的下一个记录项。每次调用此函数都会重写该结构。
在第一次调用该函数时,它打开所有使用的各个文件。在使用本函数时,对口令文件中各个记录项的安排顺序并无要求。

(2)函数setpwent反绕它所使用的文件。

(3)endpwent则关闭这些文件。

4、程序给出了getpwnam函数的一个实现。

/*************************************************************************
    > File Name: ins6_2.c
    > Author: Elliot
 ************************************************************************/

/*
 * 程序给出了getpwnam函数的一个实现
 */

#include <pwd.h>
#include <cstddef>
#include <string.h>

struct  passwd  *
getpwname(const char *name)
{
    struct  passwd  *ptr;

    setpwent();
    while ((ptr = getpwent()) != NULL)
        if (strcmp(name, ptr->pw_name) == 0)
            break;                  /*  found a match  */
    endpwent();
    return(ptr);                    /*  ptr is NULL if no match found  */
}

6.3 阴影口令

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

2、现在,某些系统将加密口令存放在另一个通常成为阴影口令(shadow password)的文件中。该文件至少要包含用户名和加密口令。与该口令相关的其他信息也可以存放在该文件中:
这里写图片描述

(1)只有用户登录名和加密口令两个字段是必须的。其他的字段控制口令更改的频率,或者说口令的衰老以及账户仍然处于活动状态的时间。

4、在Linux3.2.0 和 Solaris 10中,与访问口令文件的一组函数相类似,由另一组函数可用于访问阴影口令文件。

#include <shadow.h>
struct spwd *getspnam(const char *name);
struct spwd *getspent(void)
                                        两个函数返回值:若成功,返回指针;若出错,返回NULL。
void setspent(void);        
void endspent(void);

6.4 组文件

1、UNIX组文件(POSIX.1称其为组数据库)包含了如下字段,这些字段包含在<grp.h>所定义的group结构中.
这里写图片描述
(1)gr_mem是一个指针暑促,其中每个指针指向一个属于该组的用户名。该数组以null指针结尾。

2、两个由POSIX.1定义的函数来查看组名或数值组ID

#include <grp.h>
struct group getgrid(gid_t gid);
struct group getgrnam(const char *name);
                                        两个函数的返回值:若成功,返回指针;若出错,返回NULL。

3、如果需要搜索整个组文件,则须使用另外几个函数。下列3个函数类似于针对口令文件的3个函数:

#include <grp.h>
struct group *getgrent(void);   
                                        返回值:若成功,返回指针;若出错或到达文件尾端,返回NULL。
void setgrent(void);
void endgrent(void);

(1)setgrent函数打开组文件(如若它尚未被打开)并反绕它。
(2)getgrent函数从组文件中读下一个记录,如若该文件尚未打开,则先打开它。
(3)endgrent函数关闭组文件。

6.6 附属组ID

1、为了获取和设置附属组ID,提供了下列3个函数

#include <unistd.h>
int getgroups(int gidsetsize, gid_t grouplist[]);
                                        返回值:若成功,返回附属组ID数量;若出错,返回-1#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 and Solaris  */
#include <unistd.h>     /*  on FreeBSD and Mac OS X  */
int initgroups(const char *username, gid_t basegid);
                                        两个函数的返回值:若成功,返回0;若出错,返回-1

(1)getgroups将进程所属的各附属组ID填写到grouplist中,填写入该数组的附属组ID数最多为gidsetsize个。实际填写到数组中的附属组ID数由函数返回。

(2)若gidsetsize为0,则函数只返回附属组ID数,而对数组grouplist则不做修改(这使调用者可以确定grouplist数组的长度,以便进行分配)。

(3)setgroups可由超级用户调用以便调用进程设置附属组ID表。grouplist是组ID数组,而ngroups说明了数组中的元素数。ngroups的值不能大于NGROUPS_MAX。

(4)通常,只有initgroups函数调用setgroups,initgroups读整个组文件(用前面的getgrent、setgrent和endgrent),然后对username确定的成员关系。然后,它调用setgroups,以便该用户初始化附属组ID表。

(5)因为initgroups要调用setgroups,所以只有超级用户才能调用initgroups。

6.8 登录账户记录

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

struct utmp {
               short   ut_type;              /* Type of record */
               pid_t   ut_pid;               /* PID of login process */
               char    ut_line[UT_LINESIZE]; /* Device name of tty - "/dev/" */
               char    ut_id[4];             /* Terminal name suffix,
                                                or inittab(5) ID */
               char    ut_user[UT_NAMESIZE]; /* Username */
               char    ut_host[UT_HOSTSIZE]; /* Hostname for remote login, or
                                                kernel version for run-level
                                                messages */
               struct  exit_status ut_exit;  /* Exit status of a process
                                                marked as DEAD_PROCESS; not
                                                used by Linux init (1 */
               /* The ut_session and ut_tv fields must be the same size when
                  compiled 32- and 64-bit.  This allows data files and shared
                  memory to be shared between 32- and 64-bit applications. */
           #if __WORDSIZE == 64 && defined __WORDSIZE_COMPAT32
               int32_t ut_session;           /* Session ID (getsid(2)),
                                                used for windowing */
               struct {
                   int32_t tv_sec;           /* Seconds */
                   int32_t tv_usec;          /* Microseconds */
               } ut_tv;                      /* Time entry was made */
           #else
                long   ut_session;           /* Session ID */
                struct timeval ut_tv;        /* Time entry was made */
           #endif

               int32_t ut_addr_v6[4];        /* Internet address of remote
                                                host; IPv4 address uses
                                                just ut_addr_v6[0] */
               char __unused[20];            /* Reserved for future use */
           };

2、登录时,login程序填写此类结构,然后将其写入到utmp文件中。同时也将其添加到wtmp文件中。

3、注销时,init进程将utmp文件相应的记录擦除(每个字节都以null字节),并将一个新纪录添写到wtmp文件中。

4、在wtmp文件的注销记录中,ut_name字段清除为0.

5、再系统再启动时,以及更改系统时间和日期的前后,都在wtmp文件中追加写特殊的记录项。

6.6 系统标识

1、POSIX.1定义了uname函数,它返回与主机和操作系统有关的信息。

#include <sys/utsname.h>
int uname(struct utsname *name);
                                        返回值:若成功,返回非负值;若出错,返回-1
struct utsname
  {
    /* Name of the implementation of the operating system.  */
    char sysname[_UTSNAME_SYSNAME_LENGTH];

    /* Name of this node on the network.  */
    char nodename[_UTSNAME_NODENAME_LENGTH];

    /* Current release level of this implementation.  */
    char release[_UTSNAME_RELEASE_LENGTH];
    /* Current version level of this release.  */
    char version[_UTSNAME_VERSION_LENGTH];

    /* Name of the hardware type the system is running on.  */
    char machine[_UTSNAME_MACHINE_LENGTH];

#if _UTSNAME_DOMAIN_LENGTH - 0
    /* Name of the domain of this node on the network.  */
# ifdef __USE_GNU
    char domainname[_UTSNAME_DOMAIN_LENGTH];
# else
    char __domainname[_UTSNAME_DOMAIN_LENGTH];
# endif
#endif
  };

2、返回主机名的函数

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

(1)namelen参数指定name缓冲区长度,若提供足够的空间,则通过name返回的字符串以null字节结尾。如果没有足够的空间,则没有说明通过name返回的字符串是否以null结尾。

6.10 时间和日期例程

1、time函数返回当前时间和日期

#include <time.h>
time_t time(time_t *calptr);
                                        返回值:若成功,返回时间值;若出错,返回-1

(1)时间值作为函数值返回。如果参数非空,则是兼职也存放在由calptr指向的单元内。

2、clock_gettime函数可用于获取指定时钟的时间。

#include <sys/time.h>
int clock_gettime(clockid_t clock_id, struct timespec *tsp);
                                        返回值:若成功,返回0,;若出错,返回-1

下面是clockid_t类型的取值:
这里写图片描述
(1)clock_gettime可能比time函数得到更高精度的时间值。

3、clock_getres函数把参数tsp指向的timespec结构初始化与clock_id参数对应的时钟精度

#include <sys/time.h>
int clock_getres(clockid_t clock_id, struct timepsec *tsp);

(1)如:精度为1毫秒tv_sec字段就是0,tv_nsec字段就是1000000

4、对特定时钟设置时间,可以调用clock_settime函数。

#include <sys/time.h>
int clock_settime(clockid_t clock_id, const struct timespec *tsp);
                                        返回值:若成功,返回0;若出错,返回-1

5、函数gettimeofday提供了更高的精度(可到微妙级)

#include <sys/time.h>
int gettimeofday(struct timeval *restrict tp, void *restrict tzp);
                                        返回值,总是返回0

(1)tzp的唯一合法值是NULL。

6、两个函数localtime和gmtime将日历时间转换为分解时间,并将这些存放在一个tm结构中。

#include <time.h>
struct tm *gmtime(const time_t *calptr);
struct tm *localtime(const time_t *calptr);
                                    两个函数的返回值:指向分解的tm结构的指针;若出错,返回NULL。
tm结构:
struct tm {
               int tm_sec;    /* Seconds (0-60) */
               int tm_min;    /* Minutes (0-59) */
               int tm_hour;   /* Hours (0-23) */
               int tm_mday;   /* Day of the month (1-31) */
               int tm_mon;    /* Month (0-11) */
               int tm_year;   /* Year - 1900 */
               int tm_wday;   /* Day of the week (0-6, Sunday = 0) */
               int tm_yday;   /* Day in the year (0-365, 1 Jan = 0) */
               int tm_isdst;  /* Daylight saving time */
};

(1)localtime将日历时间转换为本地时间(考虑到本地失去和夏令时标志)
(2)gmtime将日历时间转换成自协调同意时间的年、月、日、时、分、秒、周日分解结构

这里写图片描述

7、函数mktime以本地时间的年、月、日作为参数,将其变换成time_t值。

#include <time.h>
time_t mktime(struct tm *tmptr);
                                        返回值:若成功,返回日历时间;若出错,返回-1

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

(1)strltime_l允许调用者将区域指定为参数。strftime使用通过TZ环境变量指定的区域。

(2)tmptr参数是要格式化的时间值,由一个指向分解时间值tm结构的指针说明。

(3)格式化结果存放在一个长度为maxsize个字符的buf数组中。如果buf长度足以存放格式化结果以及一个null终止符,则该函数返回在buf中存放的字符数(不包括null终止符)。否则,该函数返回0。

(4)format参数控制时间值的格式:
这里写图片描述
这里写图片描述

9、使用strftime打印包含当前日期和时间的字符串。

/*************************************************************************
    > File Name: ins6_11.c
    > Author: Elliot
 ************************************************************************/

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

10、strptime函数是strftime函数反过来的版本,把字符串时间转换成分解时间

#include <time.h>
char *strptime(const char *restrict buf, const char *restrict format,
                struct tm *restrict tmptr);
                                    返回值,指向上一次解析的字符串的下一个字符串的指针,否则,返回NULL。

这里写图片描述
这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值