主机信息
正如我们可以确定用户信息一样,程序也可以确定其运行的计算机的信息。uname命令提供了这些信息。uname同时也作为一个系统调用来在一个C程序中提供同样的信息,我们可以使用man 2 uname来查看详细的信息。
许 多情况都需要主机信息。我们也许希望依据在网络中一个程序所运行的机器的名字来自定义其行为,也就是说,是一个学生的机器还是一个管理员的机器。为了授权 的目的,我们也许希望限制一个程序只在一台机器上运行。所有这些都意味着我们需要一个方法来确定程序所运行的机器的信息。
如果系统安装了网络组件,那么我们可以非常容易的通过gethostname函数来得到其网络名:
#include <unistd.h>
int gethostname(char *name, size_t namelen);
gethostname函数将机器的网络名写入name所指的字符串中。假定这个字符串的长度至少为namelen字符长。如果成功,gethostname函数会返回0,否则会返回-1。
我们可以由uname系统调用得到关于主机的更为详细的信息:
#include <sys/utsname.h>
int uname(struct utsname *name);
uname函数将主机信息写入由name参数所指向的结构。utsname结构定义在sys/ustname.h中,大多数至少包含下列成员:
成员 描述
char sysname[] 操作系统名
char nodename[] 主机名
char release[] 系统发行级别
char version[] 系统版本号
char machine[] 硬件类型
如果成功,uname函数会返回一个非负数,否则返回-1,同时设置errno来指示错误。
试验--主机信息
下面是一个程序,hostget.c,来得到主机信息:
#include <sys/utsname.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
char computer[256];
struct utsname uts;
if(gethostname(computer, 255) != 0 || uname(&uts) < 0) {
fprintf(stderr, “Could not get host information/n”);
exit(1);
}
printf(“Computer host name is %s/n”, computer);
printf(“System is %s on %s hardware/n”, uts.sysname, uts.machine);
printf(“Nodename is %s/n”, uts.nodename);
printf(“Version is %s, %s/n”, uts.release, uts.version);
exit(0);
}
程序会产生如下的Linux特定的输出。如果我们的机器处在网络中,我们就会发现一个包含网络的扩展主机名:
$ ./hostget
Computer host name is beast
System is Linux on i686 hardware
Nodename is beast
Version is 2.4.19-4GB, #1 Wed Nov 27 00:56:40 UTC 2002
工作原理
这个程序会调用gethostname函数来得到主机的网络名。在前面的例子中,其得到的名字为tilde。更为详细的信息是由unmae函数调用得到的。注意,函数uname所返回的字符串是依赖于实现的;在这个例子中,版本号包含所编译的内核的数据。
由gethostid函数可以得到一个唯一的主机号:
#include <unistd.h>
long gethostid(void);
gethostid函数会返回一个唯一的主机号。授权管理通常使用这个函数来确保软件程序只可以运行一个具有合法授权的机器上。在Sun工作站上,他会返回一个机器在制造时在不可变内存中设置的一个数,所以,对于系统硬件,这是唯一的。
其他的系统,例如Linux,会返回一个基于机器网络地址的值,通常对于授权是足够安全的。
日志
许多程序需要记录他们的动作。系统程序通常将信息写入终端或是一个日志文件。这些信息也许指示错误,警告,或是关于系统状态的更一般的信息。例如,su程序也许会记录一个用户尝试获得超级权限并且失败的事实。
通 常,这些日志信息会记录在一个目录中的一个系统文件中。这也许是/usr/adm或者是/var/log。在通常的Linux安装中, /var/log/messages文件包含系统信息,/var/log/mail包含其他的邮件系统的日志信息,/var/log/debug包含调试 信息。我们可以在/etc/syslog.conf文件中查看我们的系统配置。
如下面的是一些日志信息:
Here are some sample log messages:
Feb 8 08:38:37 beast kernel: klogd 1.4.1, log source = /proc/kmsg started.
Feb 8 08:38:37 beast kernel: Inspecting /boot/System.map-2.4.19-4GB
Feb 8 08:38:37 beast kernel: Loaded 20716 symbols from /boot/System.map-
2.4.19-4GB.
Feb 8 08:38:37 beast kernel: Symbols match kernel version 2.4.19.
Feb 8 08:38:37 beast kernel: Loaded 372 symbols from 17 modules.
Feb 8 08:38:37 beast kernel: Linux Tulip driver version 0.9.15-pre11 (May 11,
2002)
Feb 8 08:38:37 beast kernel: PCI: Found IRQ 5 for device 00:0d.0
Feb 8 08:38:37 beast kernel: eth0: ADMtek Comet rev 17 at 0xe400,
00:04:5A:5F:46:52, IRQ 5.
...
Feb 8 08:39:20 beast /usr/sbin/cron[932]: (CRON) STARTUP (fork ok)
Feb 8 09:50:35 beast su: (to root) neil on /dev/pts/4
从这里我们可以看到日志记录的信息。前面的一些是当内核启动并检测安装的硬件时由内核自身报告的。任务调用度器,cron,报告他正是启动。最后,su程序报告用户neil访问一个超级帐户。
一些Unix系统并不会以这种可读的方式提供日志信息,而是提供管理员工具来读取系统事件的数据库。我们可以查看我们的系统文档来得到详细的信息。
尽管系统信息的格式和存储会改变,但是产生这些信息的方法是标准的。Unix系统使用syslog函数来为日志信息的产生提供一个接口:
#include <syslog.h>
void syslog(int priority, const char *message, arguments...);
syslog函数将日志信息发送到日志工具。每一个消息有一个priority参数,他是由安全级别与工具值的位或者到的。安全级别控制这些消息如何产生,而工具值记录消息源。
工具值包括LOG_USER,用来表明消息来自于一个用户程序,LOG_LOCAL0,LOG_LOCAL1直到LOG_LOCAL7,这可以由本地管理员赋于其意义。
下表列出以降序表达的安全级别:
安全级别 描述
LOG_EMERG 一个紧急事件
LOG_ALERT 高优先级问题,例如数据库崩溃
LOG_CRIT 紧急错误,例如硬件失败
LOG_ERR 错误
LOG_WARNING 警告
LOG_NOTICE 需要引起注意的特殊情况
LOG_INFO 信息消息
LOG_DEBUG 调试消息
依据于我们的系统配置,LOG_EMERG消息也许会广播给所有用户,LOG_ALERT消息也许会用邮件发送给管理员,LOG_DEBUG消息会被忽略,而其他的消息会被写入日夜。我们可以编写一个简单的程序,在我们希望创建一个日志消息时可调用syslog日志程序。
由syslog 所创建的日志消息由一个消息头与消息体组成。消息头是由程序指示器与日期和时间创建的。消息体是由到syslog的消息参数创建的,其作用类似于 printf格式化字符串。传递给syslog的其余参数用于在消息字符串中指定printf样式的约定。另外,指示器%m可以用于插入与当前的错误变量 errno相关联的错误消息字符串。这对于记录错误消息十分有用。
试验--syslog
在这个程序中,我们试图打开一个不存在的文件:
#include <syslog.h>
#include <stdio.h>
int main()
{
FILE *f;
f = fopen(“not_here”,”r”);
if(!f)
syslog(LOG_ERR|LOG_USER,”oops - %m/n”);
exit(0);
}
当我们编译并运行程序syslog.c时,我们不会看到任何输出,但是文件/var/log/messages中现在却包含下面的记录:
Feb 8 09:59:14 beast syslog: oops - No such file or directory
工作原理
在这个程序中,我们试图打开一个不存在的文件。当操作失败时,我们会看到syslog在系统日志中记录这件事情。
我们可以注意到日志消息并没有指明是哪个程序调用日志程序;他只是记录syslog被一条消息调用的事实。%m格式约定已经被一个错误描述所代替,在这个例子中,这个文件不可以找到。这与只是打印出原始的错误号要有用得多。
在syslog.h中还定义了其他的一些可以用来修改日志程序行为的函数。他们是:
#include <syslog.h>
void closelog(void);
void openlog(const char *ident, int logopt, int facility);
int setlogmask(int maskpri);
我 们可以通过调用openlog函数来修改我们的日志消息所表示的方式。这会让我们设置一个字符串,ident,这会添加到我们日志消息的前面。我们可以使 用他来表明是哪个程序创建这条消息。facility参数记录了一个syslog调用将来要使用的参数值。默认为LOG_USER。logopt参数配置 了将来的syslog调用的行为。他们下面的零个或是多个位或的结果:
logopt参数 描述
LOG_PID 在消息中包含进程标识符,这是由系统分配给进程的唯一标识号
LOG_CONS 如果消息不可以被记录就发送到终端
LOG_ODELAY 在第一次调用时打开log程序
LOG_NDELAY 立即打开log程序,而不是第一次调用时
openlog函数会分配并且打开一个用于写入日志程序的文件描述符。我们可以调用closelog函数将其关闭。注意,在调用syslog函数之前并不需要调用openlog,因为syslog本身会在需要的时候打开。
我们可以使用setlogmask函数来设置日志掩码从而控制我们日志消息的优先级别。以后那些使用在日志掩码中没有设置的优先级调用syslog函数都会被拒绝,例如,我们可以使用这个方法关闭LOG_DEBUG消息,而不需要修改程序体。
我们可以使用LOG_MASK(priority)来为日志消息创建掩码,这会创建一个只包含一个优先级的掩,或是LOG_UPTO(priority),这会创建一个包含直到priority且包含priority的所有优先级的掩码。
试验--logmask
在这个例子,我们会实际的看一下logmask的使用:
#include <syslog.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
int logmask;
openlog(“logmask”, LOG_PID|LOG_CONS, LOG_USER);
syslog(LOG_INFO,”informative message, pid = %d”, getpid());
syslog(LOG_DEBUG,”debug message, should appear”);
logmask = setlogmask(LOG_UPTO(LOG_NOTICE));
syslog(LOG_DEBUG,”debug message, should not appear”);
exit(0);
}
这个logmask.c程序并不会产生任何输出,但是在特定的Linux系统上,在/var/log/messages的结束处,我们会看到下面的行:
Feb 8 10:00:50 beast logmask[1833]: informative message, pid = 1833
配置用来接收调试日志消息的文件应包含下面的行:
Feb 8 10:00:50 beast logmask[1833]: debug message, should appear
工作原理
程 序使用他的名字,logmask来初始化日志程序,并且请求消息中包含进程标识符。信息消息被记录到/var/log/messages,而调试信息记录 到/var/log/debug文件中。第二个调试信息并不会出现,因为我们调用setlogmask函数忽略了所有优先级小于LOG_NOTICE的消 息。
如果我们的安装没有记录调试信息的功能,或者是进行了不同的配置,那么我们也许不会看到调试消息的输出。为了允许所有的调试消息,在/etc/syslog.conf的最后加入下面一行并重启。然而,一定要查看我们的系统文档来得到确切的配置方法:
*.debug /var/log/debug
logmask.c使用了getpid函数,他与其相关的getppid的函数定义如下:
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
这两个函数会返回调用进程的及其父进程的进程标识符。要了解更多的关于PID的内容,可以查看第11章。