11
10
2013
5

终止永远等待网络的程序——纠结的 getmail 不再纠结

我收取邮件一直用的是 getmail,然而它有个问题:在网络不好的时候会挂在 recv 系统调用上,等好几个小时都有可能。还好我用的 crond 是 dcron,它知道同一个任务,在上一次任务还没执行完时即使时间到了也不应该再次执行,省了我一堆 flock 锁。不过,这样子导致我收不到邮件也不行啊。

以前也研究过一次,看到 getmail 有设置 socket 的超时时间啊,没整明白。最近网络又老是抽风,而且相当严重,导致我得不断地用 htop 去看、去杀没有反应的 getmail 进程。烦了,于是一边阅读 getmail 源码,一边使用 strace 观察,再配合 iptables 这神器,以及 the Silver searcher,终于找到了问题所在。

原来,由于 Python 的 SSL 对非阻塞套接字的支持问题12,getmail 在使用 SSL 连接时会强制使用阻塞式的套接字(见代码getmailcore/_pop3ssl.py:39以及getmailcore/_retrieverbases.py:187)。也许 Python 2.7 已经解决了这个问题,但是看上去 getmail 还是比较关心 Python 2.3 和 2.4。不过就算是 SSL 支持不好,调用下alarm不要一直待在那里傻傻地等嘛……也许,大部分 getmail 用户很少遇到足够差的网络?

于是考虑 fetchmail。花了两三天的业余时间终于弄明白我的需求该怎么配置了:

set daemon 300
set logfile ~/etc/log/fetchmail.log

defaults proto pop3 timeout 120 uidl
keep fetchsizelimit 0 mda "procmail -f %T"

poll pop.163.com interval 2
username "username" password "password"

poll pop.gmail.com
username "username" password "password"
ssl

poll pop.qq.com interval 2016 # 7 days
username "username" password "password"

但结果就是,除了 GMail 好一点,我让它「对从现在起所收到的邮件启用 POP」就没太大问题之外,腾讯还好,没几封邮件。网易那边,几百封旧邮件全部拖回来了…………

其实这个问题也还好,毕竟是一次性的。可我看它的日志,又发现,它每次收到 GMail 时,都会打印有多少封邮件已读。难道说,它每次去收邮件时都要列出所有可以用 POP3 收取的邮件,然后挑出没有收取过的?想到如果是这样,以后它每次取邮件时都要先取几千上万条已读邮件列表……这不跟 Google App Engine SDK 操作数据库加 offset 时前边所有数据全部读一遍一样扯淡吗……

于是又回来折腾 getmail。其实就这么一个问题,解决了就好。本来是准备去学学ptrace怎么用的,结果忍不住了,直接拿 Python 调 strace 写了这个:

#!/usr/bin/env python3

'''wait and kill subprocess if it doesn't response (from network)'''

import os
import sys
import select
import tempfile
import subprocess

timeout = 60

def new_group():
  os.setpgrp()

def main(args):
  path = os.path.join('/dev/shm', '_'.join(args).replace('/', '-'))
  if not os.path.exists(path):
    os.mkfifo(path, 0o600)
  pipe = os.open(path, os.O_RDONLY | os.O_NONBLOCK)
  p = subprocess.Popen(['strace', '-o', path, '-e', 'trace=network'] + args, preexec_fn=new_group)

  try:
    while True:
      ret = p.poll()
      if ret is not None:
        return ret
      rs, ws, xs = select.select([pipe], (), (), timeout)
      if not rs:
        print('subprocess met network problem, killing...', file=sys.stderr)
        os.kill(-p.pid, 15)
      else:
        os.read(pipe, 1024)
  except KeyboardInterrupt:
    os.kill(-p.pid, 15)

  return -1

if __name__ == '__main__':
  try:
    import setproctitle
    setproctitle.setproctitle('killhung')
    del setproctitle
  except ImportError:
    pass
  sys.exit(main(sys.argv[1:]))

Python 果然快准狠 ^_^

代码在 winterpy 仓库里也有一份

这还是我编程时第一次用到进程组呢。没办法,光杀 strace 进程没效果。嗯,还有非阻塞的命名管道


PS: 去 GMail 设置页看完那个选项的具体名字后离开,结果遇到这个:

你这是让我「确定更改」呢还是「取消取消更改」呢……

10
29
2013
11

不需要 root 权限的 ICMP ping

ICMP 套接字是两年前 Linux 内核新加入的功能,目的是允许不需要 set-user-id 和CAP_NET_RAW权限的 ping 程序的实现。大家都知道,set-user-id 程序经常成为本地提权的途径。在 Linux 内核加入此功能之前,以安全为目标的 Openwall GNU/*/Linux 实现了除 ping 程序之外的所有程序去 suid 化……这个功能也是由他们提出并加入的。

我并没有在 man 手册中看到关于 ICMP 套接字的信息。关于 ICMP 套接字使用的细节来自于内核邮件列表

使用 ICMP 套接字的好处

  1. 程序不需要特殊的权限;
  2. 内核会帮助搞定一些工作。

坏处是:

  1. 基本没有兼容性可讲;
  2. 需要调整一个内核参数。

这个内核参数net.ipv4.ping_group_range,是一对整数,指定了允许使用 ICMP 套接字的组 ID的范围。默认值为1 0,意味着没有人能够使用这个特性。手动修改下:

sudo sysctl -w net.ipv4.ping_group_range='0 10'

当然你可以直接去写/proc/sys/net/ipv4/ping_group_range文件。

如果系统不支持这个特性,在创建套接字的时候会得到「Protocol not supported」错误,而如果没有权限,则会得到「Permission denied」错误。

创建 ICMP 套接字的方法如下:

import socket
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_ICMP)

它的类型和 UDP 套接字一样,是SOCK_DGRAM,不是SOCK_RAW哦。这意味着你不会收到 20 字节的 IP 头。不仅仅如此,使用 ICMP 套接字不需要手工计算校验和,因为内核会重新计算的。ICMP id 也是由内核填的。在接收的时候,内核会只把相应 id 的 ICMP 回应返回给程序,不需要自己或者要求内核过滤了。

所以,要组装一个 ICMP ECHO 请求包头很容易了:

header = struct.pack('bbHHh', 8, 0, 0, 0, seq)

这五项依次是:类型(ECHO_REQUEST)、code(只能为零)、校验和(不需要管)、id(不需要管)、序列号。

接收起来也简单,只要看一下序列号知道是回应自己发的哪个包的就行了。

这里是我的一个很简单的实例。

附注:Mac OS X 在 Linux 之前实现了类似的功能。但是行为可能不太一样。有报告校验和需要自己计算的,也有报告发送正确但是返回报文是乱码的。另,FreeBSD 和 OpenBSD 不支持这个特性。

Category: Linux | Tags: linux python 网络 ICMP
9
16
2013
64

为 Kindle 交叉编译 Zsh 和 Python 3.3

一些天前,根据加州旅客的文章《kindle paperwhite越狱更换屏保》获得了自己的 Kindle Paperwhite 的 root 权限,就开始想着给它编译些东西了。恰巧小虾也在玩交叉编译,而且是 Python,于是自己也照着编译。

交叉编译 Zsh

交叉编译 Python 比编译 zsh 要难不少,所以,先简要说一下 zsh 等能够「无障碍交叉编译」的使用 Autotools 构建系统的软件是怎么编译的。

首先,弄清楚几个概念。「host」是指程序要运行的目标平台,「build」是指编译该程序的平台,而在编译编译器时会遇到的「target」则指的是其生成的文件所运行的平台。(参见CLFS 构建手册

其次,得找个交叉编译器。有些发行版可能仓库里就有。但是 Arch 没有,所以我还是用的 zshaolin 的这个工具链。下回来解压了,把它的bin目录加到$PATH里。Zsh 里可以这么写:

path+=/path/to/arm-dyne-gcc_64bit-x-arm7a-21jan12/bin

然后就可以去 zsh 源码目录下开始编译啦。还是那三步,只是参数有些不一样而已:

mkdir build-arm && cd build-arm
../configure --host=arm-dyne-linux-gnueabi --enable-multibyte --enable-pcre --with-term-lib='ncursesw'
make
make DESTDIR=/kindle/software/zsh-5.0.2 install

且慢!我指定了--enable-pcre,却没有去检查自己是否有这个库。可能是 bug 吧,zsh 的 configure 脚本并没有检测到我其实没有 pcre 的 ARM 版库文件,于是它接着在很多检测过程中加入了-lpcre,导致检测结果与实际不符(比如它认为我的目标系统上没有setpgid()函数)。编译安装 pcre 到工具链的 sysroot 下后再次编译通过。

再编译一些需要的库

所以你看,使用 Autotools 构建系统的软件交叉编译起来挺容易的,加上需要的--host参数就好了。其它诸如 file、ncurses、readline 也是这么编译安装的。下边说说那些比较「个性」的软件的编译参数。它们基本上都必须在源码目录编译。

首先是 zlib:

CHOST=arm-dyne-linux-gnueabi ./configure
make && make DESTDIR=/kindle/software/zlib-1.2.8 install

TLS/SSL 很有用!OpenSSL:

CC=arm-dyne-linux-gnueabi-gcc LD=arm-dyne-linux-gnueabi-ld AR=arm-dyne-linux-gnueabi-ar RANLIB=arm-dyne-linux-gnueabi-ranlib ./Configure shared linux-armv4
make
make INSTALL_PREFIX=/ldata/media/temp/kindle/software/openssl-1.0.1e install_sw

注意最后不是make install哦,那个会安装一堆不需要的文档的。

对了,我好像忘记说了,以上软件make install之后的操作

  1. 删除文档,比如 rm -rf share/man
  2. 复制到工具链的 sysroot 下,命令:
    tar c . | tar xv -C /path/to/arm-dyne-gcc_64bit-x-arm7a-21jan12/arm-dyne-linux-gnueabi/sysroot
    
  3. 给二进制文件们减肥啦(以下命令需要 zsh;非 zsher 请自行使用合适的 find 命令代替):
arm-dyne-linux-gnueabi-strip **/*(*)

另外再附上 ncurses 的配置命令好了:

../configure --host=arm-dyne-linux-gnueabi --with-shared --with-normal --without-debug --without-ada --enable-widec --enable-pc-files --prefix=/usr/local

是的,我把软件都安装到/usr/local了。在编译 Python 3.3 时的事实表明,这是给自己找麻烦……

编译 Python 啦

好了,准备工具完毕,我们来真正开始编译 Python 啦

按照小虾的文章,首先修改Modules/Setup.dist文件,把需要的模块去掉注释。一定要把最后的xxsubtype给注释掉!因为它只是很无聊的示例模块……

除了要安装模块对应的程序库外(比如要 curses 模块就得先安装 ncurses 等),还要注意一点:如果开启 readline 支持的话,把它后边的-ltermcap删掉!

开始配置了:

mkdir build-arm && cd build-arm
echo ac_cv_file__dev_ptmx=yes > config.site
echo ac_cv_file__dev_ptc=no >> config.site
export CONFIG_SITE=config.site
../configure --host=arm-dyne-linux-gnueabi --build=arm --enable-shared --disable-ipv6

根据实际 ssh 过去的结果,我的 Kindle 有/dev/ptmx但是没有/dev/ptc,所以往config.site文件里写上那么两句。没办法,交叉编译时脚本不知道目标系统里是否有这两个设备文件。当然,你都设置成no也是没什么问题的。

--build=arm这系统纯粹是 Python 的这个配置脚本要求的,和之前所说的常见使用方法不一样的。IPv6 支持需要的某函数配置脚本找不到,那就禁用掉好了。

配置完成,开始 make?

根据所开启的模块支持不同,在编译过程中很有可能地,你会遇到Parser/pgen无法执行的问题。它被编译成 ARM 版了,当然无法执行了!解决方案是这样子的:

修改pyconfig.hSIZEOF_LONG为正确值(比如 64 位 x86 下是8)。如果已经是对的就不要动了。然后重新生成个本地可运行的pgen

rm Parser/*
make CC=gcc Parser/pgen

接着把pyconfig.h改回去。然后,为了避免pgen被重建,我们让 make 认为pyconfig.h没有被修改过:

touch -t 200001010000 pyconfig.h

继续编译!

如果又出来了架构不对的情况,删除刚刚编译pgen时编译出来的目标文件吧:

rm Parser/*.o
touch -t 210001010000 Parser/pgen

touch pgen 的原因是,不能让 make 又把它编译成本地不能运行的 ARM 架构的了。

架构不对的目标文件可能还有一些,按照错误提示删掉就好了。

最后,要生成 Python 的可执行文件啦!很可能地,你会遇到类似这个的错误(我自己这里的错误信息已经没啦,下边这个由加州旅客提供):

libpython3.3m.a(timemodule.o):在函数‘py_process_time’中:
/home/jiazhoulvke/Python-3.3.2/./Modules/timemodule.c:1076:对‘clock_gettime’未定义的引用
/home/jiazhoulvke/Python-3.3.2/./Modules/timemodule.c:1082:对‘clock_getres’未定义的引用

查阅clock_getres的 man 文档得知:

Link with -lrt (only for glibc versions before 2.17).

于是,复制 make 最后执行的那条链接命令,在后边加上lrt吧。如果crypt没有定义的话,还要加上-lcrypt

终于可以安装啦

接下来,当然是把程序安装到 Kindle 上啦!首先执行个make DESTDIR=xxx install安装到某个目录,然后进去清理下吧:

arm-dyne-linux-gnueabi-strip **/*(*)
cd lib/python3.3
# 删除所有你不想要的模块,比如测试代码(`test`,不是`unitest`哦)、tk/idle,
# 还有 distutils 里一堆乱七八糟的东东

# 删除 Python 源码和 pyc 文件,我们只要 pyo 文件就好啦=w=
rm **/*.pyc?
# 在 zip 文件里 Python 可不认 __pycache__……
perl-rename 's=__pycache__/([^.]+).cpython-33.pyo$=\1.pyo=' **/*.pyo
rmdir **/*(/)
zip -9r ../python33.zip .

然后,把生成的python33.zip放到 Kindle 的/usr/local/lib目录下,Python 二进制文件也放到对应的位置。记住,库文件要使用 tar 而非 scp 来传输!像这样子:

tar c libz.so* | ssh kindle tar xv -C /usr/local/lib

其实不少库 Kindle 上已经有了,比如这里的 zlib。不过很奇怪,使用系统自带的 zlib 运行 Python 时会报如下警告:

python3: /usr/lib/libz.so.1: no version information available (required by /usr/local/lib/libpython3.3m.so.1.0)

另一个我发现无关紧要的警告是让你设置PYTHONHOME环境变量的:

Could not find platform independent libraries <prefix>
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]

其实不设置也是没关系的。

哦对了,现在我们的 Python 应该还跑不起来的吧!有可能缺少一点库文件的哦!把之前编译生成的对应的库文件全部拿tar扔到/usr/local/lib下吧。再说一遍,使用scp传输的话软链接会变成其指向的文件,浪费掉 Kindle 上宝贵的存储空间!

库文件扔进去之后,首先确认/etc/ld.so.conf里已经包含了/usr/local/lib,然后执行下ldconfig

终于,我们的 Python 在 Kindle 上跑起来啦!

后记,及下载链接

这个是我编译的 Python 的文件大小:

-rwxr-xr-x 1 root root 5.4K Sep 15 00:15 /usr/local/bin/python3.3
-r-xr-xr-x 1 root root 4.0M Sep 15 00:15 /usr/local/lib/libpython3.3m.so.1.0
-rw-r–r– 1 root root 2.2M Sep 15 00:43 /usr/local/lib/python33.zip

主要支持特性有:SSL、readline、ncurese、zlib、中日编码集、Unicode 数据库等。最后再放百度网盘下载链接(包括好些东东哦)。

最后我要说一句,Kindle 才是真正的 Linux 啊!编译起来如此方便!还各种常见库(包括 glibc、zlib、OpenSSL、GTK 2)都有。想之前给 Android 编译点东西得砍掉多少特性啊!又有多少软件死活编译不成功 :-(

PS: Kindle 虽然也用 Java 的,但是它有 X Window,还有 GTK 2 以及 Awesome 窗口管理器哦~可惜它的 Awesome 没开启 D-Bus 支持。

更新:lxml

今天(2013年9月17日),成功编译了 Python 最著名的 XML 处理模块——lxml。编译方法是,指定CC环境变量,复制python3 setup.py build时出错的那两条编译命令并修改,编译出来目标文件存起来。将CC指定为自己的脚本来「生成」它想要的文件。链接时手动改命令链好就行。

因为有 .so 文件,lxml 不能打成 zip 包。因此我直接将*.pyc*.pyo文件连同.so文件一同复制到 Kindle 的/usr/local/lib/python3.3/lxml下。由于版本不匹配,需要把libxml2.so.2.9.1文件也传到 Kindle 中去,覆盖了 Kindle 中旧版本的库文件,希望不会有问题

Category: Linux | Tags: python zsh 交叉编译 kindle
8
23
2013
11

xmodmap 和 fcitx 配合使用

很早之前,因为有了 fcitx-keyboard,fcitx 能够管理键盘布局了。于是乎,经常干了什么事情之后,xmodmap 的效果就没了。

为了解决这个问题,fcitx 可以在相关事件时自动调用 xmodmap 命令。然后我发现,xmodmap 命令经常会调用很多很多次。我笔记本的配置还好,那个 xmodmap 配置调用多次会有命令失败,所以只要调整下顺序就可以保证键映射正确:

keysym Pause = Print
remove Lock = Caps_Lock
keysym Escape = Caps_Lock
keysym Caps_Lock = Escape
add Lock = Caps_Lock
keycode 107 = Super_R Sys_Req Super_R Sys_Req

这样子日志里会多一些消息,无所谓了,反正桌面日志我只保留最近的一份。

可是,我另外的系统上只需要交换 EscCaps Lock 这两个键:

remove Lock = Caps_Lock
keysym Escape = Caps_Lock
keysym Caps_Lock = Escape
add Lock = Caps_Lock

于是,当 fcitx 调用偶数次 xmodmap 时,这两个键就给交换回去了……实际的效果是,我几乎每次从挂起中恢复,都需要手动执行一次 xmodmap。更烦的是,几乎每次在 gnome-screensaver 里输入密码时,大小写切换键默认是开着的。这时候我得按按 Esc 或者 Caps Lock,或者是输入一个字符后再按它们中的一个。一直以来没找到规律……

最后,终于查阅 xmodmap 手册,写了下面这个简单的脚本:

#!/bin/bash -e

[[ -n $(xmodmap -pk | awk '$1 == 66 && $3 == "(Escape)"') ]] || xmodmap ~/.Xmodmap

如果 Esc 键已经交换过了,就不要再交换一次了。

再设置 fcitx 执行这个我自己的脚本就可以了:

fcitx 中的 xmodmap 配置

2013年8月28日更新:csslayer 最近已经修复了 fcitx 多次调用 xmodmap 的问题,不再需要这样特别的设置了。感谢 csslayer 的及时修正=w=

Category: Linux | Tags: fcitx X Window X window xmodmap
8
22
2013
6

BSD 版 xargs

BSD 版 xargs 与 GNU 版有一个显著的不同——它支持-J选项。

比如说,你使用 find 命令得到了一个文件列表。你要将它们传递给一个叫concat_files的程序来处理后生成一个指定的新文件,比如:

concat_files file1 file2 file3 output

而且,这个命令不像 cp 或者 mv 那样,有个-t参数来把目标文件放到不定长的文件列表之前。总之呢,你不得不构建一行命令,它的中间部分是你会从管道传过去的文件列表。而 GNU xargs 要么全给你放末尾(默认),要么每项执行一次命令(指定-I时)。而 BSD xargs 则可以用-J选项指定一个占位符,使用这个占位符指明参数插入的位置:

find ... | xargs -J % concat_files % output

BSD xargs 的另一个特有参数是-o,作用你们就自己看文档啦=w=

我想在 Linux 上使用 BSD xargs,怎么办呢?在 AUR 里搜索到了这个,但是已经编译不过去了。安装 bmake 后手动边改边编译,最终终于成功编译了 obase 中的大多数工具。我知道的比较有特色的也就这个 xargs 了,于是单独打了个包 bsdxargs,放在我的 lilydjwg 源 里。

附,obase 的补丁:

diff --git a/Makefile b/Makefile
index 2bb18b4..96acf8a 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,8 @@ LIBOBASE=${.CURDIR}/libobase/libobase.a
 INCLUDES_libobase=-isystem ${.CURDIR}/libobase/include
 COPTS_libobase=-D_GNU_SOURCE
 DPLIBS+=${LIBOBASE}
+LDADD+= ${LIBOBASE}
+.export LDADD
 .export COPTS DPLIBS HOSTCC HOSTCFLAGS USE_DPADD_MK

 SUBDIR=\
diff --git a/src/bin/ls/Makefile b/src/bin/ls/Makefile
index defd607..6ad4725 100644
--- a/src/bin/ls/Makefile
+++ b/src/bin/ls/Makefile
@@ -3,6 +3,6 @@
 PROG=  ls
 SRCS=  cmp.c ls.c main.c print.c util.c
 DPADD= ${LIBUTIL}
-LDADD= -lutil
+LDADD+= -lutil

 .include <bsd.prog.mk>
diff --git a/src/usr.bin/awk/Makefile b/src/usr.bin/awk/Makefile
index 54857d3..9d2d243 100644
--- a/src/usr.bin/awk/Makefile
+++ b/src/usr.bin/awk/Makefile
@@ -2,7 +2,7 @@

 PROG=  awk
 SRCS=  ytab.c lex.c b.c main.c parse.c proctab.c tran.c lib.c run.c
-LDADD= -lm
+LDADD+=    -lm
 DPADD= ${LIBM}
 CLEANFILES+=proctab.c maketab ytab.c ytab.h stamp_tabs
 CFLAGS+=-I. -I${.CURDIR} -DHAS_ISBLANK -DNDEBUG
diff --git a/src/usr.bin/dc/Makefile b/src/usr.bin/dc/Makefile
index b0a2396..f8ee358 100644
--- a/src/usr.bin/dc/Makefile
+++ b/src/usr.bin/dc/Makefile
@@ -3,7 +3,7 @@
 PROG=  dc
 SRCS=  dc.c bcode.c inout.c mem.c stack.c
 COPTS+= -Wall
-LDADD= -lcrypto
+LDADD+=    -lcrypto
 DPADD= ${LIBCRYPTO}

 .include <bsd.prog.mk>
diff --git a/src/usr.bin/du/Makefile b/src/usr.bin/du/Makefile
index feb644d..9676f37 100644
--- a/src/usr.bin/du/Makefile
+++ b/src/usr.bin/du/Makefile
@@ -2,6 +2,6 @@

 PROG=  du
 DPADD= ${LIBUTIL}
-LDADD= -lutil
+LDADD+= -lutil

 .include <bsd.prog.mk>
diff --git a/src/usr.bin/gzsig/Makefile b/src/usr.bin/gzsig/Makefile
index 0dc7b81..f4f0664 100644
--- a/src/usr.bin/gzsig/Makefile
+++ b/src/usr.bin/gzsig/Makefile
@@ -3,7 +3,7 @@
 PROG   = gzsig
 SRCS   = gzsig.c key.c sign.c ssh.c ssh2.c util.c verify.c x509.c

-LDADD  = -lcrypto -lm
+LDADD  += -lcrypto -lm
 DPADD  = ${LIBCRYPTO} ${LIBM}

 CLEANFILES += TAGS *~
diff --git a/src/usr.bin/lex/Makefile b/src/usr.bin/lex/Makefile
index 080a151..27a783e 100644
--- a/src/usr.bin/lex/Makefile
+++ b/src/usr.bin/lex/Makefile
@@ -17,7 +17,7 @@ SRCS= ccl.c dfa.c ecs.c gen.c main.c misc.c nfa.c parse.c sym.c tblcmp.c \
    yylex.c
 OBJS+= scan.o skel.o
 CLEANFILES+=parse.c parse.h scan.c skel.c y.tab.c y.tab.h
-LDADD= -lfl
+LDADD+=    -lfl
 DPADD= ${LIBL}

 MAN = flex.1
diff --git a/src/usr.bin/m4/Makefile b/src/usr.bin/m4/Makefile
index 7c510f5..16a282c 100644
--- a/src/usr.bin/m4/Makefile
+++ b/src/usr.bin/m4/Makefile
@@ -8,7 +8,7 @@ CFLAGS+=-DEXTENDED -I.
 CDIAGFLAGS=-W -Wall -Wstrict-prototypes -pedantic \
    -Wno-unused -Wno-char-subscripts -Wno-sign-compare

-LDADD= -ly -lfl -lm
+LDADD+= -ly -lfl -lm
 DPADD= ${LIBY} ${LIBL} ${LIBM}

 SRCS=  eval.c expr.c look.c main.c misc.c gnum4.c trace.c tokenizer.l parser.y
diff --git a/src/usr.bin/make/Makefile b/src/usr.bin/make/Makefile
index a63ed94..1d12280 100644
--- a/src/usr.bin/make/Makefile
+++ b/src/usr.bin/make/Makefile
@@ -14,7 +14,7 @@ CDEFS+=-DHAS_EXTENDED_GETCWD

 CFLAGS+=${CDEFS}
 HOSTCFLAGS+=${CDEFS}
-LDADD= -lrt
+LDADD+=    -lrt

 SRCS=  arch.c buf.c cmd_exec.c compat.c cond.c dir.c direxpand.c engine.c \
    error.c for.c init.c job.c lowparse.c main.c make.c memory.c parse.c \
diff --git a/src/usr.bin/mandoc/Makefile b/src/usr.bin/mandoc/Makefile
index cf565fd..6086a81 100644
--- a/src/usr.bin/mandoc/Makefile
+++ b/src/usr.bin/mandoc/Makefile
@@ -9,7 +9,7 @@ CFLAGS+=-W -Wall -Wstrict-prototypes
 CFLAGS+=-Wno-unused-parameter
 .endif

-LDADD= -ldb
+LDADD+= -ldb

 SRCS=  roff.c tbl.c tbl_opts.c tbl_layout.c tbl_data.c eqn.c mandoc.c read.c
 SRCS+= mdoc_macro.c mdoc.c mdoc_hash.c \
diff --git a/src/usr.bin/script/Makefile b/src/usr.bin/script/Makefile
index d7dbf01..8837084 100644
--- a/src/usr.bin/script/Makefile
+++ b/src/usr.bin/script/Makefile
@@ -1,7 +1,7 @@
 #  $OpenBSD: Makefile,v 1.3 1997/09/21 11:50:42 deraadt Exp $

 PROG=  script
-LDADD= -lutil
+LDADD+=    -lutil
 DPADD= ${LIBUTIL}

 .include <bsd.prog.mk>
diff --git a/src/usr.bin/ul/Makefile b/src/usr.bin/ul/Makefile
index bab290c..12295ec 100644
--- a/src/usr.bin/ul/Makefile
+++ b/src/usr.bin/ul/Makefile
@@ -2,6 +2,6 @@

 PROG=  ul
 DPADD= ${LIBCURSES}
-LDADD= -lcurses
+LDADD+=    -lcurses

 .include <bsd.prog.mk>
diff --git a/src/usr.bin/vacation/Makefile b/src/usr.bin/vacation/Makefile
index 6f08990..f9ef0d6 100644
--- a/src/usr.bin/vacation/Makefile
+++ b/src/usr.bin/vacation/Makefile
@@ -1,6 +1,6 @@
 #  $OpenBSD: Makefile,v 1.3 1997/09/21 11:51:42 deraadt Exp $

 PROG=  vacation
-LDADD= -ldb
+LDADD+=    -ldb

 .include <bsd.prog.mk>
diff --git a/src/usr.bin/wc/Makefile b/src/usr.bin/wc/Makefile
index 3f3c619..0f3d1a2 100644
--- a/src/usr.bin/wc/Makefile
+++ b/src/usr.bin/wc/Makefile
@@ -2,6 +2,6 @@

 PROG=  wc
 DPADD= ${LIBUTIL}
-LDADD= -lutil
+LDADD+= -lutil

 .include <bsd.prog.mk>

参见

Category: Linux | Tags: xargs BSD shell
8
6
2013
2

利用 cups 通过网络使用 Samsung SCX-4650 4x21S Series 打印机

首先去官网下个 Unified Linux Drivers(ULD)包,里边有我们需要的 .ppd 文件以及一个 cups filter。splix 和 gutenprint 包里有不少 ppd 文件,但是没有我要的这个型号的。此 ppd 文件中引用了一个名叫 rastertospl 的 cups filter,而 splix 里只有 rastertoqspl,不知道能不能用。我还是用官方给的好了。

安装 cups 并启动之:

systemctl start cups

在那个包里找到自己机器架构的 rastertospl 以及 libscmssc.so 文件,前者扔到/usr/lib/cups/filter目录下,后者扔到/usr/lib下即可。

访问 http://localhost:631/admin ,勾选右边的「Share printers connected to this system」,这样 cups 才能找到网络打印机。点「Change Settings」后会请求用户名和密码。使用 root 及相应的密码登录即可。然后就可以「Find New Printers」了。找到之后就知道打印机的 IP 地址了。(其实用 ULD 包里那个smfpnetdiscovery程序也是可以的。)然后访问 http://打印机IP:631/ 在协议里找到了它的 IPP 协议地址:ipp://打印机IP/ipp/printer。cups 默认给出的是socket://,不知道那是干什么的。忘了添加时能不能修改了,不能的话就待会再修改连接地址好了。然后填名字描述什么的,下边会向你要 ppd 文件,或者从系统已有列表里选。从下载回来的 ULD 包里找到那个Samsung_SCX-4650_4x21S_Series.ppd文件扔给它就好。配置完毕就可以用啦啦。

其实挺简单的。不过初次配置时遇到了点麻烦:

出现了两次 filter failed 错误。第一次的日志(位于/var/log/cups/error_log)是:

PID 20744 (/usr/lib/cups/filter/gstoraster) stopped with status 13.

gstoraster 是 ghostscript 包里的。通过 strace 和源码得知它退出是因为子进程 gs 在向标准输出写转换好的 raster 格式数据时出现了 SIGPIPE。Google 许久未果,最后按某帖里的建议把打印机删掉再重新添加就好了……

第二次是 rastertospl 退出 1。(rastertospl 没找到那个错误很明显就不算啦。)这个通过 strace 发现它在一些路径寻找libscmssc.so文件。在 ULD 里找到这个库并扔到它会去找的目录下就好了。

最后贴一下通过 strace 抓到的那些 cups filter 的命令行调用参数:

PPD=/etc/cups/ppd/Samsung_SCX-4650_4x21S_Series.ppd strace /usr/lib/cups/filter/rastertospl 4 lilydjwg doc.pdf 1 "InputSlot=Auto noJCLSkipBlankPages Quality=600dpi number-up=1 MediaType=None TonerSaveMode=Standard JCLDarkness=NORMAL PageSize=A4 EdgeControl=Fine job-uuid=urn:uuid:570129b0-1656-3f8d-5c8d-0edc9322c11f job-originating-host-name=localhost time-at-creation=1375697623 time-at-processing=1375701265" doc.raster > doc.spl
Category: Linux | Tags: Linux 打印机 外部设备
7
26
2013
5

flock——Linux 下的文件锁

当多个进程可能会对同样的数据执行操作时,这些进程需要保证其它进程没有也在操作,以免损坏数据。

通常,这样的进程会使用一个「锁文件」,也就是建立一个文件来告诉别的进程自己在运行,如果检测到那个文件存在则认为有操作同样数据的进程在工作。这样的问题是,进程不小心意外死亡了,没有清理掉那个锁文件,那么只能由用户手动来清理了。像 pacman 或者 apt-get 一些数据库服务经常在意外关闭时留下锁文件需要用户清理。我以前写了个 pidfile,它会将自己的 pid 写到文件里去,所以,如果启动时文件存在,但是对应的进程不存在,那么它也可以知道没有其它进程要访问它要访问的数据(这里只讨论如何避免数据的并发讨论,不考虑进程意外退出时的数据完整性)。但是,Linux 的 pid 是会复用的。而且,检查 pidfile 也有点麻烦不是么?(还有竞态呢)

某天,我发现了 flock 这个系统调用。flock 是对于整个文件的建议性锁。也就是说,如果一个进程在一个文件(inode)上放了锁,那么其它进程是可以知道的。(建议性锁不强求进程遵守。)最棒的一点是,它的第一个参数是文件描述符,在此文件描述符关闭时,锁会自动释放。而当进程终止时,所有的文件描述符均会被关闭。于是,很多时候就不用考虑解锁的事情啦。

flock 有个对应的 shell 命令也叫 flock,很好用的。使用最广泛的 cronie 这个定时任务服务很笨的,不像小巧的 dcron 那样同一任务不会同时跑多个。于是乎,服务器上经常看到一堆未退出的 cron 任务进程。把所有这样的任务包一层 flock 就不会导致 cronie 启动 N 个进程做同一件事啦:

flock -n /tmp/.my.lock -c 'command to run'

即使是 dcron,有时会有两个操作同一数据的任务,也需要使用 flock 来调度。不过这次不用-n参数让文件被锁住时失败退出了。我们要等拥有锁的进程完事再执行。如下,两个任务(有所修改),一个是从远程同步数据到本地的,另一个是备份同步过来的数据的。同时执行的话,就会备份到不完整的数据了。

*/7 *    * * * ID=syncdata       LANG=zh_CN.UTF-8 flock /tmp/.backingup -c my_backup_script
@daily         ID=backupdata     LANG=zh_CN.UTF-8 [ -d ~/data ] && cd ~/data && nice -n19 ionice -c3 flock /tmp/.backingup -c "tar cJf backup_$(date +"%Y%m%d").tar.xz data_dir --exclude='*~'"

flock 命令除了接收文件名参数外,还可以接收文件描述符参数。这种方法在 shell 脚本里特别有用。比如如下代码:

lockit () {
  exec 7<>.lock
  flock -n 7 || {
    echo "Waiting for lock to release..."
    flock 7
  }
}

exec行打开.lock文件为 7 号文件描述符,然后拿 flock 尝试锁它。如果失败了,就输出一条消息,并且等待锁被释放。那个 7 号文件描述符就让它一直开着了,反正脚本执行完毕内核会释放,也不用去调用trap内建命令了。

上边有一点很有意思的是,flock 是一个子进程,但是因为文件描述符在 fork 和 execve 中会共享,而 flock 锁在 fork 和 execve 时也不会改变,所以子进程在那个文件描述符上加锁后,即使它退出了,因为那个文件描述符父进程还有一份,所以不会被关闭,锁也就得以保留。(所以,如果余下的脚本里要是有进程带着那个文件描述符 fork 到后台锁就不会在脚本执行完后自动解除啦……)

PS: 经我测试,其它一些类 Unix 系统上或者没有 flock 这个系统调用,只有 fcntl 那个,或者行为和 Linux 的不一样。

Category: Linux | Tags: linux shell
7
10
2013
4

grub2 引导 openSUSE 安装镜像

想安装 openSUSE 12.2,但是目标机器没有光驱,亦没有可用的能够容纳下 DVD 镜像的 U 盘。尝试 dd 镜像到 U 盘,报告找不到光驱还是什么的,启动失败,自动重启。 官方 Wiki 上 http://en.opensuse.org/Installation_without_CD 这个页面已经被删除。其它页面只有如何将 ISO 镜像弄到 U 盘上的说明,没有说明如何正确启动之。grub2 带内核参数install=hd:$isofile失败。这个据说只对 DVD 镜像有效。

最终,像很早之前那样阅读init脚本后,终于得出正确的启动方法:

menuentry "openSUSE 12.2 KDE LiveCD x86_64" {
    set isofile="/images/openSUSE-12.2-KDE-LiveCD-x86_64.iso"
    echo "Setup loop device..."
    loopback loop $isofile
    echo "Loading kernel..."
    linux (loop)/boot/x86_64/loader/linux isofrom=/dev/disk/by-label/4lin:$isofile
    echo "Loading initrd..."
    initrd (loop)/boot/x86_64/loader/initrd
}

其中,isofrom指定 ISO 文件所在的设备和路径,以冒号分隔。如果没有写对的话,将得到Failed to find MBR identifier !错误。

2013年12月22日更新:对于 openSUSE 13.1,其引导命令应该这么写:

menuentry "openSUSE 13.1 KDE Live x86_64 (zh_CN)" {
	set isofile="/images/openSUSE-13.1-KDE-Live-x86_64.iso"
	echo "Setup loop device..."
	loopback loop $isofile
	echo "Loading kernel..."
	linux (loop)/boot/x86_64/loader/linux isofrom_device=/dev/disk/by-label/4lin isofrom_system=$isofile LANG=zh_CN.UTF-8
	echo "Loading initrd..."
	initrd (loop)/boot/x86_64/loader/initrd
}
Category: Linux | Tags: linux grub grub2
7
9
2013
4

把标准输出伪装成终端

fcitx-diagnose 是 fcitx 输入法的非常优秀的诊断脚本。当输出到终端时,fcitx-diagnose 会给输出加上易于区分不同类型的消息的彩色高亮。可是,当用户把输出重定向到文件以便让其他人帮助查看时,这些高亮就没了。fcitx-diagnose 的输出很长,但如果通过管道给 less 查看的看,这些彩色也会消失。

要是 fcitx-diagnose 支持--color=always这样的选项就好了。可是 yyc 说他懒得写。getopt我只在 C 里用过,好麻烦的,所以我也懒得写。于是,我还是用我的 ptyless 好了。后来又想到,用于改变 I/O 缓冲方式的 unbuffer 和 stdbuf 应该也可以。测试结果表明,只有 unbuffer 可行,因为它是和 ptyless 一样使用伪终端的。stdbuf 则是使用 LD_PRELOAD 载入一个动态链接库的方式来设置缓冲区。

不过,既然 stdbuf 用 LD_PRELOAD 来设置缓冲区,我何不来用相同的办法改变isatty()函数的返回值呢?同时,我也学学 stdbuf,试了下__attribute__ ((constructor))指令。

#include<stdarg.h>
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<dlfcn.h>

static int (*orig_isatty)(int) = 0;

int isatty(int fd){
  if(fd == 1){
    return 1;
  }
  return orig_isatty(fd);
}

void die(char *fmt, ...) {
  va_list args;
  va_start(args, fmt);
  vfprintf(stderr, fmt, args);
  va_end(args);
  fprintf(stderr, "\n");
  fflush(stderr);
  exit(-1);
}

__attribute__ ((constructor)) static void setup(void) {
  void *libhdl;
  char *dlerr;

  if (!(libhdl=dlopen("libc.so.6", RTLD_LAZY)))
    die("Failed to patch library calls: %s", dlerror());

  orig_isatty = dlsym(libhdl, "isatty");
  if ((dlerr=dlerror()) != NULL)
    die("Failed to patch isatty() library call: %s", dlerr);
}

然后,像 stdbuf、proxychains 那样做了个包装,不用自己手动设置 LD_PRELOAD 环境变量了。这也是我第一次使用 CMake,比 GNU 的 autotools 那套简单多了 :-)

使用方法很简单:

  1. 克隆或者下载源码
  2. 编译之
    $ mkdir -p build && cd build
    $ cmake .. # 或者安装到 /usr 下: cmake .. -DCMAKE_INSTALL_PREFIX=/usr
    $ make
    
  3. 安装之
    $ sudo make install
    $ sudo ldconfig
    
  4. 可以使用了:
    $ stdoutisatty fcitx-diagnose | less
    
Category: Linux | Tags: C代码 linux 终端 shell

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com