作者彙整: dada

關於「dada」

I'm dada

SSH Escape Character

SSH client 有一個 Escape Character (跳脫字元),事實上 rsh/rlogin 也有支援 Escape Character (~),就如同 Ctrl-] 之於 telnet 一樣…

SSH client 的 Escape Character 一般跟 rsh/rlogin 一樣設定成 ~,詳細用法可於命令列按 ~? 來取得說明,要注意的是,為了避免影響一般正常的輸入,Escape Character 必須是命令列換行後的第一個按鍵,如果你已經輸入別的按鍵,即使按 backspace 把游標移回開頭處再輸入 Escape Character 也是無效的!請先按 Enter 換行後再輸入吧!

myhost# ~?
Supported escape sequences:
~.  - terminate connection
~B  - send a BREAK to the remote system
~C  - open a command line
~R  - Request rekey (SSH protocol 2 only)
~^Z - suspend ssh
~#  - list forwarded connections
~&  - background ssh (when waiting for connections to terminate)
~?  - this message
~~  - send the escape character by typing it twice
(Note that escapes are only recognized immediately after newline.)

最常用的 Escape Character 就是 ~. 以及 ~^Z 這兩個:

~. 是直接切斷目前的 SSH 連線,如果你遇到遠端機器沒有反應時,可用此方式來切斷連線

~^Z (~ 及 Ctrl-Z) 則是把目前的 SSH 連線先 suspend,一般可用來回到原來機器上處理一些事情,若要再回去,輸入 fg 即可

問題來了,假設你從 HOST1 先 SSH 到 HOST2,然後再從 HOST2 SSH 到 HOST3

HOST1 ===1===> HOST2 ===2===> HOST3

如果你在 HOST3 的 terminal 下輸入 ~. 的話,你會直接切斷上面的第一段連線回到 HOST1 的 terminal,當然這樣的話第二段連線也一併 bye-bye 了!如果只是想切斷第二段連線回到 HOST2 該怎麼作呢?

這時候 ~~ 就發揮作用了,你只要輸入 ~~. 就可以切斷第二段連線了

以此累推,輸入 N 個 Escape Character 代表對第 N 段連線送出 Escape Character

最後,若不想用 ~ 當作跳脫字元,可於命令列用 -e 修改,例如把 SSH 跳脫字元改成 #:

ssh -e "#" myhost.mydomain.com

Change the core dump file name in Linux and FreeBSD

Following the previous notes about enabling core dump, here’s a note about changing the filename of core dump.

In Linux (since Linux 2.6 and 2.4.21)
you can change the core dump filename from the file /proc/sys/kernel/core_pattern

         %%  A single % character
         %p  PID of dumped process
         %u  real UID of dumped process
         %g  real GID of dumped process
         %s  number of signal causing dump
         %t  time of dump (seconds since 0:00h, 1 Jan 1970)
         %h  hostname (same as 'nodename' returned by uname(2))
         %e  executable filename

Linux have a default core filename pattern of “core”.
Alternatively, if /proc/sys/kernel/core_uses_pid contains a non-zero value, then the core dump file name will include a suffix .PID (process id), ex: core.PID

In FreeBSD, sysctl variable “kern.corefile” controls the filename of core dump.

Any sequence of %N in this filename template will be replaced by
the process name, %P by the processes PID, and %U by the UID.

FreeBSD have a default core filename pattern of “%N.core”

You can include path in the filename pattern both in Linux and FreeBSD.
This make it possible to put core dump file in a separated directory.

Enable core dump in Linux and FreeBSD

Just a note.

You can enable core dump by:

[bash] edit /etc/profile

ulimit -c unlimited

[csh/tcsh] edit /etc/csh.cshrc

limit coredumpsize unlimited

You can disable core dump by:

[bash] edit /etc/profile

ulimit -c 0

[csh/tcsh] edit /etc/csh.cshrc

limit coredumpsize 0

On FreeBSD, you also need to check the setting of kern.coredump:

# sysctl -a |grep kern.coredump
kern.coredump: 0
# sysctl kern.coredump=1
kern.coredump: 0 -> 1
# sysctl -a | grep kern.coredump
kern.coredump: 1

You can enforce this setting in /etc/sysctl.conf

[2008/01/01] Thanks for the complement from gslin, kern.sugid_coredump controls the core dump for setuid/setgid process in FreeBSD.

Script: 取得 FreeBSD CVSUP 的更新狀態

這是今年1月寫的 script… 用來監控各大 FreeBSD CVSUP mirror 網站的檔案更新程度,
提供服務的網址是 http://ftp.giga.net.tw/cvsup.php

內容其實很簡單,下面把程式放出來,有興趣的人歡迎免費拿去修改使用

==
原理說明:

CVS 不像 Subversion 一樣可以很容易知道目前 repository 中的最新版本為何,
必須要把每個檔案檢查一遍才有辦法知道最新的版本是什麼…

不過在 FreeBSD Source CVS 下面,每次 commit 都會有 commit log 可經由 CVSUP 取得,
因此我們可以利用 CVSROOT-*/commitlogs/* 這幾個檔案來判斷目前 source tree 的狀態..
只要把這幾個檔案經由 CVSUP 取出來就可以大致知道該伺服器的目前保存的最新版本是哪一個了
==

第一個是一個 Perl script,主要是連結到各大 FreeBSD CVSUP 網站取得相關資訊存下來
後面則附上一個 PHP script,可以把這些資訊用 HTML 表格方式呈現出來

check-cvsup.pl

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
#!/usr/bin/perl
 
# Check the update status for CVSUP servers
# BSD license
# Shen Cheng-Da (cdsheen AT gmail.com)
 
use POSIX qw(strftime);
use Net::DNS;
use Time::HiRes qw( gettimeofday tv_interval );
 
@servers = qw( cvsup.tw.freebsd.org cvsup1.tw.freebsd.org cvsup2.tw.freebsd.org 
                cvsup3.tw.freebsd.org cvsup4.tw.freebsd.org cvsup5.tw.freebsd.org 
                cvsup6.tw.freebsd.org cvsup7.tw.freebsd.org cvsup8.tw.freebsd.org 
                cvsup9.tw.freebsd.org cvsup10.tw.freebsd.org cvsup11.tw.freebsd.org 
                cvsup12.tw.freebsd.org cvsup13.tw.freebsd.org cvsup14.tw.freebsd.org );
 
$dir    = '/home/cvsup-monitor';
 
$cvsup  = '/usr/local/bin/cvsup';
$base   = $dir . '/base';
$log    = $dir . '/check.log';
 
chdir( $dir ) || die "ERROR: Can not change to directory [$dir]\n";
 
mkdir( $base, 0755 ) unless -d $base;
 
@files_src = qw( CVSROOT-src/commitlogs/CVSROOT CVSROOT-src/commitlogs/bin
                CVSROOT-src/commitlogs/etc CVSROOT-src/commitlogs/contrib
                CVSROOT-src/commitlogs/gnu CVSROOT-src/commitlogs/games
                CVSROOT-src/commitlogs/include CVSROOT-src/commitlogs/lib
                CVSROOT-src/commitlogs/release CVSROOT-src/commitlogs/sys
                CVSROOT-src/commitlogs/sbin CVSROOT-src/commitlogs/share
                CVSROOT-src/commitlogs/tools CVSROOT-src/commitlogs/user
                CVSROOT-src/commitlogs/usrbin CVSROOT-src/commitlogs/usrsbin );
 
@files_ports = qw( CVSROOT-ports/commitlogs/CVSROOT
                        CVSROOT-ports/commitlogs/ports );
 
$includes = '';
foreach $f ( @files_src ) {
        $includes .= " -i $f";
}
foreach $f ( @files_ports ) {
        $includes .= " -i $f";
}
 
open( LOG, ">$log");
 
my $resolver = Net::DNS::Resolver->new;
 
$resolver->usevc(1);
$resolver->udp_timeout(5);
$resolver->tcp_timeout(5);
$resolver->retrans(3);
$resolver->retry(2);
$resolver->persistent_tcp(1);
 
foreach $server ( @servers ) {
        my $query = $resolver->query($server, 'A');
        @ipaddr = @cname = ();
        if( $query ) {
                foreach $rr ($query->answer) {
                        if( $rr->type eq 'A' ) {
                                print LOG "$server => [A] ".$rr->address."\n";
                                push( @ipaddr, $rr->address );
                        }
                        elsif( $rr->type eq 'CNAME' ) {
                                print LOG "$server => [CNAME] ".$rr->cname."\n";
                                push( @cname, $rr->cname );
                        }
                }
        }
        $ip = $ipaddr[0];
        $ipaddr = join( '|', @ipaddr );
        $cname  = join( '|', @cname  );
 
        $ibase   = "$base/$server";
        mkdir( $ibase, 0755 ) unless -d $ibase;
        mkdir( "$ibase/SRC", 0755 ) unless -d "$ibase/SRC";
 
        $cmd = "$cvsup -h $ip -L 0 -b $ibase -r 0 $includes supfile";
 
        $time_start = [gettimeofday];
        system($cmd);
        $elapsed = tv_interval( $time_start );
 
        printf LOG ("$server => cvsup on $ip elapsed %.2fs\n", $time_elapsed);
 
        unless( $? ) {
                $mt_src = $mt_ports = 0;
                foreach $f ( @files_src ) {
                        $t = (stat( "$ibase/SRC/$f" ))[9];
                        $mt_src = $t if $t > $mt_src;
                }
                foreach $f ( @files_ports ) {
                        $t = (stat( "$ibase/SRC/$f" ))[9];
                        $mt_ports = $t if $t > $mt_ports;
                }
                open( REC, ">$ibase/last-commit.txt" );
                printf REC("$mt_src,$mt_ports,%.2f,$ipaddr,$cname",$elapsed);
                close(REC);
 
                printf LOG ("$server => SRC: %s\n",
                        strftime("%Y/%m/%d %H:%M:%S", localtime($mt_src)) );
                printf LOG ("$server => PORTS: %s\n",
                        strftime("%Y/%m/%d %H:%M:%S", localtime($mt_ports)) );
        }
}
 
close(LOG);

supfile:

*default prefix=SRC
*default release=cvs delete use-rel-suffix
*default compress
 
cvsroot-src
cvsroot-ports

cvsup.php

<style type="text/css">
  td       { font-size: 12px; vertical-align: top }
  td.head  { background: #FFFFC0 }
  td.c     { background: #E0E0FF }
  td.red   { background: #FFE0E0 }
  td.green { background: #E0FFE0 }
</style>
<table border=1 cellspacing=0 cellpadding=2>
<tr><td class=head>Server Name</td><td class=head>IP</td>
<td class=head>CNAME</td><td class=head>latest commit of src</td>
<td class=head>latest commit of ports</td>
</tr>
<?
        $dir = '/home/cvsup-monitor';
        $servers = array(
              'cvsup.tw.freebsd.org',  'cvsup1.tw.freebsd.org',  'cvsup2.tw.freebsd.org',
              'cvsup3.tw.freebsd.org', 'cvsup4.tw.freebsd.org', 'cvsup5.tw.freebsd.org',
              'cvsup6.tw.freebsd.org', 'cvsup7.tw.freebsd.org', 'cvsup8.tw.freebsd.org',
              'cvsup9.tw.freebsd.org', 'cvsup10.tw.freebsd.org', 'cvsup11.tw.freebsd.org',
              'cvsup12.tw.freebsd.org', 'cvsup13.tw.freebsd.org', 'cvsup14.tw.freebsd.org' );
        $check = time() - 86400;
        $time_format = '%Y/%m/%d %H:%M:%S';
        $latest_src = $latest_ports = 0;
        foreach( $servers as $server ) {
                $data = file_get_contents("$dir/base/$server/last-commit.txt");
                $data = trim($data);
                if( $data != '' ) {
                        $SERVER[$server] = explode(',', $data);
                        if( $SERVER[$server][0] > $latest_src )
                                $latest_src = $SERVER[$server][0];
                        if( $SERVER[$server][1] > $latest_ports )
                                $latest_ports = $SERVER[$server][1];
                }
        }
        foreach( $servers as $server ) {
                if( is_array($SERVER[$server]) ) {
                        list( $src, $ports, $elapsed, $ipaddr, $aliases ) = $SERVER[$server];
                        $ipaddr = str_replace( '|', '<br />', $ipaddr );
                        $aliases = str_replace( '|', '<br />', $aliases );
                        if( $aliases == '' )
                                $aliases = '&nbsp;';
                        print "<tr>\n";
                        print "  <td class=c>$server</td>\n";
                        print "  <td class=c>$ipaddr</td>\n";
                        print "  <td class=c>$aliases</td>\n";
                        if( $src == $latest_src )
                                print "  <td class=green>";
                        elseif( $src < $check )
                                print "  <td class=red>";
                        else
                                print "  <td class=c>";
                        print strftime( $time_format, $src ) . "</td>\n";
                        if( $ports == $latest_ports )
                                print "  <td class=green>";
                        elseif( $ports < $check )
                                print "  <td class=red>";
                        else
                                print "  <td class=c>";
                        print strftime( $time_format, $ports ) . "</td>\n";
#                       print "  <td align=right>$elapsed s</td>\n";
                        print "</tr>\n";
                }
        }
?>
</table>

Wanted: 誠徵資深軟體工程師 [網路遊戲業]

這篇是廣告文…. 替公司徵一下人..

誠徵: 資深軟體工程師 / 2~5人

公司名稱: 和信超媒體 | Wikipedia:GigaMedia | 那斯達克上市股票代碼: GIGM
工作內容: 大型休閒類網路遊戲架構開發與設計
工作地點: 台北 / 內湖科學園區 (Google Map)

需求條件(符合任一項均可):
1. 熟悉 Windows 程式設計 (VC++)、ActiveX 元件開發
2. 熟悉 UNIX C/C++ 程式開發、基本 FreeBSD / Linux 作業系統操作管理
3. 熟悉 Oracle/MySQL 資料庫程式設計、Stored Procedure
4. 熟悉 Perl, PHP or Python 程式語言
5. 具有網路電玩遊戲開發相關經驗者尤佳

過去和信超媒體 (GigaMedia) 是一個 ISP 公司,近年來重心已經大幅轉移至線上遊戲,透過併購國內外多家遊戲開發及營運商,前景大為看好。分公司「戲谷」更是國內休閒類遊戲(Casual game)的龍頭。自 2004 年以來,和信超媒體每年獲利均呈高度成長。2004年營運淨利168萬美元,2005年營運淨利633萬美元,2006年營運淨利3078萬美元,2007年營運淨利亦高達3889萬美元。

GigaMedia 並擁有大陸 T2CN 的多數股權,T2CN 在大陸經營的遊戲有超過4500萬個註冊用戶,同時上線用戶高達數十萬人。

未來我們的目標將是更大的日本及大陸市場,為因應系統架構的大幅擴充,我們需要能立刻接手開發的技術人才,只要您對自己的能力有自信,我們給的待遇絕對可高於業界水準!

如您對遊戲產業文化有興趣,這份工作內容將非常適合您。不用忍受竹科每天加班的夢靨,不用放棄自己的工作興趣!歡迎您的加入!

本職務的詳細工作內容及應徵網址請參考: 104人力銀行

對此職務有任何問題,都歡迎私底下來信詢問: cdsheen AT gigamedia.com.tw

和信超媒體: http://www.gigamedia.com.tw/
戲谷分公司: http://www.funtown.com.tw/

消失的硬碟空間

話說某一天,一位同事發現某個在 UNIX 上用 C 寫的程式,跑一陣子後似乎會吃掉很多硬碟空間,吃掉的硬碟空間用 du 去算卻跟 df 的結果差異很大,而且把 process 停掉後,空間竟然又自動恢復正常了

最後,用 fstat 去仔細分析,終於找到原因:

已經開啟的檔案,即使開啟中被強制砍掉(unlink),對原 file descriptor 持續寫入的部份仍會繼續佔用硬碟空間,寫得越多,佔用的空間也越多

實務上最常遇到這種狀況的就是 log rotation,尤其是 rotation 後的舊 LOG 是壓縮過的情況。因為經過 gzip 壓縮過後,原始的 LOG 會被刪除,只留下 XXX.gz。這個時候如果沒有人通知原來寫 LOG 的程式要重新開啟一次 LOG (重新寫一個檔案),就會導致程式在不知情狀況下繼續寫 LOG,然後空間就莫名其妙被用掉了!

例如 FreeBSD 下專門作 log rotation 的 newsyslog 設定檔 (newsyslog.conf) 就有個欄位可以設定在 log rotation 後送一個 signal 給 process,而 apache (httpd) 就接受 SIGUSR1 來當作重新開啟 LOG 檔案的訊號(事實上對 apache 而言是 graceful restart)。很多人以為這只是為了讓 LOG 能繼續寫不會漏掉,但其實更重要的是:如果不這麼作,你的硬碟可能很快就爆掉啦…

我們可以寫個簡單的程式來測試一下這種狀況:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <stdio.h>
#include <fcntl.h>
int main()
{
        int     fd, i;
        char    cmd[32], buf[1024];
        memset( buf, 0, 1024);
        snprintf(cmd,sizeof(cmd),"df .");
 
        printf("==> open file for write and delete it ...\n");
        fd = open( "test-file.log", O_CREAT|O_WRONLY|O_TRUNC );
        unlink("test-file.log");
        system(cmd);
 
        printf("\n==> write 100MB to file ...\n");
        for( i = 0 ; i < 1000*100 ; i++ )
                write( fd, buf, 1024);
        system(cmd);
 
        printf("\n==> close file ...\n");
        close(fd);
        system(cmd);
}

首先,這個小程式會先開啟一個檔案,然後馬上砍掉它(但先不關閉檔案),接下來執行 “df .” 來查看目前硬碟用量。第二步驟是寫入100MB的垃圾資料到這個已開啟的檔案(file descriptor)中,然後再執行 “df .” 來取得硬碟用量。最後關閉檔案後,再執行一次 “df .”。執行結果如下:

==> open file for write and delete it ...
Filesystem  1K-blocks     Used     Avail Capacity  Mounted on
/dev/ad8s1d 144520482 28011428 104947416    21%    /home

==> write 100MB to file ...
Filesystem  1K-blocks     Used     Avail Capacity  Mounted on
/dev/ad8s1d 144520482 28111508 104847336    21%    /home

==> close file ...
Filesystem  1K-blocks     Used     Avail Capacity  Mounted on
/dev/ad8s1d 144520482 28011428 104947416    21%    /home

我們可以看到程式寫了100MB之後,空間真的被佔掉了,即使我們已經刪除這個檔案,且從目錄的檔案列表中無法直接看到這個檔案了。而當被開啟的檔案關掉後,這些空間也立即被釋放回來了

接下來我們把程式中的 df 改成 fstat,可以更清楚看到狀況

1
        snprintf(cmd,sizeof(cmd),"fstat -f -p %d .", getpid());

這是最後的結果:

==> open file for write and delete it ...
USER     CMD     PID   FD MOUNT     INUM MODE         SZ|DV R/W
cdsheen  a.out 91475   wd /home 12694528 drwxr-xr-x    2048  r
cdsheen  a.out 91475 text /home 12694672 -rwxr-xr-x    7910  r
cdsheen  a.out 91475    3 /home 12694673 ----r-x--x       0  w

==> write 100MB to file ...
USER     CMD     PID   FD MOUNT     INUM MODE         SZ|DV R/W
cdsheen  a.out 91475   wd /home 12694528 drwxr-xr-x    2048  r
cdsheen  a.out 91475 text /home 12694672 -rwxr-xr-x    7910  r
cdsheen  a.out 91475    3 /home 12694673 ----r-x--x  102400000  w

==> close file ...
USER     CMD     PID   FD MOUNT     INUM MODE         SZ|DV R/W
cdsheen  a.out 91475   wd /home 12694528 drwxr-xr-x    2048  r
cdsheen  a.out 91475 text /home 12694672 -rwxr-xr-x    7910  r