3
9
2014
8

GM 脚本:GMail 日期正常化

我所谓的「正常化」,就是适合人而不是机器读取的格式啦。比如你们说「12/2/14」这是哪天呢……明明可以显示成「20YY年M月DD日 上午H:MM」这样子的(鼠标移过去会出现),但是为什么非要我去移鼠标呢。于是就有了这个很简单的脚本:

// ==UserScript==
// @name           GMail 日期正常化
// @namespace      http://lilydjwg.is-programmer.com/
// @description    将 GMail 中的日期使用更适合人阅读的形式显示
// @include        https://mail.google.com/*
// @grant	   none
// ==/UserScript==

var doit = function(){
  var elements = document.querySelectorAll('span[alt]');
  for(var i=0, len=elements.length; i<len; i++){
    elements[i].textContent = elements[i].getAttribute('alt');
  }
};
document.addEventListener('overflow', doit);
window.addEventListener('focus', doit);

点此安装

Category: 火狐 | Tags: gmail GreaseMonkey
3
8
2014
9

编译 Android 版 htop

编译完 lsof,却发现 htop 还是不能用……另开一个终端 strace 之,才知它竟然在调用 /bin/sh 而不是 /system/bin/sh。检查之,原来这个 htop 来自 Terminal IDE,是一个静态链接的版本,大概是使用 glibc 的吧,所以才会去标准位置寻找命令解释器。

编译过程:

path+=/opt/android-ndk/toolchains/arm-linux-androideabi-4.8/prebuilt/linux-x86_64/bin
mkdir android-build && cd android-build
CFLAGS='-I/ldata/media/temp/android/installed_binaries/include/ncurses -I/ldata/media/temp/android/installed_binaries/include' LDFLAGS=-L/ldata/media/temp/android/installed_binaries/lib CC='arm-linux-androideabi-gcc --sysroot=/opt/android-ndk/platforms/android-18/arch-arm' ../configure --host=arm-linux-gnu --prefix=/system --bindir=/system/xbin --disable-unicode
rm ../config.h

然后修改当前目录中的 config.h 文件,注释掉 HAVE_NATIVE_AFFINITY 行,添加两个 syscall 的定义,即:

// #define HAVE_NATIVE_AFFINITY 1
#define SYS_ioprio_get __NR_ioprio_get
#define SYS_ioprio_set __NR_ioprio_set

继续 make 即可。

编译好的 htop 可由此下载

Category: Android | Tags: Android 交叉编译
3
8
2014
3

编译 Android 版 lsof

Android 自带的那个 lsof 实际上是 toolbox 里的,功能十分单一,除了显示出所有进程的所有打开的文件外就什么都不能做,连说明也没有 :-( 于是为了 htop 用着爽一点,还是自己编译一个吧。

首先弄个 GNU 工具链。Android NDK 的没有 tcp.h 头文件,会报 TCP_LISTEN 等标识符未定义。

  • 我使用 crosstool-NG 1.19.0 编译的 gcc 4.8.1。适用于 Arch Linux x86_64。第一次编译工具链,没想到在 crosstool-NG 的帮助下一次就编译好了,所以是未作静态链接的版本,其它版本的 Linux 可能无法运行。
  • zshaolin 使用的工具链 gcc 4.4.6。它是使用 crosstool-NG 1.13.2 编译的。

编译过程。使用了静态链接,最终文件大小 741K。

path+=/ldata/txtfiles/soft/arm-lilydjwg-linux-gnueabi/bin
LSOF_CC=arm-lilydjwg-linux-gnueabi-gcc LSOF_CFGF='-DHAS_STRFTIME -DHASNORPC_H -DGLIBCV' LSOF_VERS=3.0.8 ./Configure -n linux
make
arm-lilydjwg-linux-gnueabi-gcc -o lsof -static dfile.o dmnt.o dnode.o dproc.o dsock.o dstore.o arg.o main.o misc.o node.o print.o proc.o store.o usage.o util.o -L./lib -llsof
arm-lilydjwg-linux-gnueabi-strip lsof

编译好的 lsof 可由此下载

Category: Android | Tags: Android 交叉编译
3
6
2014
5

使用 PyQt 转换网页到 PDF

代码很简单,功能也很简单 =w=

#!/usr/bin/env python3

import sys

try:
  from PyQt4 import QtWebKit
  from PyQt4.QtCore import QUrl
  from PyQt4.QtGui import QApplication, QPrinter
except ImportError:
  from PySide import QtWebKit
  from PySide.QtCore import QUrl
  from PySide.QtGui import QApplication, QPrinter

app = QApplication(sys.argv)

def done(status):
  p = QPrinter()
  p.setOutputFormat(QPrinter.PdfFormat)
  p.setOutputFileName('a.pdf')
  view.print(p)
  app.exit()

view = QtWebKit.QWebView()
view.load(QUrl('http://lilydjwg.is-programmer.com/'))
view.loadFinished[bool].connect(done)
# PySide does not have QApplication.exec
app.exec_()

注意:虽然没有图形界面,但是还是需要 X 连接……

Category: python | Tags: Python PyQt Qt
3
3
2014
17

《冰雪奇缘》之缘——A站弹幕 JSON 转 crt

都是《冰雪奇缘》惹的祸,不仅画面美仑美奂,音乐激荡人心,而且还玩多语言版本的主题曲

you-get 竟助纣为虐,把歌词给下回来了!只叹 soimort 没有好人做到底,留一个语义不详的 JSON 让我情何以堪。

好在 muzuiget 才识不凡,有码略释其义,遂如我光影、声音、文字三位一体之愿!

编成代码数十行,只为女王歌一曲:

#!/usr/bin/env python3

import json
import sys

def cmtjson_reader(f):
  data = json.load(f)
  for o in data:
    c = o['c'].split(',')
    m = o['m']
    # see
    # https://github.com/muzuiget/niconvert/blob/master/niconvert/libsite/acfun.py
    start = float(c[0])
    yield start, m

def format_time(t):
  s, ms = divmod(t, 1)
  s = int(s)
  ms = int(ms * 1000)
  m, s = divmod(s, 60)
  h, m = divmod(m, 60)
  return '%d:%02d:%02d,%03d' % (h, m, s, ms)

def crt_writer(f):
  i = 0

  fmt = '%d\n%s --> %s\n%s\n\n'
  try:
    while True:
      t, m = yield
      ts = format_time(t)
      if i != 0:
        f.write(fmt % (i, format_time(old_t), ts, old_m))
      i += 1
      old_t = t
      old_m = m
  except GeneratorExit:
    f.write(fmt % (
      i+1, format_time(t), format_time(t+2), m))

def main():
  w = crt_writer(sys.stdout)
  w.send(None)
  old_d0 = 0
  for d in cmtjson_reader(sys.stdin):
    if d[0] < old_d0:
      break
    w.send(d)
    old_d0 = d[0]

if __name__ == '__main__':
  main()

mplayer 君虽识得 UTF-8 之码,却不通 fontconfig 之术,部分文字沦为了下划线。又及。

Category: python | Tags: python
3
2
2014
3

FUSE 初体验:Android dedupefs

自打知道 FUSE 以来都觉得亲手写一个 FUSE 文件系统是很好玩的事情,但是因为没好的自己能够很快实现的点子所以一直没动手。前段时间需要从 Android xrecovery 备份中取得一旧版本的应用,才决定动手的,顺便也练习一下很久没怎么用到的 C 语言。至于为什么不用 Python,好像那个 Python 绑定不太稳定的样子,Python 3 版更是如此。而且我也不希望效率太差。

首先介绍一下,所谓的「dedupefs」,就是把 Android xrecovery 的「dedupe」备份格式的数据挂载成文件系统来查看。其实仅仅只是想查看的话,把那个 dedupe 目录下的东东 gcc 一下就可以创建和解开 dedupe 的备份了,只是占用很多磁盘空间而已。

dedupe 的格式很简单,一个文本文件描述文件信息(时间、路径、大小、类型等),一个目录里全是 sha256 命名的文件来存储文件的数据,以便在备份时不同的备份中的相同文件只保存一次。

FUSE 嘛,我好像从来没看到过完整一点的文档,就是官方 API 文档也经常语焉不详。dedupefs 是参考 rofs 写的。dedupefs 也是只读的。

挂载之前,先得把 dedupe 的纯文本格式处理一下。纯文本适合存储和人阅读,但是查询效率低下。我决定用更适合处理纯文本的 Python,把数据存储到 GNU dbm 键值对数据库中,然后 dedupefs 直接读取数据库就好了。(于是顺便学会了在 C 中使用 GNU dbm :-))数据的组织方式如下:

  • d + 文件路径:该目录下的文件名列表
  • f + 文件路径:该文件的信息

这样要读取一个目录下的文件列表就查 d 开头的项,要取得一个文件的信息(stat)或者打开文件,就读 f 开头的。

下边是编码和调试过程中的经验与收获:

  • GNU dbm 没说它是线程安全的,所以它不是线程安全的。但是 FUSE 又是多线程的(调试用的单线程模式我就不玩的),所以读取数据库时要加锁。
  • GNU dbm 查询结果数据是要调用者来 free 的。
  • 因为涉及到二进制数据交换(Python <-> C),所以要注意在结构体声明时围上#pragma pack(push, 1)#pragma pack(pop),以免对齐不一致造成数据错误。
  • valgrind 用来诊断内存访问错误效果非常棒!
  • FUSE 的struct fuse_file_info里有个fh域可以用来存文件描述符,这样就不用像 rofs 那样每次读取都要打开一遍文件了。
  • FUSE 读取用的回调函数传的offset一定要用,要首先lseek(finfo->fh, offset, SEEK_SET);一下,不然指不定读取到什么地方的数据了。
  • FUSE 文件系统可以忽略文件权限,所以自己不在openaccess里判断的话,就可以访问到明明看上去不能访问的文件(这正在我想要的)。
  • du 命令读取文件占用磁盘空间时使用了struct statst_blocks域。如果在 FUSE 程序里不管它的话,那么 du 将总是报告占用了 0 字节的空间……这里的块大小总是 512 字节。

第一次写 FUSE 程序,虽然文档差了一点,但用起来还是挺方便 =w=

哦对了,android-dedupefs 的仓库链接。

2
23
2014
11

让我们收养孤儿进程吧

稍微了解一点类 UNIX 系统的进程管理的都知道,当一个进程的父进程死亡之后,它就变成了孤儿进程,会由进程号 1 的 init 进程收养,并且在它死亡时由 init 来收尸。但是,自从使用 systemd 来管理用户级服务进程之后,我发现 systemd --user 管理的进程总是在它之下,即使进程已经 fork 了好几次。systemd 是怎么做到的呢?

对一个软件的实现有不懂的想了解当然是读它的源码了。这种东西可没有另外的文档,因为源码本身即文档。当然之前我也 Google 过,没有得到结果。在又一个全新的源码树里寻寻觅觅一两天之后,终于找到了这个:

        if (arg_running_as == SYSTEMD_USER) {
                /* Become reaper of our children */
                if (prctl(PR_SET_CHILD_SUBREAPER, 1) < 0) {
                        log_warning("Failed to make us a subreaper: %m");
                        if (errno == EINVAL)
                                log_info("Perhaps the kernel version is too old (< 3.4?)");
                }
        }

原来是通过prctl系统调用实现的。于是去翻 prctl 的 man 手册,得知PR_SET_CHILD_SUBREAPER是 Linux 3.4 加入的新特性。把它设置为非零值,当前进程就会变成 subreaper,会像 1 号进程那样收养孤儿进程了。

当然用 C 写不好玩,于是先用 python-cffi 玩了会儿,最后还是写了个 Python 模块,也是抓住机会练习一下 C 啦。有个 python-prctl 模块,但是它没有包含这个调用。

#include<sys/prctl.h>
#include<Python.h>

static PyObject* subreap(PyObject *self, PyObject *args){
  PyObject* pyreaping;
  int reaping;
  int result;

  if (!PyArg_ParseTuple(args, "O!", &PyBool_Type, &pyreaping))
    return NULL;
  reaping = pyreaping == Py_True;

  Py_BEGIN_ALLOW_THREADS
  result = prctl(PR_SET_CHILD_SUBREAPER, reaping);
  Py_END_ALLOW_THREADS

  if(result != 0){
    return PyErr_SetFromErrno(PyExc_OSError);
  }else{
    Py_RETURN_NONE;
  }
}

static PyMethodDef mysysutil_methods[] = {
  {"subreap", subreap, METH_VARARGS},
  {NULL, NULL}    /* Sentinel */
};

static PyModuleDef mysysutil = {
  PyModuleDef_HEAD_INIT,
  "mysysutil",
  "My system utils",
  -1,
  mysysutil_methods,
  NULL, NULL, NULL, NULL
};

PyMODINIT_FUNC PyInit_mysysutil(void){
  PyObject* m;

  m = PyModule_Create(&mysysutil);
  if(m == NULL)
    return NULL;
  return m;
}

编译之后,

>>> import mysysutil
>>> mysysutil.subreap(True)

然后开子进程,不管它 fork 多少次,都依然会在这个 Python 进程之下啦。

但是,这样子不太好玩呢。如果我登陆之后所有启动的子进程都在一个进程之下不是更有意思么?于是我打上了 Awesome 的主意,因为它支持运行任意的 Lua 代码嘛。于是我又给这个 prctl 调用弄了个 Lua 绑定。最终的版本如下:

#include<lua.h>
#include<lualib.h>
#include<lauxlib.h>

#include<sys/prctl.h>
#include<sys/wait.h>
#include<errno.h>
#include<string.h>
#include<signal.h>

static int l_setsubreap(lua_State * L){
  int reap;
  if(lua_isboolean(L, 1)){
    reap = lua_toboolean(L, 1);
  }else{
    return luaL_argerror(L, 1, "not a boolean");
  }
  if(prctl(PR_SET_CHILD_SUBREAPER, reap) != 0){
    return luaL_error(L, "prctl failed: %s", strerror(errno));
  }
  return 0;
}

static int l_ignore_SIGCHLD(lua_State * L){
  signal(SIGCHLD, SIG_IGN);
  return 0;
}

static int l_reap(lua_State * L){
  int pid, st;
  pid = waitpid(-1, &st, WNOHANG);
  lua_pushinteger(L, st);
  lua_pushinteger(L, pid);
  return 2;
}

static const struct luaL_Reg l_lib[] = {
  {"setsubreap", l_setsubreap},
  {"reap", l_reap},
  {"ignore_SIGCHLD", l_ignore_SIGCHLD},
  {NULL, NULL}
};

int luaopen_clua(lua_State * L){
  lua_newtable(L);
  luaL_setfuncs(L, l_lib, 0);
  return 1;
}

除了调用 prctl 外,还增加了显式忽略 SIGCHLD 信号,以及非阻塞地调用 waitpid 收割单个僵尸进程的函数,因为 Awesome 本身没处理子进程退出,我一不小心弄出了好几个僵尸进程……对了,那个 waitpid 要注意给弄成非阻塞的,不然一不小心就会出问题

用的时候就是这样子,可以写到rc.lua里,也可以在 awesome-client 里调用:

package.cpath = package.cpath .. ';/home/lilydjwg/scripts/lua/cmod/?.so'
clua = require('clua')
clua.setsubreap(true)
clua.ignore_SIGCHLD()

最终,我的进程树成了这样子:

htop-awesome-tree

可以看到,由 Awesome 启动的进程已经全部待在 Awesome 进程树之下了。systemd --user 是由 PAM 启动的,所以不在 Awesome 树下。但是,那些 dbus 的东西和 gconfd-2、at-spi 之类的是怎么回事呀……

2
21
2014
10

编程语言的特色

Tcl:一切皆字符串。
Python:一切皆对象,真的!整数、函数、类、方法、模块、代码、函数调用栈等等都是对象!
Lisp:好一碗美味的括号汤!
Lua:在与不在之间——nil。
Zsh 脚本:Perl 根本不算什么!
Haskell:=纯粹+庸懒+晦涩
Erlang:你不是一个人在战斗,不是一个人!
 

Category: 未分类 | Tags: 随思
2
19
2014
18

利用 Aufs 和 LXC 快速建立一个用于测试的系统副本

起因是,我偶尔看到 MediaWiki 导出时可以把图片也包含在 XML 文件中,但是不确定能不能顺利地导入回去。本来是准备拿虚拟机测试的,但是得在虚拟机里安装整套环境,麻烦呀。于是,结合前段时间折腾 Aufs 和 LXC 的经验,把当前正在运行的系统利用 Aufs 搞了一份只读挂载。当然还要弄个空目录来放可写分支:

mkdir -p root data
sudo mount -t aufs -o br:$PWD/data=rw:/=ro aufs $PWD/root

其实这个样子就已经可以 chroot 进去跑 httpd 了。不过,得先改一下监听的端口,因为 chroot 环境与主系统只有文件系统是隔离的,网络空间还是共享的。chroot 中 PID 空间也是共享的,所以在里边杀进程时不小心把 PID 写错的话,是可能会把外边的进程给杀掉的……(而 LXC 中,主系统是可以杀容器中的进程,但是反过来不行,因为主系统中的进程在容器中根本没分配 PID。)

于是就来玩玩 LXC 啦。要注意把 fstab 删掉,不然 systemd 会不高兴。日志文件不能共享,否则 journald 会不高兴。因为把 mknod 权限给禁掉了,所以在容器里 loop 设备是没法创建的。如果需要,在主系统里 losetup 之后像注释里那样写一条挂载信息就好。

sudo rm root/etc/fstab
sudo rm -r root/var/log/journal
sudo mkdir root/var/log/journal
sudo chgrp systemd-journal root/var/log/journal
sudo brctl addbr br0
sudo ifconfig br0 192.168.10.1

cat > lxc.conf <<EOF
lxc.utsname = arch2
lxc.autodev = 1
lxc.tty = 1
lxc.pts = 1024
lxc.rootfs = ${PWD}/root
lxc.mount.entry = sysfs sys sysfs ro,defaults 0 0
lxc.mount.entry = proc proc proc nodev,noexec,nosuid 0 0
lxc.mount.entry = /proc/sys ${PWD}/root/proc/sys none ro,bind 0 0
lxc.cap.drop = mknod sys_module mac_admin mac_override
# loop mount
# lxc.mount.entry = /dev/loop1 /home/lilydjwg/tmpfs/root/var/lib/pacman ext4 rw 0 0
#networking
lxc.network.type = veth
lxc.network.link = br0
lxc.network.flags = up
lxc.network.ipv4 = 192.168.10.3
lxc.network.name = eth0
#cgroups
lxc.cgroup.devices.deny = a
lxc.cgroup.devices.allow = c *:* m
lxc.cgroup.devices.allow = b *:* m
lxc.cgroup.devices.allow = c 1:3 rwm
lxc.cgroup.devices.allow = c 1:5 rwm
lxc.cgroup.devices.allow = c 1:7 rwm
lxc.cgroup.devices.allow = c 1:8 rwm
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 1:9 rwm
lxc.cgroup.devices.allow = c 4:1 rwm
lxc.cgroup.devices.allow = c 5:0 rwm
lxc.cgroup.devices.allow = c 5:1 rwm
lxc.cgroup.devices.allow = c 5:2 rwm
lxc.cgroup.devices.allow = c 136:* rwm
EOF
sudo lxc-start -n arch-dup -f lxc.conf

当然网络和 DNS 还要进去再设置一下:

route del -net 192.0.0.0/8
route add -net 192.168.0.0/16 eth0
route add -net default gw 192.168.10.1
echo 'nameserver 192.168.10.1' > /etc/resolve.conf

LXC 挺有点复杂的。systemd 的开发者也是这么认为的,所以他们搞了个操作便捷性类似于 chroot 但是功能类似于 LXC 的东东——systemd-nspawn!比如上边那个新系统可以这么启动:

sudo systemd-nspawn -b --private-network -D root

不过很遗憾的是,要么加--private-network让新启动的容器没有网络,要么不加,和 chroot 一样与主系统共享网络。毕竟是他们用来测试 systemd 的东东嘛。调试系统的第一个进程可不容易,但是当它在另一个系统中只是一个普通进程、可以连 gdb 和 strace 时情况就大不一样啦 =w=

PS: 在 systemd-nspawn 的 manpage 中(上边那个 freedesktop.org 的链接),Arch 和 Fedora 以及 Debian 并列作为示例了呢 =w=


2015年3月14日更新:使用 Linux 3.18 及以上版本的内核,也可以使用 overlayfs 取代 aufs 来挂载,挂载命令示例如下:

modprobe overlay
mount -t overlay -o lowerdir=/,upperdir=$PWD/.lxc-data,workdir=$PWD/.lxc-root overlayfs $PWD/.lxc-root

lowerdir是只读的目录(其中的数据不会被修改),upperdir是用于记录修改的可写目录,workdir是工作目录,其必要性我也不理解,需要和upperdir同一文件系统。我习惯上指定为挂载目标目录。

overlayfs 某些操作的效率似乎比 aufs 高不少。这里是我自己用来创建这个系统副本的 Shell 脚本。

Category: Linux | Tags: linux systemd lxc aufs
2
19
2014
3

zsh 异步生成提示符

为什么要异步?当然是因为慢了。比如 Arch 核心仓库 git 版挺大的,第一次进去时显示个 git 分支名要等好一会儿。今天在 zsh-users 列表中看到 Bart Schaefer 给出了一个使用 coprocess 的解决方案,眼前一亮,立即照葫芦画瓢给自己的 zsh 用上了。以下是整个提示符设置部分的代码:

if [[ -n $commands[git] ]]; then
  _nogit_dir=()
  for p in $nogit_dir; do
    [[ -d $p ]] && _nogit_dir+=$(realpath $p)
  done
  unset p

  typeset -g _current_branch= vcs_info_fd=
  zmodload zsh/zselect 2>/dev/null

  _vcs_update_info () {
    eval $(read -rE -u$1)
    zle -F $1
    exec {1}>&-
    zle reset-prompt
  }

  _set_current_branch () {
    cwd=$(pwd -P)
    for p in $_nogit_dir; do
      if [[ $cwd == $p* ]]; then
        return
      fi
    done

    setopt localoptions no_monitor
    coproc {
      _br=$(git branch --no-color 2>/dev/null)
      if [[ $? -eq 0 ]]; then
        _current_branch=$(echo $_br|awk '{if($1 == "*"){print "%{\x1b[33m%} (" substr($0, 3) ")"}}')
      fi
      # always gives something for reading, or _vcs_update_info won't be
      # called, fd not closed
      typeset -p _current_branch
    }
    disown %{\ _br
    exec {vcs_info_fd}<&p
    # wait 0.1 seconds before showing up to avoid unnecessary double update
    # precmd functions are called *after* prompt is expanded, and we can't call
    # zle reset-prompt outside zle, so turn to zselect
    zselect -r -t 10 $vcs_info_fd 2>/dev/null
    zle -F $vcs_info_fd _vcs_update_info
  }

  typeset -gaU precmd_functions
  precmd_functions+=_set_current_branch
  setopt PROMPT_SUBST
fi

[[ -n $ZSH_PS_HOST && $ZSH_PS_HOST != \(*\)\  ]] && ZSH_PS_HOST="($ZSH_PS_HOST) "

E=$'\x1b'
PS1="%{${E}[2m%}%h $ZSH_PS_HOST%(?..%{${E}[1;31m%}%?%{${E}[0m%} )%{${E}[32m%}%~\$_current_branch
%(!.%{${E}[0;31m%}###.%{${E}[1;34m%}>>>)%{${E}[0m%} "

比较坑的是使用chpwd_functions的话只能在目录改变时显示一次,再随便执行个什么命令分支提示就没了。又想到目录不改变的时候分支也可以变化(切换分支了嘛),所以使用precmd_functions,每次显示提示符前(单纯的重绘除外)都执行一次。另外,为了避免每次显示提示符时都明显地分为两步干扰视线,所以在那个_set_current_branch函数里等了 0.1 秒,超时才会不管分支名显示先继续了。

2014年2月24日更新:注意,直到 zsh 5.0.5(就是当前最新版本)有个 bug,在显示提示符之后、用户输入之前,上述代码会经常出现「忙等待」的情况浪费 CPU。这里有个补丁可以修复。

Category: shell | Tags: linux Git zsh

部分静态文件存储由又拍云存储提供。 | Theme: Aeros 2.0 by TheBuckmaker.com