5
29
2016
9

配置 Postfix 通过外部 SMTP 服务器发邮件

程序要发邮件。

我不想自己去连外部 SMTP 服务器,因为我懒得自己去处理各种错误。我们服务器上很多程序用的是 heirloom mailx 软件的 mail 命令。结果是有些服务器上有好几个甚至几十个 mail 进程卡在那里了。还有不知道通过什么东西发邮件的,偶尔会有邮件没发出来,发出来了的还因为在一个字符的多个字节之间换行再编码导致 mutt 和 Android 的 GMail 程序显示为乱码的。

所以我要用 Postfix,让它来处理这些杂事。之前只把 Postfix 用作普通的 SMTP 服务器过。然而现在目标邮寄地址是腾讯企业邮箱,对由子域名发出的邮件很不友好,老是扔垃圾箱里,连加白名单都没用……于是搜了一下,Postfix 是可以作为客户端登录到 SMTP 服务器来发信的。不过资料比较少,好不容易配置好了,自然要记录一下。

要登录腾讯企业邮箱发信,main.cf 里写上:

relayhost = [smtp.exmail.qq.com]:587

smtp_sasl_auth_enable = yes
smtp_sasl_security_options = noanonymous
smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd
smtp_use_tls = yes
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_sender_dependent_authentication = yes
smtp_generic_maps = hash:/etc/postfix/generic

/etc/postfix/sasl_passwd 里边写用户名和密码,比如 user@example.com 用户的密码是「password」,就这么写:

[smtp.exmail.qq.com]:587 user@example.com:password

/etc/postfix/generic 里配置信封上的发件人(MAIL FROM 命令)的地址重写,不然腾讯不收:

@hostname user@example.com

hostname 就是 Postfix 里那个 myhostname,默认是机器的主机名。这句配置的意思是,本来发件人地址是这个主机名的,全部改写成 user@example.com 这个。

然后生存 hash 数据库,并更改权限(更好的做法当然是创建的时候就弄好权限):

postmap /etc/postfix/sasl_passwd
postmap /etc/postfix/generic
chmod 600 /etc/postfix/sasl_passwd*

就绪,启动或者重新加载 Postfix 就可以了:

systemctl reload postfix
Category: Linux | Tags: 电子邮件 Postfix
5
21
2016
16

当 SSD 坏掉之后

某天,我注意到系统日志中有如下报错:

ata2.00: exception Emask 0x0 SAct 0x80 SErr 0x0 action 0x0
ata2.00: irq_stat 0x40000008
ata2.00: failed command: READ FPDMA QUEUED
ata2.00: cmd 60/18:38:64:ad:96/00:00:00:00:00/40 tag 7 ncq 12288 in
         res 41/40:00:64:ad:96/00:00:00:00:00/00 Emask 0x409 (media error) <F>
ata2.00: status: { DRDY ERR }
ata2.00: error: { UNC }
ata2.00: configured for UDMA/133
sd 1:0:0:0: [sdb] tag#7 UNKNOWN(0x2003) Result: hostbyte=0x00 driverbyte=0x08
sd 1:0:0:0: [sdb] tag#7 Sense Key : 0x3 [current] [descriptor] 
sd 1:0:0:0: [sdb] tag#7 ASC=0x11 ASCQ=0x4 
sd 1:0:0:0: [sdb] tag#7 CDB: opcode=0x28 28 00 00 96 ad 64 00 00 18 00
blk_update_request: I/O error, dev sdb, sector 9874788
bcache: bch_count_io_errors() sdb1: IO error on reading dirty data from cache, recovering
ata2: EH complete

啊,看来我的 SSD 要坏掉了么?!拿 bcache-status 看了一下状态,已经500多个报错了,但是我并没有遇到什么问题。看了一下 SSD 的 SMART 信息,也是500多个报错。这种报错出现已经有几天了,但是似乎影响并不大?SMART 报告也说「状况正常」。那就等有时间再处理好了,先把 bcache 缓存策略从「writeback」改成「writethrough」好了。我当时是这么想的。

出事的 SSD 是笔记本自带的,16G,被我用作缓存了。使用的是 bcache,一年之前我写文章讲过的。没想到刚刚一年,就开始出问题了。

过了一两天之后。我正上班中,同步 MediaWiki 数据时发现自己的机器又连不上了。我起初以为是网络问题,因为我记得很清楚我的机器是开着的。但是过了一会儿它还是不能访问。我 ping 了一下,却发现是通的!登陆到我作为路由器的树莓派上却发现,网络是正常的。能 ping 通笔记本,但是从树莓派上也 ssh 不了,访问 nginx 也没回应。所以笔记本还在线,但是系统出问题了。奇怪的是,访问另一端口上我用 Tornado 写的 Web 程序时却是正常的。

终于等到了下班。回到家里之后就听见笔记本风扇呼呼地响着。赶紧打开查看。htop 显示 journald 正在使用 CPU。于是去看 journald 日志,心里顿时凉了半截:日志只记录到上午,但满是上边那样的 SSD 报错。进一步的检查发现,/ 分区已经被挂载为只读,syslog-ng 日志比 journald 日志记录了更多的报错。火狐已经不知道什么时候挂掉了,并且启动不了。sudo 还是好的,但是运行 zsh 时会段错误。运行 bash 的话,会报一点错,可以看到有些文件已经变成无意义的数据了。bash 能启动,但是很奇怪的是,输入不了字符 e……想起还装了 dash,于是用这个连最基本的补全都没有的 shell,配合还没有挂掉的连着 VPS 的 mosh 上的 IRC 上网友们的帮助,尝试禁用 SSD 缓存。

结果是失败了。关于 bcache 有些资料与我看到的情况并不一致。重启会报一些错,然后我进入了 initramfs 里的 shell,尝试禁用 SSD,依旧失败中,脏数据写不回去,所以禁用不了缓存……不过看到了缓存中脏数据只有几百K。当然单位是什么我就不知道了……

放弃。又一天,我改从救援系统进入。记得有个工具能把普通分区转成 bcache。手机上网找到了,blocks。但是它并不能把 bcache 转回来。好在是开源项目,我读了一下代码,结合别人的说法,结论是:把普通分区转为 bcache,只要把分区缩小,在起始处空出一点空间,然后写入 bcache 的超级块就可以了。于是反过来,只要跳过 bcache 的超级块来读,就可以读到原来的分区数据了。

但是,bcache 超级块有多大呢?不知道。不过之前看到过一个扫数据的脚本,用类似的办法找一下分区数据的位置好了:

for i in {1..20}; do dd if=/dev/sda2 skip=$i | file -; done

file 显示在第8块的地方发现了「LUKS encrypted file」。这就是我要找的文件系统了!

为保险起见,先读读看。使用 losetup 把 /dev/sda2 映射成只读的 loop 设备,但是跳过开头的 8192 字节(dd 的块是 512 字节一个)。然后用 cryptsetup 解密。解密成功了。只读挂载解密之后的 ext4 文件系统。挂载成功了。看了一下内核日志,没有报错,没有警告。chroot 进去,正常,没有段错误,配置文件没有变成无意义的乱码。一切进行得好顺利!损坏程度比我以为的要轻不少呢!

为了确认损坏情况,我进行了一次模拟备份,也就是 rsync 的「--dry-run」选项。这样与最近的一次备份作比较,看看差异。结果很乐观,rsync 只报了十来个 I/O 错误。不过这也说明文件系统还是有问题的。

又一天。开始恢复系统了。还是从救援系统进入。进入 gdisk,删除原来的 sda2 分区,新建一个小了 8192 字节的分区。经验表明这样子删掉再重建分区表是没有问题的。也确实没出什么问题。不带 bcache 的文件系统出来了。解密之,然后使用 fsck 修复。修复过程中,fsck 问了我一大堆是不是要修复的问题。修复完毕之后,更改内核选项以便适应新的分区表,重启。

一切正常。不过根据之前 rsync 和修复过程中提到的文件名,去看了一下那些文件。都是最近更新系统时的一些 Python 文件。有一些文件消失了,另一些文件的权限和类型变得很奇怪。删掉,重装安装对应的软件包。完事了~不过没有了 SSD 缓存,能够感觉到有些操作会慢不少。

这 SSD 也真容易坏啊。刚刚一年呢。而且坏的过程很快,预警期只有几天。现在 SMART 报告已经出现过1000多个错误了,但整体评估还是「磁盘状况正常」……

好在我用的都是自由软件,虽然可能找遍整个互联网都没有个说法,但是还有实现它们的源码可以阅读,可以自己去思考解决方案。当然啦,就算这个 bcache 完全坏掉了,我也不需要太惊慌,因为我有备份嘛,更麻烦,但最多只损失几天的数据,不会突然之间什么都没有了。

Category: Linux | Tags: linux 数据恢复 bcache SSD
4
29
2016
7

一个系统,两套网络

创建并配置一个新的网络命名空间

公司 VPN 用的网段是 192.168.1.0/24,而我家里的网络,就像大多数人的一样,也是 192.168.1.0/24。网段冲突了。当然啦,隔离得相当好的 lxc 可以解决这个问题。可是它隔离得太好了,我可不想再多维护一套环境。那么,就只隔离网络好了!

正好之前看到 iproute2 套件可以管理网络命名空间。那么,就用它啦。

首先,创建一个名为 vpn 的网络命名空间:

ip netns add vpn

现在如果进去看的话,会发现里边只有一个孤零零的 lo 网络接口,所以里边是一片与世隔绝的黑暗。我没有其它的网络接口给它用。桥接当然是可以尝试的,只是需要更改已有的网络配置。所以还是用另外的方案吧。弄根网线来,把里边和外边连接起来好了。

创建一对 veth 接口。这就是我们的「网线」~

ip link add vpn0 type veth peer name vpn1

一端留外边,另一端移到 vpn 里边去:

ip link set vpn1 netns vpn

接好网线的两端:

ip link set vpn0 up
ip netns exec vpn ip link set vpn1 up

好了,现在两个网络之间可以通信了~当然,只是链路层。为了使用 TCP/IP,我们得分配 IP 地址。在外边的这个就不需要 IP 地址了,因为我要把它接到一个网桥上,网桥自己有 IP 的。

brctl addif br0 vpn0

这里 br0 是我已有的网桥(给 lxc 用的那个)。如果你没有的话,就自己按以下方法弄一个。我用的是 bridge-utils 里的 brctl 命令。iproute2 也可以做的。

brctl addbr br0
ifconfig br0 192.168.57.1
iptables -t nat -A POSTROUTING -s 192.168.57.1/24 -j MASQUERADE

最后的 iptables 命令是做 NAT 啦。当然内核的 IPv4 转发功能还要开启的。

vpn0 这端弄好了,再给 vpn1 分配 IP,并设置相关路由表项:

ip netns exec vpn ip address add dev vpn1 192.168.57.101/24
ip netns exec vpn ip route add default via 192.168.57.1

现在就可以在里边连 VPN 啦~

ip netns exec vpn openvpn ...

当然也可以去里边开个 zsh 用

ip netns exec vpn zsh

不过要注意如果跑 GUI,或者连接 D-Bus 的话,需要手动把相关环境变量移进去。虽然是独立的网络命名空间,但是因为共享了文件系统,所以虽然看不到在监听的 UNIX 套接字,但是连起来还是没有问题的。X 和 D-Bus 都可以良好工作的。

export DISPLAY=:0 LANG=zh_CN.UTF-8 LANGUAGE=zh_CN:zh_TW
export GTK_IM_MODULE=fcitx QT_IM_MODULE=fcitx XMODIFIERS=@im=fcitx
export DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus
# 如果在 tmux 里的话
export TMUX=1

再加上 mount 命名空间吧

我这里在里边得用 IP 地址,因为我的 DNS 是 127.0.0.1,在里边当然是用不了的。怎么办呢?我不想改 DNS 服务器地址,因为里外的文件系统是共享的。那就再加上 mount 命名空间吧,这样就可以 bind mount 一个修改过的文件到 /etc/resolv.conf 上了。

其实呢,ip netns 是通过把网络命名空间(/proc/<pid>/ns/net)bind mount 到文件来实现命名空间的持久化的(不然使用这个命名空间的进程都退出,该命名空间就销毁了)。其文件位于 /run/netns 下。对于 mount 命名空间我们可以手工这么做:

mkdir -p /var/run/ns
mount --bind /var/run/ns /var/run/ns
# 命名空间只能 bind mount 到 private 挂载的文件系统上
mount --make-private /var/run/ns
# 随意找个普通文件就行。一般是用空文件;我这样可以省一个文件~
cp /etc/resolv.conf /var/run/ns

然后用 unshare 建立新的 mount 命名空间,并进入之前的 vpn 网络命名空间(当然用 nsenter 进入也是可以的):

unshare --mount=/var/run/ns/resolv.conf ip netns exec vpn zsh

创建了之后就可以用 nsenter 进去玩儿了:

nsenter --mount=/var/run/ns/resolv.conf --net=/var/run/netns/vpn zsh

可以在里边各种 bind mount,不会影响外边的哦:

# 在新的命名空间里边
mount --bind /var/run/ns/resolv.conf /etc/resolv.conf
vim /etc/resolv.conf

组合更多的命名空间

当然也可以组合更多种的命名空间的。我还试过 pid 命名空间,不过 pid 命名空间比较特殊:它在 fork 后才生效,当 init 进程(pid=1 的进程)退出之后所有位于此 pid 命名空间的进程都会被杀死,并且再也进不去了。所以不是很好玩的啦。

创建与进入的命令如下:

unshare --mount-proc -f --pid=/var/run/ns/pid --mount=/var/run/ns/resolv.conf nsenter --net=/var/run/netns/vpn su - lilydjwg
# 进入时用 -t 选项,或者重新 bind mount 文件(不然新进程会在原 pid 命名空间)
nsenter -t 9416 --mount=/var/run/mountns/resolv.conf --net=/var/run/netns/vpn --pid su - lilydjwg

后记

除了可能偶尔连接公司 VPN 会用到这个技术之外,我又给其找到了一个更好的使用场合:Wine QQ!于是本地的 nginx 日志里终于不会再有 /srv/http/cgi/reccom 找不到的提示了,CGI 服务也不会被不必要地启动了。

2016年5月13日更新:自用的脚本放在 Gist 上了。

Category: Linux | Tags: linux 网络 netns
1
10
2016
3

配置 spamassassin 来过滤垃圾邮件

虽然 GMail 的垃圾邮件过滤功能十分强大,误判率很低。但是我另有使用网易的邮件服务的域名邮箱,屡次教它哪些是垃圾邮件却依然不奏效。于是在本地配置一下 spamassassin 好了。

安装。在 Arch Linux 上这样子:

sudo pacman -S spamassassin
sudo sa-update

教学。之前就计划用 spamassassin,所以预先收集了不少垃圾邮件样本。喂给 sa-learn 学习一下:

sa-learn --spam --mbox < ~/.Mail/spam

过滤。编辑 ~/.procmailrc 配置文件,让 procmail 将邮件交给 spamassassin 检查,如果判定为垃圾邮件就放到特定的邮箱中:

:0fw: spamassassin.lock
* < 256000
| /usr/bin/vendor_perl/spamassassin

:0:
* ^X-Spam-Status: Yes
autospam

完成。其实挺简单的。

11
4
2015
23

从 slim 到 lightdm

从一开始使用 Arch Linux,我就选择了 slim 作为登录管理器。因为它轻量嘛,而且配合 Arch Linux 自己做的主题也挺漂亮的:

slim 登录界面

所以即使 slim 不再使用配置文件来指定有哪些桌面环境可用,改用无法指定顺序的 .desktop 文件,我只好告诉 pacman,不要升级 slim 了。

于是就这么用了很久,直到 lightdm 出世,直到 slim 所使用的托管网站关闭、停止开发,我也依然在用 slim。

直到有一天,那是在 systemd 开始使用用户级别的 session D-Bus 之后。我登出了会话,再次登入时,发现整个系统都不好了,因为 DBUS_SESSION_BUS_ADDRESS 环境变量没有被设置,导致程序找不到 session bus 而自动启动了一个。可我用户级别的 systemd 还在旧的 session bus 里呢,联系不上了。当然 tmux 里所使用的 session bus 也开始混乱了(我有让 tmux 从环境里更新各种 window 中的 DBUS_SESSION_BUS_ADDRESS 变量)。

究其原因,是因为 slim 没有正确地处理 sesssion。从 loginctl 及 systemd-cgls 可以看到,重新登录 slim 之后,进程并没有处在新的会话里,而是复用了旧的会话。

systemd 上,会话管理是 pam_systemd 来管理的,同时它会引入 DBUS_SESSION_BUS_ADDRESS 环境变量。PAM 会话是有一个 leader 进程的,它的结束标志着这个会话的结束(当然里边存活的进程还会继续存在)。比如文本终端登录用的 login,每一次登录都是一个新进程。比如 sshd,每个连接都是由单独的子进程来处理,PAM 会话也是那个时候打开的。然而 slim 却是在父进程里打开了 PAM 会话。于是 pam_systemd 一看,这个 session leader 怎么又要打开会话啦?报错:

8月 02 21:54:32 lilyforest slim[669]: pam_systemd(slim:session): Cannot create session: Already running in a session

所以,slim 下,只有第一次登录是正常的……

所以是时候换个跟得上时代的登录管理器了。那就 lightdm + GTK greeter 好了。

这个我以前也用过,不过没怎么配置所以背景一片漆黑,难看死了。这次制作了张背景图,在/etc/lightdm/lightdm-gtk-greeter.conf里配置一下

[greeter]
background=/usr/local/share/pixmaps/background.png

咦?还有头像?那就放一个~/.face好了。什么?lightdm 你没权限读取它?OK,给你权限:

setfacl -m u:lightdm:x ~
setfacl -m u:lightdm:r ~/.face

我的 HOME 目录的权限是 750,别人(other)进不来的,所以要给 lightdm x 权限。

最终的样子就是这样,也挺漂亮的,功能还挺全 :-)

lightdm 登录界面

Category: Linux | Tags: linux X Window systemd X window
10
26
2015
52

使用 Wine 运行 QQ 轻聊版

2021年07月11日更新:轻聊版已经不可用。

截图

Wine QQ 轻聊版 7.5

注:图中的文字方框已经解决。

安装使用

测试过的 Wine 版本为 1.7.52、1.7.54。1.7.53 是坏的。1.6 系列大概跑不起来。

Wine 环境下载地址签名,使用的新 key)。压缩包中安装的是官方完整版的 QQ 轻聊版 7.7(安装文件名 QQ7.7Light.exe)。

文件最后更新于2016年1月29日

  1. 下载文件并解压、找个地方放好(移动之后启动图标会失效)。
  2. 如果系统上没有 simsun.ttc 字体,去网上下一个放到 ~/.fonts 中。或者到解压出来的 Wine 环境里 winetricks fakechinese 也行。
  3. 运行解压出来的目录下边的install-icon.sh脚本来安装启动图标

安装好之后,在你通常找所有已安装的应用程序的地方就可以找到「Wine QQ 轻聊版」的启动图标了。你也可以运行解压出来的目录下边的qq脚本来启动,但必须在那个目录下运行。

在火狐里点击「加入QQ群」之类tencent://协议的链接时,也是可以调用到的哦~不过即使已经登录,也会开启新的 QQ 登录界面。但是不这样又会有更严重的问题,所以凑合着用吧。

其它调整,如使 QQ 无限制地访问文件系统(默认只允许访问 C 盘)、更改一些文件的存放位置、字体选择与渲染选项,请自行修改。想要 Flash 的也请自己 winetricks 安装。

已知问题

  • 记不住密码
  • 密码框需要耐心地点几下才能开始输入
  • 表情图片只有启动后的第一次使用时正常,之后弹出窗口的内容不能正常显示
  • 窗口周围部分的显示不正常(可能和 Awesome 有关)
  • 使用 Windows 内建的移动窗口功能(拖动窗口边框、标题栏)时可能会导致窗口开始避开鼠标指针(可能和 Awesome 有关)
  • 部分群文件下载能够成功,但是界面上看到的进度一直是0字节未完成状态(比较小的文件不受影响)

制作步骤

由于之前的 TM2013 已经很久没更新了,有很多功能都跟不上(比如群公告、语音消息等),所以我又 Wine 了新版本。QQ 轻聊版感觉就是 QQ 标准版去掉了广告,不用付费开通会员来去掉那些干扰广告了。

首先 winetricks 一下:

winetricks sandbox riched20

然后把 Windows 版本设置为Windows 8.1(大于 XP 即可)。不设置安装不上,因为缺少CmRegisterCallback函数。

将 QQ7.5Light.exe 安装文件链接到 C 盘内,安装。

再打开 winecfg,作如下设置:

  • 设置 Windows 版本为 Windows XP
  • 「增加程序设置」,找到 drive_c/Program Files/Common Files/Tencent/QQProtect/Bin/QQProtect.exe 文件,设置其版本为 Windows 8.1(不然会出错)
  • 回到「默认设置」,切换到「函数库」标签,添加「txplatform.exe」「函数库顶替」,并且设置为「禁用」

禁用 TXPlatform.exe 会导致tencent://链接不能在已有的 QQ 里打开。但是启用它会导致登录不了或者退出不了(卡住)的情况。

从 Windows 上复制 iphlpapi.dll,放到 system32 目录,并设置原装优先,以绕过「查找」对话框打开时卡死的问题。

修改system.reg注册表文件,设置 Tahoma 的 FontLink 项(不然部分文字乱码):

[Software\\Microsoft\\Windows NT\\CurrentVersion\\FontLink\\SystemLink] 1420549548 0
"Tahoma"="simsun.ttc,SimSun"

当然你也可以把这里的宋体替换成你想要的任何字体。

另外,这里有人在制作并维护 Wine QQ 精简版。

2015年11月12日更新:更新到 QQ 轻聊版 7.7。旧版用户以 Windows 8.1 版本号来运行 7.7 的安装程序即可更新。

旧的 QQ 轻聊版 7.5 Wine 环境下载地址签名,使用的新 key)。压缩包中安装的是官方完整版的 QQ 轻聊版 7.5(安装文件名 QQ7.5Light.exe)。

2016年3月5日更新:请禁用QQProtectUpd.exe或者将drive_c/Program Files/Common Files/Tencent/QQProtect目录及其下的所有文件设置为只读(chmod -R -w 目录),以免 QQ 自动后台更新之后崩溃。

Category: Linux | Tags: linux windows QQ wine 腾讯
9
13
2015
22

为树莓派交叉编译 8192eu 网卡驱动

最近打算把闲置了许久的树莓派重新利用起来。交给它的第一个任务是:做路由器。于是去弄了个 USB 无线网卡,型号是 TP-Link WN823N 版本 2.0。买的时候没注意,拿到手才知道这款需要自行安装驱动。还得使用特制版本的 hostapd。

驱动名叫 8192eu,或者 rtl8192eu,随便啦。GitHub 上有多个版本,我使用的是 Mange/rtl8192eu-linux-driver。因为 gcc 及内核更新的原因,需要修改两处:

diff --git a/Makefile b/Makefile
index 0c800f8..85058fa 100644
--- a/Makefile
+++ b/Makefile
@@ -13,6 +13,7 @@ EXTRA_CFLAGS += -Wno-unused-label
 EXTRA_CFLAGS += -Wno-unused-parameter
 EXTRA_CFLAGS += -Wno-unused-function
 EXTRA_CFLAGS += -Wno-unused
+EXTRA_CFLAGS += -Wno-date-time

 #EXTRA_CFLAGS += -Wno-uninitialized

diff --git a/os_dep/linux/rtw_android.c b/os_dep/linux/rtw_android.c
index 98f0d31..8a2ee56 100644
--- a/os_dep/linux/rtw_android.c
+++ b/os_dep/linux/rtw_android.c
@@ -337,7 +337,7 @@ int rtw_android_cmdstr_to_num(char *cmdstr)
 {
        int cmd_num;
        for(cmd_num=0 ; cmd_num<ANDROID_WIFI_CMD_MAX; cmd_num++)
-               if(0 == strnicmp(cmdstr , android_wifi_cmd_str[cmd_num], strlen(android_wifi_cmd_str[cmd_num])) )
+               if(0 == strncasecmp(cmdstr , android_wifi_cmd_str[cmd_num], strlen(android_wifi_cmd_str[cmd_num])) )
                        break;

        return cmd_num;

然后,本文的主题来了:我需要 ARM 版的驱动!因为我的树莓派没有键盘也没有显示器,也没有网线什么的。除了电源和 SD 卡,它只有一块无线网卡了。所以只能交叉编译了。

本来呢,内核使用的构建系统非常棒,一切都会很顺利的。但是,我不要先交叉编译个 ARM 版内核。于是我遇到了这个问题scripts 目录下的二进制文件是编译模块的时候需要执行的,然而我的机器并不能执行 ARM 版本的二进制。

好吧,不就是一些小程序么。把我本机的复制过去就可以跑了嘛。结果开心地看着各源码文件被编译成目标文件之后,遇到 modpost 报了这么个错误:

FATAL: section header offset=11258999068426292 in file '/ldata/DATA/src/rtl8192eu-linux-driver/8192eu.o' is bigger than filesize=1094666

大概是因为我的系统是 64 位的,然而 ARM 是 32 位的吧。不过我没兴趣去找一个 i686 版本的 modpost 来尝试了。真要在我笔记本上跑 ARM 程序又不是不可以,我们有 qemu 嘛。虽然是模拟器,不过我不觉得它会比在我那树莓派上运行慢 :-)

以下是整个完整的步骤:

首先说明一点,我使用的是 Arch Linux ARM。树莓派官方提供的 Raspberry 镜像里东西太多了,我的 SD 卡放不下我也用不着。而且它是基于 Debian Wheezy 的,没有 systemd 可用。

新建一个目录rpi,开始啦!

因为要运行 ARM 版的 modpost 程序,我们先下载树莓派的 gcc-libs、glibc,并解压出其 /usr/lib 下的文件:

wget https://mirrors.ustc.edu.cn/archlinuxarm/armv6h/core/gcc-libs-5.2.0-2-armv6h.pkg.tar.xz https://mirrors.ustc.edu.cn/archlinuxarm/armv6h/core/gcc-libs-5.2.0-2-armv6h.pkg.tar.xz.sig https://mirrors.ustc.edu.cn/archlinuxarm/armv6h/core/glibc-2.22-1-armv6h.pkg.tar.xz https://mirrors.ustc.edu.cn/archlinuxarm/armv6h/core/glibc-2.22-1-armv6h.pkg.tar.xz.sig
gpg --verify glibc-2.22-1-armv6h.pkg.tar.xz.sig
tar xf glibc-2.22-1-armv6h.pkg.tar.xz usr/lib || true
gpg --verify gcc-libs-5.2.0-2-armv6h.pkg.tar.xz.sig
tar xf gcc-libs-5.2.0-2-armv6h.pkg.tar.xz usr/lib
[[ ! -f lib ]] && ln -s usr/lib lib

要编译内核模块,当然少不了 linux-*-headers 包了:

wget https://mirrors.ustc.edu.cn/archlinuxarm/armv6h/core/linux-raspberrypi-headers-4.1.6-3-armv6h.pkg.tar.xz https://mirrors.ustc.edu.cn/archlinuxarm/armv6h/core/linux-raspberrypi-headers-4.1.6-3-armv6h.pkg.tar.xz.sig
gpg --verify linux-raspberrypi-headers-4.1.6-3-armv6h.pkg.tar.xz.sig
tar xf linux-raspberrypi-headers-4.1.6-3-armv6h.pkg.tar.xz usr

不必每次更新 gcc-libs 和 glibc,只要它们能跑 modpost 程序就可以了。但是内核头文件是要和系统上运行的内核匹配的。

我们删掉 ARM 版的 scripts 目录,换上本机的版本。但是 modpost 例外。同时要修改 Makefile.modpost,使之使用 qemu-arm 来运行 modpost 程序:

pushd usr/lib/modules/4.1.6-3-ARCH/build
mv scripts/mod/modpost .
rm -rf scripts
cp -r /usr/lib/modules/$(uname -r)/build/scripts .
sed -i '/^modpost =/s/scripts/qemu-arm scripts/' scripts/Makefile.modpost
mv modpost scripts/mod
popd

最后就可以编译啦。把交叉编译工具链签名)的路径加到 $PATH 里去。还要设置 QEMU_LD_PREFIX 到我们解压出来的那些文件所在的目录好让 qemu-arm 能够找到需要的库文件。然后进入驱动目录,开始编译!

path+=/ldata/DATA/soft/arm-lilydjwg-linux-gnueabi/bin
export QEMU_LD_PREFIX=$PWD
cd ../rtl8192eu-linux-driver
make CROSS_COMPILE=arm-lilydjwg-linux-gnueabi- KSRC=../rpi/usr/lib/modules/4.1.6-3-ARCH/build ARCH=arm
gzip 8192eu.ko

就酱。

试错几次之后,终于把配置写对了,于是我看到树莓派的 Wi-Fi 灯闪动了,随即从系统日志看到 hostapd 和 dnsmasq 都报告它连上网了~然后 ssh 登陆过去:

Last login: Tue Jun 11 22:57:29 2013 from 192.168.2.101

两年零三个月没进去过了呢。然后,我换 USTC 源,执行了pacman -Syu跨越两年零三个月的滚动更新,然而除了很多配置文件有新版本需要手工合并外,并没有发生什么特别的事情,就更没有滚挂了=w=

后来我也尝试在树莓派上直接编译内核模块(因为内核升级了嘛)。结果表明,交叉编译是正确的选择!在树莓派上编译这个模块的时间,我的笔记本估计可以编译出整个内核了……

这是我编译的 8192eu 模块签名文件,对应内核版本 4.1.6。


至于 hostapd,下载这个,把其中的wpa_supplicant_hostapd-0.8_rtw_r7048.20130424.tar.gzhostapd目录下的东西编译了就好。只需要指定CC变量就可以交叉编译成功。

这是我编译的 hostapd签名文件。配置文件中要写driver=rtl871xdrv

Category: Linux | Tags: linux 交叉编译 树莓派
7
31
2015
10

交换 ThinkPad 键盘上的 Insert 和 End 键

ThinkPad 键盘上的第一行键现在默认在不按下Fn键时执行多媒体按键功能,按下Fn时才能执行F1-F12的功能,对于像我这种 Vimhtop 等的用户来说颇为不便。还好,BIOS 选项里可以改回来。

然后我遇到了 X250,发现InsertEnd键怎么也受那个选项的影响了!也就是,如果F1-F12设置得方便了,那么End键就需要Fn键配合。而HomeEnd这种光标移动键虽然很少用,但毕竟还是要用到的,比如在 htop、weechat、mutt 以及不支持自定义编辑键的 Qt 程序里的时候。

所以呢,我在~/.Xmodmap里把这两个键交换了:

keysym End = Insert
keysym Insert = End

这下子用笔记本上的键盘是没问题了。可是我用外接键盘的话,这两个键就又反过来了 Orz……

当然网上会有 udev 规则,在插上外设时跑个脚本什么的。可不管怎么映射,总有个键盘的按键是反的啊

仔细询问 Google 之后,在 Gentoo 的论坛里终于发现这么一条线索

To alter keymap of a particular keyboard you need to issue EVIOCSKEYCODE ioctl on corresponding /dev/input/eventX node.

所以,找到相应的 event 设备文件之后,只需要 ioctl 一下就可以了?可是EVIOCSKEYCODE是个什么鬼啊……

找过 manpages、头文件、内核文档、Google 之后,我不得不相信这个东西真的没文档!于是只好看源码了……还好有 LXR,不用在本地近一个G的源码里搜索。

所以,翻完文档又试验,最终有了这个程序:

#include<sys/ioctl.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<linux/input.h>

#define EVENT_FILE "/dev/input/by-path/platform-i8042-serio-0-event-kbd"

int main(int argc, char **argv){
  unsigned int codes[][2] = {
    {0xd2, 107}, //Insert -> End
    {0xcf, 110}, //End -> Insert
    {0, 0},
  };
  int fd = open(EVENT_FILE, O_RDONLY);
  if(fd < 0) {
    perror("open " EVENT_FILE);
    exit(1);
  }

  unsigned int (*p)[2];
  for(p=codes; *p[0]; p++){
    if(ioctl(fd, EVIOCSKEYCODE, *p)) {
      perror("ioctl EVIOCSKEYCODE");
      exit(1);
    }
  }
  return 0;
}

那个EVENT_FILE当然就是笔记本键盘的节点啦,在/dev/input/by-path下很容易识别的。

编译之后,每次启动系统后执行一次就可以了(大概)。

对了,顺便说一下,找那些代码可以用 showkey 和 getkeycodes 之类的命令。当然我们有 setkeycodes,但是它不能为指定键盘单独设置。传进去的参数,第一个整数是 scancode,就是硬件上报的编码,第二个是 keycode,内核给键的编码,不同硬件的不同 scancode 可以对应同一个 keycode(比如本文所做的)。然后文本终端还有 keymap、X Window 还有键盘布局和 xmodmap,大概是把 keycode 映射到可读的键名。

7
26
2015
1

一个简单的 zsh 模块

曾经,我让 Awesome 收养孤儿进程,以保持一个清晰的进程树。后来我又想让 zsh 也做这个 prctl 系统调用,免得子进程 fork 之后跑太远。比如 Wine 跑起来就好多个服务进程,如果不能把它们全部关掉的话,再启动另一个版本的 Wine 会出问题的。而当我启动好些个不同版本的 Wine 环境之后,只看到 Awesome 下边挂了一堆 Wine 的进程,却不知道哪些属于哪个 Wine 环境的了。

zsh 本身并不支持做这个调用,不过如同 Python 和 Lua 一样,zsh 也可以通过共享库来扩展功能。不同的是,zsh 模块是没有文档的……好在 zsh 源码里提供了一个 example 示例模块。把它改改就有了以下代码:

把这两个文档保存到 zsh 源码目录的Src/Modules下,可能还需要编辑一下config.modules文件,然后编译就可得 subreap.so 文件。把这个文件放到/usr/lib/zsh/$ZSH_VERSION/zsh/subreap.so然后就可以用了:

zmodload zsh/subreap
subreap

模块加载之后,多了个subreap内建命令。不带参数即调用prctl(PR_SET_CHILD_SUBREAPER, 1),这样不管其子进程怎么 fork,都会在此 zsh 的进程树之下。使用subreap -u来取消这个设置。

如果你不想编译而又是 Linux 64 位系统,可以试试我编译好的版本:下载地址, 签名, SHA1: 09eb1cc9ebf6ec1e681641c0a60f57425cbb1e8c。

Category: Linux | Tags: linux zsh C代码
6
21
2015
0

在用户命名空间中运行 LXC 虚拟机

不用 sudo 也可以跑 LXC 虚拟机啦。使用 root 权限的 LXC 虚拟机,里边的 root 权限就是真实的 root 权限,虽然不太能够跑出来。而利用用户命名空间来启动的普通权限的 LXC 虚拟机则只在那个虚拟机里有 root 权限,从外面看跟一普通用户一样的。

首先需要一枚启用了CONFIG_USER_NS的内核。使用以下命令查看:

zgrep USER_NS /proc/config.gz

部分发行版会默认禁用用户命名空间功能,需要手动启用,参见 vagga 的安装文档。而 Arch Linux 不喜欢给软件打补丁,而这个特性又被认为是不安全的,所以并没有启用。当然这并不妨碍自己编译一个启用了这个特性的内核啦,比如 linux-lily 从 4.0.1 开始启用此特性。

注意:这个特性被认为不安全的,会时不时地爆出个提权漏洞(比如前不久这个),请谨慎启用。

内核支持没问题的话就可以开始配置了。以下配置过程主要参考 Arch Linux 论坛里的这篇帖子

首先给自己配置一些子 UID 和子 GID,也就是自己的分身。我在/etc/subuid/etc/subgid内写下如下内容

lilydjwg:100000:65536

意思是说,我(lilydjwg)被授权使用从 100000 开始的 65536 个 UID 和 GID。这一步是需要 root 权限的。这个配置好之后就可以创建用户命名空间了,比如:

lxc-usernsexec -m u:0:100000:1 -m g:0:1000:1 -m g:1:100000:1 -- /bin/zsh

此命令是说,创建一个用户命名空间,其中 UID 从 0 开始,实际对应于外边 100000 开始的 UID,总共分配一个;GID 从 0 开始,实际对应于外边 1000 开始的 GID,总共分配一个。执行之后可以看到新启动的 zsh 已经是 root 权限了。不过cat /etc/shadow就会发现还是没权限 :-D 在里边 touch 个文件的话,在外边看会是 UID 为 100000 的用户创建的。我之所以要指定 GID 的映射,是因为我的 HOME 目录外人读不了的。为了加载 zsh 的配置,就把自己的 GID 映射给它了。

注意:如果这里没有包含 /etc/subgid 中的 GID 区间,那么 shadow 4.6 将不允许 setgroups,导致命令失败。(只要有任意一部分即可。)

当然我也可以把自己的真实 UID 映射过去,这样子除了被里边的进程自认为有 root 权限之外没什么别的差异。用户命名空间要配合别的命名空间一起用才有意思。

然后要配置一下 cgroup,不然 lxc 会报错的。这一步也是需要 root 权限的。

echo 1 | sudo tee /sys/fs/cgroup/cpuset/cgroup.clone_children

for d in /sys/fs/cgroup/*; do
    sudo mkdir $d/$USER
    sudo chown -R $USER: $d/$USER
done

用处后边再说。

虚拟机里的网络是分开的。默认是没有网络的。想要的话得先授权,向/etc/lxc/lxc-usernet文件里写入

lilydjwg veth br0 10

其中br0是桥接用的网络接口名。没有就自己建一个:

brctl addbr br0
ifconfig br0 192.168.57.1
iptables -t nat -A POSTROUTING -s 192.168.57.1/24 -j MASQUERADE

这些当然也是需要 root 权限的。

还要告诉 LXC 使用用户命名空间:在~/.config/lxc/default.conf写入:

 lxc.include = /etc/lxc/default.conf
 lxc.id_map = u 0 100000 65536
 lxc.id_map = g 0 100000 65536

然后,去弄一个 LXC 系统镜像吧:

lxc-create -t download -n lxcname

名字自己起。这个命令会让你选择你要的发行版和版本的。这一步不需要 root 权限了。镜像文件列表可以看这里

等它跑完之后新的 LXC 虚拟机的 root 文件系统已经就绪了。不过在启动它之前先去编辑一下它的配置文件,加入网络配置。默认它位于~/.local/share/lxc下与 LXC 虚拟机同名的目录下。

在配置文件里加上

lxc.network.type = veth
lxc.network.link = br0
lxc.network.flags = up
lxc.network.ipv4 = 192.168.57.4
lxc.network.name = eth0

在启动之前还要做一件事——将当前进程加入到之前创建的 cgroup 中:

for d in /sys/fs/cgroup/*; do echo $$ > $d/$USER/tasks; done

然后就可以启动 LXC 虚拟机啦。当然是不需要 root 权限的:

lxc-start -F -n lxcname

当然,得给里边的 root 用户设置一个密码,不然登录不了的。可以使用 lxc-usernsexec 来 chroot 过去:

lxc-usernsexec -- chroot rootfs /bin/bash

2019年07月31日更新:修正 lxc-usernsexec setgroups 可能失败的问题。

Category: Linux | Tags: linux lxc

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com