Category Archive軟體



軟體 dada on 十二月 24, 2007

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>

軟體 dada on 十一月 07, 2007

Subversion: revert a commit [時光倒轉]

使用 Subversion 偶而就會遇到有人不小心把一堆不該存在的檔案 commit 進來了,這在去年寫的一篇「svndumpflter with wildcards support ?」有提到從歷史記憶中去除個別檔案的方式

但如果你能即時發現最新的一個(或幾個) commit(s) 的任何更動都是不該發生的(通常就是有人做了蠢事),那麼在有其他正常人繼續 commit 之前,其實是有蠻好的挽救機會的 :idea:

假設你的 repository 名稱是 foobar,最新的 revision number 是 r105,但是你即時發現 r101 ~ r105 都是某人亂搞的結果,那麼在 r106 commit 之前,應該還有機會把時光倒轉到 r100 那時清純未受污染的狀態..

廢話不多說,下面是作法

# cd /home/svn
# svnadmin create foobar.new
# svnadmin dump -r0:100 foobar | svnadmin load --force-uuid foobar.new
# mv foobar foobar.old
# mv foobar.new foobar

做完以上動作後再把該設的權限弄好,hooks 下的東西 copy 回去就可以了

如果有其他人在這段動作完成前 checkout 了 r101~r105 之間的版本,就跟他們說他們看到鬼了,請他們砍掉 source tree 重新 checkout 一份吧 :roll:

上面這段作法最重要的地方是 --force-uuid 這個選項,沒有加這個選項會使得回復的 repository 的 UUID 跟原本的不同,進而導致之前已經 checkout 的 working copy 都會認不得新的 repository!

如果還來不及作上述動作,就有其他正常人 commit 東西進來了,其實也不會很麻煩,反正做完回復動作後,統統當作沒發生過,再叫作蠢事的人請一頓飯,拜託其他人把新的修改再改一次就好了

軟體 dada on 十月 11, 2007

Access Subversion with Apache Proxy Module

由於網路架構的問題,我們有時會設計讓不同網段必須透過 Proxy 才能存取 Subversion Repository,但是為此架設一個獨立的 Proxy Server (如 squid)實在太麻煩,利用 Apache 內建的 mod_proxy / mod_proxy_http 功能有時候會讓工作簡化很多

ProxyRequests Off
<Proxy *>
    Order deny,allow
    Deny from all
</Proxy>
ProxyVia On

為了安全性考量,如同上面的設定,我們先把所有 Proxy 的存取關掉,然後依據需要開放特定目錄來允許 Proxy

例如我們想把對 /svn 的存取導向到 http://svn.domain/svn,那麼設定內容就會變成:

<VirtualHost *:80>
    ProxyPass /svn http://svn.domain/svn
    ProxyPassReverse /svn http://svn.domain/svn
    <Location /svn>
        <Limit OPTIONS PROPFIND GET REPORT MKACTIVITY PROPPATCH PUT CHECKOUT
 MKCOL MOVE COPY DELETE LOCK UNLOCK MERGE>
          Order Deny,Allow
          Allow from all
          Satisfy Any
        </Limit>
    </Location>
</VirtualHost>

裡面最重要的就是 <Limit OPTIONS PROPFIND... > 這一行(這一整行不能分成兩行,Copy-Paste時請注意),因為 Subversion 會用到除了 HTTP GET / POST 以外的一些特殊 Method,因此必須設定讓 HTTP 的 Proxy Module 允許這些 Methods 通過,才能讓 Subversion 正常運作

另外建議修改 Allow from all 這一行,改成僅允許特定 IP 來連結,也可強化系統的安全性

參考資料: Subversion behind an Apache Reverse Proxy (不過裡面的設定方式跟我上面提供的有一點點小小差異)

如果你還是想用 squid 來解決問題,那您可以參考 Subversion 網站上面的 FAQ

軟體 &電腦資訊 dada on 九月 13, 2007

MSN Messenger 7.5 強迫升級

一早起來發現 MSN Messenger 7.5 無法登入了...

不升級就無法登入了!

我還是比較喜歡用 7.5 的介面,尤其 Messenger Plus!自訂暱稱的功能只支援 7.5,而我又不想用 8.0 以後內建但殘廢的自訂暱稱功能... 而且我的聯絡人已經上百個了,懶得一個一個還要作轉換,所以能撐多久就撐多久...

要讓 7.5 免除升級困擾,請先把 MSN Messenger 關閉,然後開啟我的電腦,打開 C:\Program Files\MSN Messenger\ 資料夾,在 msnmsgr.exe上面按滑鼠右鍵選內容:

選擇「相容性」,然後把「以相容性模式執行這個程式」打勾,再從選單中選擇「Windows 2000」後按「確定」即可

然後執行 MSN Messenger 後,就可以正常登入了

這方法不曉得可以用多久就是了...

目前唯一的缺點是,系統列上面的圖示變得有點難看,只能裝作看不見了...

軟體 dada on 十月 06, 2006

svndumpfilter with wildcards support ?

Subversionsvnadmin obliterate 功能一直沒有實作出來,這實在是有點麻煩,尤其是當負責管理一個 repository 時,常常就會有人把 *.exe, *.obj, *.lib, *.a 統統給 commit 進去了....

然後你就會發現整個 repository 長大的速度越來越快,就算事後用 svn delete 把它砍掉還是無濟於事,因為那已經成為無法抹滅的歷史了

雖然可以事先設定 svn:ignore 來解決,不過偶而還是會有漏網之魚,官方提供的解決方法是使用 svndumpfiler 來把不要的檔案徹底從歷史記憶中濾除

# svnadmin create REPOS2
# svnadmin dump REPOS1 | svndumpfilter exclude XX.exe | svnadmin load REPOS2

這方法當然沒有 svnadmin obliterate 好,不過也是目前狀況下不得不的作法

但是這個方法最大的問題在於 svndumpfilter 無法接受 wildcards (萬用字元),也就是你無法指定 svndumpfilter exclude '*.exe'

所以,萬一不小心遇上了機車的狀況,你可能要一個一個把檔名找出來濾掉 XD

所以不久前就想辦法修改了 svndumpfilter 的程式,把它加上直接濾除特定副檔名檔案的功能...

[patch to subversion/svndumpfilter/main.c]
(the filename with leading dot will be matched against filename extension)

--- subversion/svndumpfilter/main.c.orig
+++ subversion/svndumpfilter/main.c
@@ -111,7 +111,12 @@
       pfx_len = strlen(pfx);
       if (path_len < pfx_len)
         continue;
-      if (strncmp(path, pfx, pfx_len) == 0)
+      if( pfx[0] == '.' )
+      {
+        if( strncmp( path+path_len-pfx_len, pfx, pfx_len ) == 0 )
+          return TRUE;
+      }
+      else if (strncmp (path, pfx, pfx_len) == 0)
         return TRUE;
     }
 
@@ -1257,7 +1262,8 @@
             style, and absolute. */
        SVN_INT_ERR(svn_utf_cstring_to_utf8(&prefix, os->argv[i], pool));
        prefix = svn_path_internal_style(prefix, pool);
-       prefix = svn_path_join("/", prefix, pool);
+       if( prefix[0] != '.' )
+          prefix = svn_path_join("/", prefix, pool);
        APR_ARRAY_PUSH(opt_state.prefixes, const char *) = prefix;
      }
  }

這個 patch 有點投機取巧,因為我懶得真的去實作 wildcards,我需要的功能只要能濾除指定的副檔名就可以了...

所以這個 patch 把原來 svndumpfilter include/exclude 後面接的檔名參數作特殊判斷,只要第一字元是小數點,就當作要去 match 的副檔名,因此,要濾除 *.exe 只要這樣作就可以了:

# svnadmin create REPOS2
# svnadmin dump REPOS1 | svndumpfilter exclude .exe | svnadmin load REPOS2

剛好看到前幾天也有人寫了個 PATCH 讓 svndumpfilter 完全支援 wildcards... 不過我沒試過,相信不久的將來應該會被整合進正式版本中

當然最希望還是 svmadmin obliterate 能早日被支援,就不用這麼辛苦了

« 前一頁後一頁 »