星期五, 四月 27, 2007

lftp mirror for dot files

默认情况下,lftp mirror 命令不会下载 .file 这样的隐藏文件,需要打开相应的选项才行:
sh$ lftp $hostname
lftp> cd pub
lftp> set ftp:list-options -a
lftp> mirror src

星期一, 四月 16, 2007

df 和 du 输出不符 -- lsof +L1

在删除了大日志文件后,使用 df 发现分区快用尽了,但用 du 统计的结果却发现没有使用多少分区。这是因为文件虽然被删除了,但运行中的进程却仍然 link 到它们,所以磁盘空间实际上并没有释放。

可以使用 lsof +L1 来查看所有这样的文件。类似下面的输出:
COMMAND   PID  USER   FD   TYPE DEVICE SIZE NLINK   NODE NAME
httpd 10944 root 42u REG 253,0 0 0 491543 /tmp/ZCUD6duOXq (deleted)
httpd 11381 httpd 42u REG 253,0 0 0 491543 /tmp/ZCUD6duOXq (deleted)
httpd 11382 httpd 42u REG 253,0 0 0 491543 /tmp/ZCUD6duOXq (deleted)
httpd 11383 httpd 42u REG 253,0 0 0 491543 /tmp/ZCUD6duOXq (deleted)
httpd 11384 httpd 42u REG 253,0 0 0 491543 /tmp/ZCUD6duOXq (deleted)
...
这里只是一个小文件,实际可能是一个很大的文件。+L1 标识 link counts 的上限为 1,所以就是 0。

如果重启服务不行,那就 kill 掉这些进程:
sh# lsof +L1 | awk '{print $2}' | sed -n '2,$p' | uniq | xargs kill
所以最好还是先更改 httpd.conf 的 log 配置,重启 Apache 之后再移除旧日志。

参考:
http://www.prefetch.net/blog/index.php/2006/01/10/where-is-my-space/

tar cfz onefile simultaneously by 2 processes

先看看同时打包同一个文件:
#!/usr/bin/python

import os,sys

dirname = os.path.dirname(os.path.abspath(sys.argv[0]))

os.chdir(dirname)
pid1 = os.fork()
if pid1 == 0:
os.system("tar cfz /tmp/sametar.tgz src")
sys.exit(0)

pid2 = os.fork()
if pid2 == 0:
os.system("tar cfz /tmp/sametar.tgz src")
sys.exit(0)
os.wait()
这样做似乎还没有什么问题,两个进程都结束后用 tar tfz 看不出有问题。再这样看看:
sh$ tar cfz /tmp/sametar1.tgz src
sh$ diff /tmp/sametar.tgz /tmp/sametar.tgz --brief

也说明两个包相同。

再看看对不同目录打包的情况:
#!/usr/bin/python

import os,sys

dirname = os.path.dirname(os.path.abspath(sys.argv[0]))

os.chdir(dirname)
pid1 = os.fork()
if pid1 == 0:
os.system("tar cfz /tmp/sametar.tgz src")
sys.exit(0)

pid2 = os.fork()
if pid2 == 0:
os.system("tar cfz /tmp/sametar.tgz src_archive")
sys.exit(0)
os.wait()

sh$ tar tfz sametar.py
gzip: stdin: not in gzip format
tar: Child returned status 1
tar: Error exit delayed from previous errors
这时候就暴露除问题了。这应该是一个进程资源互斥的问题。而且每次报告的出错情况可能还不一样:
tar tfz /tmp/sametar.tgz
src_archive/
src_archive/file1

gzip: stdin: invalid compressed data--crc error

gzip: stdin: invalid compressed data--length error
tar: Unexpected EOF in archive
tar: Error is not recoverable: exiting now

星期一, 四月 02, 2007

sourceforge svn

现在需要使用 sourceforge 的 subversion,并将以前的版本库导入到里面。这涉及几个方面的问题,可以参考 sourceforge 的文档。
http://sourceforge.net/docs/E09

首先是需要启用 subversion。我的项目创建时默认使用的是 CVS,可以进入 "Admin"->"CVS" 禁止掉,在进入 "Admin"->"subversion" enable。

sourceforge svn 的 repos URL 是:
http://PROJECTNAME.svn.sourceforge.net/svnroot/PROJECTNAME
所以我的项目将是:
http://crablfs.svn.sourceforge.net/svnroot/crablfs
但是如果要写入,则需要使用 https。

先试试检出:
sh$ svn co https://crablfs.svn.sourceforge.net/svnroot/crablfs
svn: Unrecognized URL scheme https
要检查 svn 支持哪些 scheme,使用 svn --version 命令:
sh$ svn --version
...
he following repository access (RA) modules are available:

* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
- handles 'http' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
- handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
- handles 'file' scheme
从 google search 的情况来看,必须添加 ssl 支持,而这个支持是由 neon 库来完成的。另外还需要 apr/apr-util。我之前使用的是 1.1.4 版,neon 和 apr 都包含在 source 里了,但编译添加 --with-ssl,或单独编译 neon-0.26.3 再在 svn ./configure 时添加参数 --with-neon 都无效。

换用 subversion-1.4.3,neon 和 apr 都不再包括在源代码中了,只能自己编译。但似乎这次必须使用 apache(必须指定 apxs),即使使用 --disable-mod-activative,否则连 http scheme 都没有了。

不想为了一个 svn client 装一个 apache server,结果找了一个 1.3.2 解决这个问题,也是包含 neon 和 apr 的,
The following repository access (RA) modules are available:

* ra_dav : Module for accessing a repository via WebDAV (DeltaV) protocol.
- handles 'http' scheme
- handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
- handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
- handles 'file' scheme
添加 --with-ssl 编译。

然后需要将以前的版本库导入。不可能直接使用 svn import 导入,因为需要保留以前的所有信息。可以参考:
http://sourceforge.net/docs/E09#import
的指导。大体上,就是先使用 svnadmin dump 得到当前版本库的备份,再利用 sourceforge 的机制导入。
sh$ svnadmin dump /mnt/file/data/gears | gzip >/tmp/gears.gz
sh$ cp /tmp/gears.gz chowroc@crablfs.sourceforge.net:/home/groups/c/cr/crablfs/
这里 /home/groups/p/pr/project 就是相关文档的 DocumentRoot。

然后进入 "Admin"->"Subversion"->"Migration Instructions"->"migrate"进入:
http://sourceforge.net/project/admin/svn_migration.php?group_id=180695
填写刚才的 .gz 文件并提交即可。

最后还有一个问题,就是备份。虽然 svn 版本库保存在网络上了,但是还是需要以防万一,且不说如果 sourceforge 本身出问题如何,如果出现象地震或 GFW 等问题导致不能连接,至少还可以使用本地拷贝,如果可能还可以使用前面的 migrate 方法重新导入。用如下方法备份:
sh$ mkdir gears
sh$ cd gears
sh$ rsync -av crablfs.svn.sourceforge.net::svn/crablfs/* .
注意需要使用 svn/crablfs/* 而不是 svn/crablfs,因为 svn/crablfs 实际上是到 /home/groups/c/cr/crablfs/svnroot 的一个符号链接。

其他一些问题:
如何查询某个特定版本与另一个特定版本相比修改了哪些文件?
目前不知道如何直接利用 svn 的命令来做到,但是可以使用 --diff-cmd -x 来调用 diff,从而可以得到一个简要清单:
sh$ svn diff --diff-cmd -x --breif
因此也可以利用这一点做一些其他形式的比较,比如我比较喜欢使用 diff 的 -y --suppress-common-lines:
sh$ svn diff --diff-cmd diff -x '-y --suppress-common-lines' utilsea/trunk/fs_backup  | less

python try/except variable scope?

>>> try:
... testr = "still be effective in the except?"
... raise "exception"
... except:
... print testr
...
still be effective in the except?
所以在 try 中定义的变量在 except 中仍有效!

然后看看下面这种情况:
>>> try:
... for item in ['A', 'B', 'C']:
... print item
... raise "exception"
... except:
... print item
...
A
B
C
C
注意这里的 item,在 excpet 中仍然有效,只不过只是最后一个值。

再改成 finally:
>>> try:
... testr = "still be effective in the except?"
... raise "exception"
... finally:
... print testr
...
still be effective in the finally
Traceback (most recent call last):
File "", line 3, in ?
exception

python script suid

在较新的内核中,已经禁止了对 script 的 suid,只有二进制程序拥有 suid 权限:
#!/usr/bin/python
import os
print os.geteuid(), os.getegid()

-bash-3.00$ ls /usr/bin/passwd -l
-r-s--x--x 1 root root 21200 2005-06-17 /usr/bin/passwd

-bash-3.00$ ls test.py -l
-r-sr-xr-x 1 root root 62 Mar 26 17:43 test.py
-bash-3.00$ ./test.py
10001 10001
-rwsr--r-- 1 root root 66 Mar 30 17:59 test.py
-bash-3.00$ ./test.py
-bash: ./test.py: Permission denied
可以看到,起作用的并不是 s 位,而仅仅是 x 位。其他 script 如 perl/php 亦同。

因此在编写 cgi 脚本时需要考虑这一点,mailman 的 cgi 程序就都是二进制的程序(C 编译)。

python unzip

先,企图利用 os.popen3("unzip ...") 直接来完成,但遇到问题:
fin, fout, ferr = os.popen3("unzip -o -d %s %s" % (dest, zipfile))
strerr = ferr.read()
# 这一步挂起!
if strerr:
print >> sys.stderr, strerr
outlog.error(strerr)
估计是 unzip 命令没有在 stderr 输出文件结束符 EOF。于是只能自己编写相关函数。如下:
import zipfile
def _extract_all(self, destdir):
namelist = self.namelist()
namelist.sort()
for name in namelist:
print "extracting... %s" % name
if name.endswith('/'):
print name
os.mkdir(os.path.join(destdir, name))
else:
outfile = open(os.path.join(destdir, name), 'wb')
outfile.write(self.read(name))
outfile.close()
zipfile.ZipFile.extract_all = _extract_all

def unzip(...):
zipo = zipfile.ZipFile(zipfn, 'r')
zipo.extract_all(dest)
但是这里没有是否覆盖的选项,所以改进方案如下。

改进:
import zipfile
def _extract_all(self, destdir, overwrite=0):
namelist = self.namelist()
namelist.sort()
for name in namelist:
if os.path.exists(name) and not overwrite:
strerr = "destination %s exists" % name
print strerr
outlog.error(strerr)
break
print "extracting... %s" % name
if name.endswith('/'):
try:
os.mkdir(os.path.join(destdir, name))
except OSError:
pass

else:
outfile = open(os.path.join(destdir, name), 'wb')
outfile.write(self.read(name))
outfile.close()
zipfile.ZipFile.extract_all = _extract_all

def unzip(...overwrite=0):
zipo = zipfile.ZipFile(zipfn, 'r')
zipo.extract_all(dest, overwrite)
参考:
http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252508
http://www.thescripts.com/forum/thread25297.html