主機資訊
正如我們可以確定使用者資訊一樣,程式也可以確定其啟動並執行電腦的資訊。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章。