Linux程式設計之一(轉)

post0發表於2007-08-11
Linux程式設計之一(轉)[@more@]

Linux程式設計 - 1.fork

(2001-05-24 14:08:00)

在UNIX程式設計中,學會fork()的運用,算是相當基本的功夫。

fork()及signal經常運用在daemon守護神這一類常駐程式,另外像a4c.tty/yact/chdrv這些中文終端機程式也有用到,一般如Mozilla/Apache/Squid等大程式幾乎都一定會用到。

--------------------------------------------------------------------------------

程式分歧fork()

fork()會產生一個與父程式相同的子程式,唯一不同之處在於其process id(pid)。

如果我們要撰寫守護神程式,或是例如網路伺服器,需要多個行程來同時提供多個連線,可以利用fork()來產生多個相同的行程。

函式宣告

pid_t fork(void);

pid_t vfork(void);

返回值:

-1 : 失敗。

0 : 子程式。

>0 : 將子程式的process id傳回給父程式。

在Linux下fork()及vfork()是相同的東西。

Linux程式設計- 2.fork, pthread, and signals

(2001-05-24 15:00:00)

雖然在UNIX下的程式寫作,對thread的功能需求並非很大,但thread在現代的作業系統中,幾乎都已經存在了。pthread是Linux上的thread函式庫,如果您要在Linux下撰寫多執行緒式,例如MP3播放程式,熟悉pthread的用法是必要的。

有關thread寫作,有兩本很好的書:

Programming with POSIX Threads

Multithreading Programming Techniques

另外有一份初學者的參考檔案Getting Started With POSIX Threads

pthread及signal都可以用一大章來討論。在這裡,我只談及最簡單及常用的技巧,當您熟悉這些基本技巧的運用後,再找一些專門深入探討pthread及signal程式寫作的書籍來研究。這些進階的寫法,用到的機會較少,將層次分明,學習速度應該會比較快。

--------------------------------------------------------------------------------

thread

我假設您對thread已經有一些基本的概念,因此,在此我將著重於如何實作。

函式宣告

int pthread_create(pthread_t * thread, pthread_attr_t * attr, void * (*start_routine)(void *), void * arg);

int pthread_join(pthread_t th, void **thread_return);

int pthread_detach(pthread_t th);

void pthread_exit(void *retval);

int pthread_attr_init(pthread_attr_t *attr);

資料結構

typedef struct

{

int detachstate;

int schedpolicy;

struct sched_param schedparam;

int inheritsched;

int scope;

} pthread_attr_t;

例一:

#include

#include

#include

#include

void * mythread(void *arg)

{

for (;;) {

printf("thread ");

sleep(1);

}

return NULL;

}

void main(void)

{

pthread_t th;

if (pthread_create(&th,NULL,mythread,NULL)!=0) exit(0);

for (;;) {

printf("main process ");

sleep(3);

}

}

執行結果:

./ex1

main process

thread

thread

thread

main process

thread

thread

thread

main process

thread

thread

thread

main process

Linux程式設計- 3.signals

(2001-05-24 16:47:48)

訊號處理

--------------------------------------------------------------------------------

訊號處理概說

送出訊號

接收訊號

訊號的處理

任務控制

--------------------------------------------------------------------------------

POSIX IPC

reliable/unreliable

reentrant

pending

sending signals

catching signals

manipulating

signal definitions

--------------------------------------------------------------------------------

訊號singals

訊號的處理可以用一大章來寫,涉及的層面也會深入整個作業系統中,我並不打算這樣做,因為您可能會越搞越迷糊。這裡我只告訴您如何接上訊號,在實用的層面上,這樣便很夠用了。您可以先利用這些基本的技巧來撰寫程式,等到有進一步高等應用的需要時,找一本較深入的UNIX Programming教材,專門研究signal的寫法。

一般簡單的signal寫法如下:

void mysignal(int signo)

{

/* my signal handler */

}

void initsignal(void)

{

struct sigaction act;

act.sa_handler = mysignal;

act.sa_flags = 0;

sigemptyset(&act.sa_mask);

sigaction(SIGHUP,&act,NULL);

sigaction(SIGINT,&act,NULL);

sigaction(SIGQUIT,&act,NULL);

sigaction(SIGILL,&act,NULL);

sigaction(SIGTERM,&act,NULL);

}

例一: lock.c

在fork的例三中提到,在daemon被殺掉時,需要在離開前,將/var/run/lock.pid刪除。這裡我們可以利用signal來處理這件事。

#include

#include

#include

#include

#define LOCK_FILE "/var/run/lock.pid"

void quit(int signo)

{

printf("Receive signal %d ",signo);

unlink(LOCK_FILE);

exit(1);

}

void main(void)

{

FILE *fp;

pid_t pid;

struct sigaction act;

if (access(LOCK_FILE,R_OK)==0) {

printf("Existing a copy of this daemon! ");

exit(1);

}

pid = fork();

if (pid>0) {

printf("daemon on duty! ");

fp = fopen(LOCK_FILE,"wt");

fprintf(fp,"%d",pid);

fclose(fp);

} else

exit(0); if (pid<0) {

printf("Can't fork! ");

exit(-1);

}

act.sa_handler = quit;

act.sa_flags = 0;

sigemptyset(&act.sa_mask);

sigaction(SIGTERM,&act,NULL);

sigaction(SIGHUP,&act,NULL);

sigaction(SIGINT,&act,NULL);

sigaction(SIGQUIT,&act,NULL);

sigaction(SIGUSR1,&act,NULL);

sigaction(SIGUSR2,&act,NULL);

for (;;) {

sleep(3);

}

}

編譯:

gcc -o ex1 lock.c

執行

./ex1

daemon on duty!

送訊號

我們先找出該守護神程式的pid

PID=`cat /var/run/lock.pid`

接下來利用kill來送訊號

kill $PID

Receive signal 15

程式將會結束,並且/var/run/lock.pid將會被刪除掉,以便下一次daemon再啟動。注意到如果quit函式內,沒有放exit(),程式將永遠殺不掉。

接下來送一些其它的訊號試試看。

./ex1

PID=`cat /var/run/lock.pid`

kill -HUP $PID

Receive signal 1

您可以自行試試

kill -INT $PID

kill -QUIT $PID

kill -ILL $PID

.

.

.

等等這些訊號,看看他們的結果如何。

訊號的定義

在/usr/include/signum.h中有各種訊號的定義

#define SIGHUP 1 &nb

Linux程式設計- 4.socket

(2001-05-24 17:24:26)

UNIX Socket Programming基本上是一本書名。Socket programming其實需要相當程度的基礎,我不想在這包山包海地,如果您需要徹底研究,可以買這本書來看。在此我想提供一些簡單的 Server/Client兩端的簡單寫法,讓你有個起點,做為進一步研究的基礎。很多涉及較複雜的內容的,我在這便不詳細說明,您可以照本宣科,照抄著用,稍微熟悉時,再細細研究。

--------------------------------------------------------------------------------

Client

int sock_connect(char *domain,int port)

{

int white_sock;

struct hostent * site;

struct sockaddr_in me;

site = gethostbyname(domain);

if (site==NULL) return -2;

white_sock = socket(AF_INET,SOCK_STREAM,0);

if (white_sock<0) return -1;

memset(&me,0,sizeof(struct sockaddr_in));

memcpy(&me.sin_addr,site->h_addr_list[0],site->h_length);

me.sin_family = AF_INET;

me.sin_port = htons(port);

return (connect(white_sock,(struct sockaddr *)&me,sizeof(struct sockaddr))<0) ? -1 : white_sock;

}

要由Client向伺服器端要求連線的步驟,首先您必須要找出對方的位址,可利用:

gethostbyname()

接下來要建立起一個socket,然後用這個socket來建立連線。

接下來我們利用這個簡單的socket程式來寫一個讀取WWW網頁的簡單瀏覽器(看html source)。

#include

#include

#include

#include

#include

#include

#include

int htconnect(char *domain,int port)

{

int white_sock;

struct hostent * site;

struct sockaddr_in me;

site = gethostbyname(domain);

if (site==NULL) return -2;

white_sock = socket(AF_INET,SOCK_STREAM,0);

if (white_sock<0) return -1;

memset(&me,0,sizeof(struct sockaddr_in));

memcpy(&me.sin_addr,site->h_addr_list[0],site->h_length);

me.sin_family = AF_INET;

me.sin_port = htons(port);

return (connect(white_sock,(struct sockaddr *)&me,sizeof(struct sockaddr))<0) ? -1 : white_sock;

}

int htsend(int sock,char *fmt,...)

{

char BUF[1024];

va_list argptr;

va_start(argptr,fmt);

vsprintf(BUF,fmt,argptr);

va_end(argptr);

return send(sock,BUF,strlen(BUF),0);

}

void main(int argc,char **argv)

{

int black_sock;

char bugs_bunny[3];

if (argc<2) return;

black_sock = htconnect(argv[1],80);

if (black_sock<0) return;

htsend(black_sock,"GET / HTTP/1.0%c",10);

htsend(black_sock,"Host: %s%c",argv[1],10);

htsend(black_sock,"%c",10);

while (read(black_sock,bugs_bunny,1)>0) { printf("%c",bugs_bunny[0]); }

close(black_sock);

}

編譯:

gcc -o ex1 client.c

執行

./ex1

--------------------------------------------------------------------------------

Server

Listen to a port

要建立起一個網路伺服器,第一步就是要"傾遠方",也就是要Listen。

以下是一般建立服務的方法:

int DaemonSocket;

struct sockaddr_in DaemonAddr;

int BindSocket(void)

{

DaemonSocket = socket(AF_INET,SOCK_STREAM,0);

if (DaemonSocket==-1) return 0;

DaemonAddr.sin_family = AF_INET;

DaemonAddr.sin_port = htons(DAEMON_PORT);

if (bind(DaemonSocket,&DaemonAddr,sizeof(DaemonAddr))<0) {

printf("Can not bind! ");

return 0;

}

if (listen(DaemonSocket,1024)!=0) {

printf("Can not listen! ");

return 0;

}

return 1;

}

Incoming call

要檢視是否有連線進來,可用以下方式:

int incoming_call(void)

{

fd_set sock;

struct timeval tv;

int t;

FD_ZERO(&sock);

FD_SET(DaemonSocket,&sock);

tv.tv_sec = 60; tv.tv_usec = 0;

t = select(DaemonSocket + 1,&sock,NULL,NULL,&tv);

if (t<=0||!FD_ISSET(DaemonSocket,&sock)) return 0;

printf("incoming... ");

return 1;

}

Connect Client

當我們確認有人進來要求服務時,會需要accept connection,可用以下方式

int ConnectClient(void)

{

int socksize=sizeof(HostAddr);

unsigned char * addr;

ClientSocket = accept(DaemonSocket,(struct sockaddr*)&HostAddr,&socksize);

if (ClientSocket<0) return 0;

addr = (unsigned char *)&HostAddr.sin_addr.s_addr;

printf("incoming address:%d.%d.%d.%d ",addr[0],addr[1],addr[2],addr[3]);

return 1;

}

注意到當您accept connection之後,連線已建立起,此時要用的socket是ClientSocket,而非DaemonSocket,ClientSocket才是真正用來連線用的socket。

這是個我才剛開始動手寫的象棋伺服器。

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include

#define DAEMON_LOCK "/var/chess/daemon.lock"

#define DAEMON_LOG "/var/chess/daemon.log"

#define DAEMON_PORT 9901

int DaemonSocket;

struct sockaddr_in DaemonAddr;

int ClientSocket=0;

struct sockaddr_in HostAddr;

void dlog(char *fmt,...)

{

va_list argptr;

FILE *fp;

fp = fopen(DAEMON_LOG,"a+t");

va_start(argptr,fmt);

vfprintf(fp,fmt,argptr);

va_end(argptr);

fclose(fp);

}

pid_t CheckLock(void)

{

pid_t me;

FILE * fp;

fp = fopen(DAEMON_LOCK,"rt");

if (fp==NULL) return 0;

fscanf(fp,"%d",&me);

fclose(fp);

return me;

}

pid_t WriteLock(void)

{

pid_t me;

FILE *fp;

me = getpid();

fp = fopen(DAEMON_LOCK,"w");

fprintf(fp,"%d",me);

fclose(fp);

return me;

}

int CleanLock(void)

{

return (unlink(DAEMON_LOCK)==0);

}

void report_time(void)

{

time_t now;

now = time(NULL);

dlog("%s",asctime((const struct tm*)localtime(&now)));

}

static void signal_catch(int signo)

{

time_t now;

close(DaemonSocket);

if (ClientSocket>0) close(ClientSocket);

CleanLock();

now = time(NULL);

dlog("Catch signal %d, leave at %s ",signo,asctime((const struct tm*)localti

exit(-1);

}

void SetupSignal(void)

{

struct sigaction act;

act.sa_handler = signal_catch;

act.sa_flags = 0;

sigemptyset(&act.sa_mask);

sigaction(SIGHUP,&act,NULL);

sigaction(SIGINT,&act,NULL);

sigaction(SIGQUIT,&act,NULL);

sigaction(SIGILL,&act,NULL);

sigaction(SIGABRT,&act,NULL);

sigaction(SIGIOT,&act,NULL);

sigaction(SIGBUS,&act,NULL);

sigaction(SIGFPE,&act,NULL);

sigaction(SIGTERM,&act,NULL);

}

int BindSocket(void)

{

DaemonSocket = socket(AF_INET,SOCK_STREAM,0);

if (DaemonSocket==-1) return 0;

DaemonAddr.sin_family = AF_INET;

DaemonAddr.sin_port = htons(DAEMON_PORT);

if (bind(DaemonSocket,&DaemonAddr,sizeof(DaemonAddr))<0) {

printf("Can not bind! ");

return 0;

}

if (listen(DaemonSocket,1024)!=0) {

printf("Can not listen! ");

return 0;

}

return 1;

}

int incoming_call(void)

{

fd_set sock;

struct timeval tv;

int t;

FD_ZERO(&sock);

FD_SET(DaemonSocket,&sock);

tv.tv_sec = 60; tv.tv_usec = 0;

t = select(DaemonSocket + 1,&sock,NULL,NULL,&tv);

if (t<=0||!FD_ISSET(DaemonSocket,&sock)) return 0;

dlog("incoming... ");

return 1;

}

int ConnectClient(void)

{

int socksize=sizeof(HostAddr);

unsigned char * addr;

ClientSocket = accept(DaemonSocket,(struct sockaddr*)&HostAddr,&socksize);

if (ClientSocket<0) return 0;

addr = (unsigned char *)&HostAddr.sin_addr.s_addr;

dlog("incoming address:%d.%d.%d.%d ",addr[0],addr[1],addr[2],addr[3]);

return 1;

}

int daemon_printf(char *fmt,...)

{

char BUF[4096];

va_list argptr;

va_start(argptr,fmt);

vsprintf(BUF,fmt,argptr);

va_end(argptr);

return write(ClientSocket,BUF,strlen(BUF));

}

void Log(void)

{

char BUF[4096];

read(DaemonSocket,BUF,16);

daemon_printf("%s",BUF[0]);

}

int main(int argc,char **argv)

{

pid_t myself;

time_t now;

/* find myself */

myself = CheckLock();

if (myself!=0) {

printf("Existing a copy of chess daemon[pid=%d], leave now. ",myself);

exit(1);

}

/* fork */

myself = fork();

if (myself>0) {

exit(1);

} else

if (myself<0) {

printf("Strange world! I don't like it. Quit because of pid=%d ",myself);

exit(1);

} else {

SetupSignal();

if (!BindSocket()) {

printf("Can not bind socket! ");

exit(1);

}

WriteLock();

}

printf("Chess Daemon is up, have fun! ");

now = time(NULL);

dlog("---------------------------------------------- ");

dlog(

"I am back! %s"

"Chess Daemon comes to alive again. ",

asctime((const struct tm*)localtime(&now))

);

do {

if (incoming_call()) {

if (ConnectClient()) {

fd_set sock;

struct timeval tv;

int t;

char BUF[128];

char CC[2];

int n;

daemon_printf("Welcome to Chinese Chess Game Center! ");

FD_ZERO(&sock);

FD_SET(ClientSocket,&sock);

n = 0;

do {

tv.tv_sec = 60; tv.tv_usec = 0;

t = select(ClientSocket+1,&sock,NULL,NULL,&tv);

if (t<=0||!FD_ISSET(ClientSocket,&sock)) ;

read(ClientSocket,CC,1);

if (CC[0]==13||CC[0]==10||CC[0]==0) {

BUF[n] = 0;

dlog("%s ",BUF);

if (strncasecmp(BUF,"exit",4)==0) {

close(ClientSocket);

break;

}

n = 0;

} else {

BUF[n]=CC[0]; n++;

}

} while (1);

}

}

} while (1);

return 1;

}

檢驗

telnet localhost 9901

在處理Connect Client時,事實上可以運用fork或thread來處理多個連線。

Linux程式設計- 5.inetd

(2001-05-24 18:08:00)

inetd 提供被動式的伺服器服務,也就是伺服器是被使用端所啟動,平時則無須存在。例如,ftp, telnetd, pop3,imap, auth等等,這些服務沒有人使用時,無須啟動。此外,inetd將socket轉換成stdin/stdout,因而使得網路服務程式設計大大簡化,您可以只用printf及fgets便可完成處理很複雜的網路協定。

--------------------------------------------------------------------------------

inetd programming

利用inetd來做網路程式設計是個既簡單又穩定的設計方法,您不需要考慮到複雜的socket programming。您的設計工作幾乎在設計好通訊協定後就完成了,所需要的技巧,僅為簡單的文字分析技巧。

goodie inet service

首先,我們先來撰寫一個稱為goodie的服務程式。

goodie.c

#include

#include

#include

void main(void)

{

printf("Welcome to goodie service! ");

}

這個程式很簡單,不是嗎?

編譯

gcc -o goodie goodie.c

設定/etc/services及/etc/inetd.conf

在/etc/services中加入以下這一行

goodie 20001/tcp

其意義為goodie這項服務是在port 20001、TCP協定。

接下來在/etc/inetd.conf中加入以下這一行

goodie stream tcp nowait root /full_goodie_path_name/goodie

各項引數的意義為

service_name需要為在services中存在的名稱。

sock_type有很多種,大多用的是stream/dgram。

proto一般用tcp/udp。

flags有wait/nowait。

user是您指定該程式要以那一個使用者來啟動,這個例子中用的是root,如果有安全性的考量,應該要改用nobody。一般來說,建議您用低許可權的使用者,除非必要,不開放root使用權。

server_path及args,這是您的服務程式的位置及您所想加入的引數。

接下來重新啟動inetd

killall inetd

inetd

這樣我們便建立起一個port 20001的goodie service。

現在我們來檢驗一下goodie是否可以執行:

telnet localhost 20001

telnet your_host_name 20001

執行結果

Trying 127.0.0.1...

Connected to localhost.

Escape character is '^]'.

Welcome to goodie service!

Connection closed by foreign host.

很簡單不是嗎? 信不信由您,telnet/pop3/imap/ftp都是靠這種方式建立起來的服務。

我們現在來建立一點小小的"網路協定",這個協定使我們可以輸入"exit"時,離開程式,而其他的指令都是輸出與輸入相同的字串。

#include

#include

#include

void main(void)

{

char buf[1024];

int ok;

printf("Welcome to goodie service! ");

fflush(stdout);

ok=0;

do {

while (fgets(buf,1023,stdin)==NULL);

if (strncasecmp(buf,"exit",4)==0) ok=1;

printf(buf);

fflush(stdout);

} while (!ok);

}

執行結果

telnet localhost 20001

telnet your_host_name 20001

Trying 127.0.0.1...

Connected to localhost.

Escape character is '^]'.

Welcome to goodie service!

輸入"help"

help

help

輸入"exit"

exit

exit

Connection closed by foreign host.

接下來,我們將設計一個稍微複雜一點點的通訊協定,比較通用於一般用途。

#include

#include

#include

char *cmds[]={

"help",

"say",

"hello",

"bye",

"exit",

NULL

};

int getcmd(char *cmd)

{

int n=0;

while (cmds[n]!=NULL) {

if (strncasecmp(cmd,cmds[n],strlen(cmds[n]))==0) return n;

n++;

}

return -1;

}

void main(void)

{

char buf[1024];

int ok;

printf("Welcome to goodie service! ");

fflush(stdout);

ok=0;

do {

while (fgets(buf,1023,stdin)==NULL);

switch (getcmd(buf)) {

case -1: printf("Unknown command! "); break;

case 0: printf("How may I help you, sir? "); break;

case 1: printf("I will say %s",&buf[3]); break;

case 2: printf("How're you doing today? "); break;

case 3: printf("Si ya, mate! "); ok=1; break;

case 4: printf("Go ahead! "); ok=1; break;

}

fflush(stdout);

} while (!ok);

}

telnet localhost 20001

telnet your_host_name 20001

試試看輸入"help"、"say"、"hello"、"bye"、"exit"等等指令,及其它一些不在命令列中的指令。

在設計inetd服務程式時,要特別注意buffer overflow的問題,也就是以下這種狀況:

char buffer_overflow[64];

fscanf(stdin,"%s",buffer_overflow);

歷來幾乎所有的安全漏洞都是由此而來的。

你一定不可這樣用,不論任何理由,類同的用法也不可以。Cracker可以透過將您的buffer塞爆,然後塞進他自己的程式進來執行。

Linux程式設計- 6.syslog

(2001-05-24 19:00:00)

在Linux下有個syslogd的Daemon程式,syslog是個系統管理員必看的檔案。因此,如果您的程式有除錯或安全訊息要顯示,寫到syslog是個很好的選擇。

syslog有三個函式,使用上,一般您只需要用syslog(...)這個函式即可,一般使用狀況下,openlog/closelog是可有可無的。

syslog()中的priority是facility及level的組合,其後引數的用法與printf無異。

例:

#include

#include

#include

#include

void main(void)

{

if (fork()==0) {

for (;;) {

syslog(LOG_USER|LOG_INFO,"syslog programming test ");

sleep(10);

}

}

}

檢驗:

tail -f /var/log/messages

Mar 22 01:42:51 foxman log: syslog programming test

Mar 22 01:43:31 foxman last message repeated 4 times

Mar 22 01:44:31 foxman last message repeated 6 times

Mar 22 01:45:31 foxman last message repeated 6 times

Mar 22 01:46:21 foxman last message repeated 5 times

--------------------------------------------------------------------------------

void openlog( char *ident, int option, int facility)

void syslog( int priority, char *format, ...)

void closelog( void )

option

用於openlog()的option引數可以是以下幾個的組合:

LOG_CONS : 如果送到system logger時發生問題,直接寫入系統console。

LOG_NDELAY : 立即開啟連線(通常,連線是在第一次寫入訊息時才開啟的)。

LOG_PERROR : 將訊息也同時送到stderr

LOG_PID : 將PID含入所有訊息中

facility

facility引數用來指定何種程式在記錄訊息,這可讓設定檔來設定何種訊息如何處理。

LOG_AUTH : 安全/授權訊息(別用這個,請改用LOG_AUTHPRIV)

LOG_AUTHPRIV : 安全/授權訊息

LOG_CRON : 時間守護神專用(cron及at)

LOG_DAEMON : 其它系統守護神

LOG_KERN : 核心訊息

LOG_LOCAL0到LOG_LOCAL7 : 保留

LOG_LPR : line printer次系統

LOG_MAIL : mail次系統

LOG_NEWS : USENET news次系統

LOG_SYSLOG : syslogd內部所產生的訊息

LOG_USER(default) : 一般使用者等級訊息

LOG_UUCP : UUCP次系統

level

決定訊息的重要性. 以下的等級重要性逐次遞減:

LOG_EMERG : 系統無法使用

LOG_ALERT : 必須要立即採取反應行動

LOG_CRIT : 重要狀況發生

LOG_ERR : 錯誤狀況發生

LOG_WARNING : 警告狀況發生

LOG_NOTICE : 一般狀況,但也是重要狀況

LOG_INFO : 資訊訊息

LOG_DEBUG : 除錯訊息

Linux程式設計- 7.zlib的運用

(2001-05-24 20:10:00)

gzip (*.gz)檔案格式幾乎是Linux下的標準格式了,有人認為bzip2的壓縮率比gzip來得高。一般來說,這個說法大致正確,不過根據我個人的經驗,有一半以上的檔案,bzip2沒有比gzip的壓縮率來得高,有少數狀況下,gzip壓縮率反而比bzip2來的高。

zlib是個支援gzip檔案格式的函式庫,它使得gz檔的存取就猶如開檔關檔一樣地容易,您可以很容易地為您的程式加入gz檔的支援。

--------------------------------------------------------------------------------

使用例 : showgz.c

#include

#include

#include

void main(int argc,char **argv)

{

gzFile zip;

int c;

if (argc<2) return;

zip = gzopen(argv[1],"rb");

while ((c=gzgetc(zip))!=EOF) putchar(c);

gzclose(zip);

}

編譯

gcc -o showgz showgz.c -lz

檢驗

gzip -9 < showgz.c > showgz.c.gz

./showgz showgz.c.gz

將會把這個程式內容顯示出來,showgz的作用可說等於gzip -dc。

--------------------------------------------------------------------------------

函式宣告

gzFile gzopen (const char *path, const char *mode);

開啟一個gzip(*.gz)檔。

mode引數可為"rb"或"wb"。

另外也可包含壓縮程度如"wb9"。

用'f'作為過濾資料,如"wb6f"。

用'h'可指定Huffman only壓縮,如"wb1h"

gzopen亦可用於讀取非壓縮的gzip檔案格式,在這種狀況下,gzread會直接讀取,而不進行解壓縮。

int gzread (gzFile file, voidp buf, unsigned len);

與read的用法相同。

int gzwrite (gzFile file, const voidp buf, unsigned len);

與write用法相同。

int gzprintf (gzFile file, const char *format, ...);

與fprintf用法相同。

char * gzgets (gzFile file, char *buf, int len);

與fgets用法相同。

int gzputc (gzFile file, int c);

與fputc用法相同。

int gzgetc (gzFile file);

與fgetc用法相同。

int gzflush (gzFile file, int flush);

與fflush作用相同。

z_off_t gzseek (gzFile file, z_off_t offset, int whence);

whence不支援SEEK_END

如果檔案是開啟為"讀取",則SEEK_SET及SEEK_CUR,向前及向後均支援,不過很慢就是了。

如果檔案是開啟為"寫入",僅支援向前SEEK。

int gzrewind (gzFile file);

與gzseek(file, 0L, SEEK_SET)相同作用,僅在讀取時有效。

z_off_t gztell (gzFile file);

返回值 : 目前檔案位置(解壓縮後的位置)

int gzeof (gzFile file);

返回值 : 1 - EOF, 0 - not EOF

int gzclose (gzFile file);

關閉檔案

返回值 : zlib error number

Linux程式設計- 8.crypt

(2001-05-24 21:04:00)

crypt是個密碼加密函式,它是基於Data Encryption Standard(DES)演演算法。crypt基本上是One way encryption,因此它只適用於密碼的使用,不適合於資料加密。

char *crypt(const char *key, const char *salt);

key 是使用者的密碼。salt是兩個字,每個字可從[a-zA-Z0-9./]中選出來,因此同一密碼增加了4096種可能性。透過使用key中每個字的低七位元,取得56-bit關鍵字,這56-bit關鍵字被用來加密成一組字,這組字有13個可顯示的ASCII字,包含開頭兩個salt。

crypt在您有自行管理使用者的場合時使用,例如會員網站、BBS等等。

--------------------------------------------------------------------------------

例一 : crypt_word.c

#include

#include

#include

void main(int argc,char **argv)

{

if (argc!=3) exit(0);

printf("%s ",crypt(argv[1],argv[2]));

}

編譯

gcc -o crypt_word crypt.c -lcrypt

檢驗

請先看您的/etc/passwd,找一個您自己的帳號,看前面兩個字,那是您自己的salt。接下來輸入:

./crypt_word your_password salt

看看它們是否相同(應該要相同,除非您加了crypt plugin或使用不同的crypt function,例如shadow、pam,這種狀況下,加密字是不同的),另外檢驗看看他們是否為13個字。

您也可以利用Apache上所附的htpasswd來產生加密字做為驗。

例二: verify_passwd.c

注意,這個例讀取/etc/passwd的資料,不適用於使用shadow或已經使用pam的系統(例如slackware,RedHat及Debian在不外加crypt plugin的狀況下,應當相同)。此例僅供參考,做為解crypt函式運作的情形,真正撰寫程式時,應該避免類似的寫法。

#include

#include

#include

typedef struct {

char username[64];

char passwd[16];

int uid;

int gid;

char name[256];

char root[256];

char shell[256];

} account;

/* 注意! 以下的寫法,真實世界的軟體開發狀況下,千萬不要用! */

int acc_info(char *info,account *user)

{

char * start = info;

char * now = info;

/* username */

while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */

if (!*now) return 0;

*now = 0; now++;

strcpy(user->username,start); /* 這會導致buffer overflow */

start = now;

/* passwd */

while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */

if (!*now) return 0;

*now = 0; now++;

strcpy(user->passwd,start); /* 這會導致buffer overflow */

start = now;

/* uid */

while (*now&&*now!=':') now++;

if (!*now) return 0;

*now = 0; now++;

user->uid = atoi(start);

start = now;

/* gid */

while (*now&&*now!=':') now++;

if (!*now) return 0;

*now = 0; now++;

user->gid = atoi(start);

start = now;

/* name */

while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */

if (!*now) return 0;

*now = 0; now++;

strcpy(user->name,start); /* 這會導致buffer overflow */

start = now;

/* root */

while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */

if (!*now) return 0;

*now = 0; now++;

strcpy(user->root,start); /* 這會導致buffer overflow */

start = now;

/* shell */

while (*now&&*now!=':') now++; /* 這是超級大安全漏洞 */

*now = 0; now++;

strcpy(user->shell,start); /* 這會導致buffer overflow */

start = now;

return 1;

}

int read_password(char *filename,account *users)

{

FILE *fp;

char buf[1024];

int n;

n = 0;

fp = fopen(filename,"rt");

while (fgets(buf,1024,fp)!=NULL) {

if (acc_info(buf,&users[n])) n++;

}

fclose(fp);

return n;

}

void main(int argc,char **argv)

{

int n,i,done;

account ACC[128];

char username[256];

char password[256];

char * passwd;

char salt[4];

if (argc<2) {

printf("username:");

scanf("%s",username); /* 這是超級大安全漏洞 */

} else strcpy(username,argv[1]); /* 這是超級大安全漏洞 */

if (argc<3) {

printf("password:");

scanf("%s",password); /* 這是超級大安全漏洞 */

} else strcpy(password,argv[2]); /* 這是超級大安全漏洞 */

n = read_password("/etc/passwd",ACC);

for (i=0,done=0;i if (strcmp(username,ACC[i].username)==0) {

salt[0] = ACC[i].passwd[0];

salt[1] = ACC[i].passwd[1];

salt[2] = 0;

passwd = crypt(password,salt);

printf("%s %s %s ",ACC[i].username,ACC[i].passwd,passwd);

if (strcmp(passwd,ACC[i].passwd)==0) {

printf("login successfully! ");

} else {

printf("incorrect password! ");

}

done = 1;

}

if (!done) printf("invalid username! ");

}

編譯

gcc -o verify_passwd verify_passwd.c -lcrypt

檢驗

./verify_passwd your_username your_password

避免安全漏洞

buffer overflow是個很嚴重的安全漏洞,通常您不可使用像char buf[xxxx]的宣告。在這一類與安全有相關的任何程式寫作中(不是隻有密碼,例如像www/ftp/telnet的這一類對外視窗都要算在內),您應該要先檢查字串長度。例如以下例子:

len = strlen(incoming_username);

if (len>xxx) invalid;

new_string = (char*)malloc(len+1);

strcpy(new_string,incoming_username);

your_own_operations...

如此才能避免buffer overflow,萬萬不可濫做假設,切記切記,連許多數十年經驗豐富的老手都會犯這個錯誤。

--------------------------------------------------------------------------------

與crypt函式相關者尚有以下三個:

void setkey (const char *key);

void encrypt (char *block, int edflag);

void swab (const char *from, char *to, ssize_t n);

一般來說,除非您有特殊需求,你不會用到這三個。

Linux程式設計- 9.PAM

(2001-05-24 22:08:00)

Linux-PAM stands for Pluggable Authentication Modules for Linux。

PAM是個可外掛式的認模組。其詳細檔案一般在/usr/doc/pam-XX中,您也可以在metalab.unc.edu/LDP或Red Hat站中找到PAM-HOWTO。

我不準備介紹PAM的使用,在此我將精力放在如何運用PAM的函式庫上。您在進一步看下去之前,應當先閱讀有關PAM的相關資料,並且先解其運作機制,對它先有個初步解,然後再回來繼續。

Linux程式設計- 10.termios/keymap/terminal programming

(2001-05-25 07:00:00)

termios

int tcgetattr (int fd, struct termios *termios_p);

int tcsetattr (int fd, int optional_actions,const struct termios *termios_p);

--------------------------------------------------------------------------------

keymap

我寫了一個小程式來專門處理Linux上的keymap,keymap.h及keymap.c。

在Linux Terminal上,如果您想要設定某些按鍵返回特定值,您會需要用到以下這些技巧。

設定keymap

#include

#include

void setkeymap(void)

{

struct kbentry KEYMAP;

KEYMAP.kb_table=STATE;

KEYMAP.kb_index=SCANCODE;

KEYMAP.kb_value=VALUE;

ioctl(console,KDSKBENT,&KEYMAP);

}

STATE為狀態鍵組合

/usr/include/linux/keyboard.h中

#define KG_SHIFT 0

#define KG_CTRL 2

#define KG_ALT 3

#define KG_ALTGR 1

#define KG_SHIFTL 4

#define KG_SHIFTR 5

#define KG_CTRLL 6

#define KG_CTRLR 7

#define KG_CAPSSHIFT 8

使用方式如:

#define KST_CTRL (1#define KST_ALT_SHIFT (KST_ALT|KST_SHIFT)

SCANCODE為鍵盤掃描碼

#define SCAN_ESC 0x01

#define SCAN_1 0x02

#define SCAN_2 0x03

#define SCAN_3 0x04

#define SCAN_4 0x05

#define SCAN_5 0x06

#define SCAN_6 0x07

#define SCAN_7 0x08

#define SCAN_8 0x09

#define SCAN_9 0x0A

#define SCAN_0 0x0B

#define SCAN_MINUS 0x0C

#define SCAN_PLUS 0x0D

#define SCAN_BACK 0x0E

#define SCAN_TAB 0x0F

#define SCAN_Q 0x10

#define SCAN_W 0x11

#define SCAN_E 0x12

#define SCAN_R 0x13

#define SCAN_T 0x14

#define SCAN_Y 0x15

#define SCAN_U 0x16

#define SCAN_I 0x17

#define SCAN_O 0x18

#define SCAN_P 0x19

#define SCAN_LTQUOTE 0x1A

#define SCAN_RTQUOTE 0x1B

#define SCAN_ENTER 0x1C

#define SCAN_CTRL 0x1D

#define SCAN_A 0x1E

#define SCAN_S 0x1F

#define SCAN_D 0x20

#define SCAN_F 0x21

#define SCAN_G 0x22

#define SCAN_H 0x23

#define SCAN_J 0x24

#define SCAN_K 0x25

#define SCAN_L 0x26

#define SCAN_SPLIT 0x27

#define SCAN_QUOTE 0x28

#define SCAN_MARK 0x29

#define SCAN_LSHIFT 0x2A

#define SCAN_STAND 0x2B

#define SCAN_Z 0x2C

#define SCAN_X 0x2D

#define SCAN_C 0x2E

#define SCAN_V 0x2F

#define SCAN_B 0x30

#define SCAN_N 0x31

#define SCAN_M 0x32

#define SCAN_LSQUOTE 0x33

#define SCAN_RSQUOTE 0x34

#define SCAN_QUESTION 0x35

#define SCAN_RSHIFT 0x36

#define SCAN_PRTSCR 0x37

#define SCAN_ALT 0x38

#define SCAN_SPACE 0x39

#define SCAN_CAPSLOCK 0x3A

#define SCAN_F1 0x3B

#define SCAN_F2 0x3C

#define SCAN_F3 0x3D

#define SCAN_F4 0x3E

#define SCAN_F5 0x3F

#define SCAN_F6 0x40

#define SCAN_F7 0x41

#define SCAN_F8 0x42

#define SCAN_F9 0x43

#define SCAN_F10 0x44

#define SCAN_NUMLOCK 0x45

#define SCAN_HOME 0x47

#define SCAN_UP 0x48

#define SCAN_PGUP 0x49

#define SCAN_LEFT 0x4B

#define SCAN_RIGHT 0x4D

#define SCAN_END 0x4F

#define SCAN_DOWN 0x50

#define SCAN_PGDN 0x51

#define SCAN_INSERT 0x52

#define SCAN_DELETE 0x53

#define SCAN_F11 0x85

#define SCAN_F12 0x86

/usr/include/linux/kd.h中

struct kbentry {

unsigned char kb_table;

unsigned char kb_index;

unsigned short kb_value;

};

#define KDGKBENT 0x4B46 /* gets one entry in translation table */

#define KDSKBENT 0x4B47 /* sets one entry in translation table */

而console為

console = open("/dev/console",O_RDWR);

讀取按鍵

read(console,&c,sizeof(char));

--------------------------------------------------------------------------------

terminal programming

term.h/term.c是我寫來專門處理一些小型的互動介面程式。

Terminal指令集

設定顏色 :

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/8225414/viewspace-944730/,如需轉載,請註明出處,否則將追究法律責任。

相關文章