9
20
2018
8

永远不要 tail -f 管道

运维同事为了收集日志,配置程序将日志写入一个命名管道。然后他在外边拿 tail -f 去读,结果发生了灵异事件。通过 strace 可以看到,tail 进程读取了日志,但是却并没有再输出来。但是如果不启动输出日志的程序,而是在实例启动之后再进去往管道里写数据,却又是可以立即得到 tail 的输出的。

很奇怪的行为,一群人在那里研究半天,猜测是不是环境变量造成的啊,是不是放后台组执行造成的啊,是不是 XYZ 啊。——典型的「霰弹枪式」除错法

我当时也被带沟里了。于是跑去尝试复现,接着去读 tail 的源码。花了好久才明白这是一个很简单的问题:tail -f 的语义首先是 tail 这个词本身——先读文件最末尾的数据(默认是十行),然后再是 -f 选项的语义,即在文件更新时接着读取数据并输出。所以,当程序往里边写日志时,管道写端一直没关闭,tail 就一直读不到文件结束,也就无法确定最后十行是什么。当他们测试的时候,因为使用的是 echo shell 命令,打开文件、写入数据、关闭。这样 tail 一下子就读取到了文件末尾,然后把数据输出来了。接下来就是边读边输出了。

其实这种使用方法本身就很奇怪了,以至于这个执行流是兼容许多系统的 tail 的各种分支里,最最不常规、无可奈何的那一个分支。你都用管道了,cat 一下嘛。如果怕遇到管道被 reopen 的情况,就在 while true 里 cat 就好。

这个事件中,我也是见识了很多人解决问题的奇怪思路:「我猜猜猜。猜对了哦耶,猜错了,哎呀编程好难啊,Linux 系统好难啊……」猜你妹啊!你长的是大脑又不是骰子,用逻辑一步一步地取得结果不好吗!

有一个小游戏——猜数字。比如甲确定一个 1 到 1000 之内的整数,然后乙来猜。每当乙给出一个猜测时,甲回应猜对了,还是过大或者过小。如果乙知道什么叫二进制的话,乙可以保证在十次之内猜中的。

计算机系统和编程世界里,最棒的一点是确定性和逻辑性。虽然经常也不是像上例那样完全确定的,但至少比起人类社会要容易确定得多。特别是在有源码的时候。所以解决问题的路线也很简单,顺着问题的症状一路回溯,确认然后排除那些没有问题的部分,逐步缩小问题所在的范围,直接你看见它。就跟上边的猜数字游戏或者地毯式搜索一样。每一次猜测都是带着排除一部分没有问题的地方而去,而不是明明有证据表明某个地方不可能有问题,你还偏偏怀疑问题在那里,做无用功。

就像调查一个凶案,这些人放着有作案嫌疑的人不管,非要费劲地去调查那些有相当好的不在场证明的人。

Arch Linux 中文社区这边也有很多这种人。出了问题描述不清楚症状。新手嘛,没经验也没学习过如何描述事实,讲不清楚也没什么,引导对方获取截屏、日志,逐步排查问题就好了嘛。可就是有些热心人,喜欢提出自己的猜测。重点是:都不尝试证实猜测是否属实,就急着上解决方案。结果就是,我询问细节事实的消息没人理,求助者试试这个,试试那个,最终问题能否解决,就跟买彩票能否中奖一样,全凭运气。

Category: Linux | Tags: Arch Linux fifo linux 社群
7
11
2018
14

Linux 下获取文件的创建时间

其实 Linux 是支持文件的创建时间的呢。不过不是所有文件系统都支持,比如 ext4、xfs、btrfs 都支持,zfs、vfat、ntfs 不支持。

但是呢,用户基本上是看不到的。文件系统有记录,但是没有 API 可以获取到这个数据。所以你用 stat 命令的话,会看到「创建时间」一行总是「-」。用 debugfs 搞 ext4 是可以的,但是那个需要 root 权限,并且一不小心会搞坏文件系统。

最近,我阅读内核源码时,忽然发现内核已经通过 4.11 版本引入的 statx 系统调用支持获取创建时间了。字段名里用的是 btime(birth time),没有用 crtime(creation time),也没有用大写的 Btime 呢。

但是 glibc 并没有支持,所以要用 syscall 函数来调用。也不是很复杂。不过我正着手用 Rust 实现的时候,却在内核源码树里找到了 samples/statx/test-statx.c 这么个文件。原来有现成的啊!

gcc 编译一下,还真好用:

>>> statx /
statx(/) = 0
results=fff
  Size: 224             Blocks: 0          IO Block: 4096    directory
Device: fe:01           Inode: 96          Links: 17
Access: (0755/drwxr-xr-x)  Uid:     0   Gid:     0
Access: 2018-07-11 13:33:08.659477830+0800
Modify: 2018-03-30 15:06:02.645864827+0800
Change: 2018-03-30 15:06:02.645864827+0800
 Birth: 2017-06-19 21:07:53.653467000+0800

2019年09月03日更新:现在(coreutils 8.31)stat 命令已经支持创建时间了。

Category: Linux | Tags: linux
2
14
2018
5

使用 VirtualBox 启动本地磁盘上的其它系统

VBox 可以从一个指向本地硬盘的 vmdk 文件启动虚拟机。

首先,为了避免使用 root 运行 VBox,我们需要给自己访问磁盘的权限。我即将启动的是位于 sda5 上的 openSUSE。它使用 UEFI 启动,所以 UEFI 分区的权限也是需要的。创建 vmdk 文件的时候需要读取分区表,因此,还需要 sda 的权限:

sudo setfacl -m u:${USER}:rw /dev/sda{,1,5}

然后我们创建 vmdk 文件。使用-partitions 1,5选项的话,只有这两个分区能在虚拟机里访问,别的分区读的时候是全零,写入操作会被忽略。-relative选择使用分区设备名(sda1、sda5),这样创建好之后 VBox 不再需要对整块硬盘 sda 的权限了。另外会附带创建一个名字以 -pt.vmdk 结尾的文件。它是单独的分区表。如果是 MBR 启动的话,是可以直接在虚拟机系统里更新引导器的,不影响外边的系统。不过我这次是使用 UEFI 启动,所以用不上了。

VBoxManage internalcommands createrawvmdk -filename hostdisk.vmdk -rawdisk /dev/sda -partitions 1,5 -relative

创建好之后就可以撤销对 sda 的权限了:

sudo setfacl -b /dev/sda

然后去 VirtualBox 界面那边创建新虚拟机,并「启用 EFI」。另外,可以在存储设置里,把「控制器: SATA」的「使用主机输入输出 (I/O) 缓存」启用,似乎这样 I/O 会快一点。

VBox 的 EFI 并不像电脑的那样,按 F12 可以选择启动项。因此,它会启动默认的那个,也就是 /EFI/Boot/bootx64.efi。如果你想启动的系统不是这个的话,就把它的 efi 文件复制过来覆盖它。比如我是这么做的:

cd /boot/EFI/Boot
sudo cp ../opensuse/grubx64.efi bootx64.efi

如果是 Windows 10 并使用 MBR 启动的话,可以在虚拟机里用如下命令更新 MBR,干掉原来用于多启动的 grub:

bootsect /nt60 c: /mbr

做好之后就可以启动啦~

对于设备的权限设置,重启之后会丢失的。需要的时候再加上好了。

PS: openSUSE 自带了 VBox 的驱动啊,不过剪贴板共享不能用,大概只有显示驱动没带上服务。

PPS: 启动没一会儿就通知我更新出现错误,一看软件源设置,果然是 HTTP 的,被垃圾鹏博士劫持了。

Category: Linux | Tags: linux 虚拟机 vbox
2
10
2018
3

加固 systemd 服务

最近学 wzyboy 搭了一套 collectd + Graphite + Grafana 监控。collectd 和 Grafana 都比较好搞,Arch 官方源里有。但是 Graphite 就没有了。

我没有使用 Python 2 版、带 Web 前端的 Graphite 包,而是使用 graphite-api 提供 Web API,python2-carbon 存储数据。它们在 AUR 上有,其中 python2-carbon 是相当危险的(现在已经改了)。

为什么危险呢?

首先,最明显的,carbon 服务以 root 用户运行。它本身没有任何使用 root 权限的必要,所以专门创建一个 carbon 用户更好。

其次,它运行起来之后,我发现是监听 0.0.0.0 的。这个也无必要:我的 collectd 就在本地呢。

最后,也是最吓人的:它默认开启了接收 pickle 数据的端口。Python pickle 模块的文档一打开,就能看到红色的警告,告诉人们不能接收不信任来源的 pickle 数据。而我曾经工作过的公司也发生过通过 pickle 注入代码的事情:攻击者发现了一个对外网开放的 Redis 服务,刚好那个 Redis 是给 Celery 用的。攻击者于是往里边写了条自己构造的 pickle,在解析时调用 curl 命令向其服务器报告IP、端口和当前UNIX用户的信息。

这接口,开在外网,就是远程代码招行;开在本地,就是本地提权。很危险的。

为了防止各种漏洞被利用,一个未雨绸缪的办法就是:权限最小化。本来这是件比较麻烦的事情,好在 systemd 提供了许多现成的配置项,使得给 carbon 这种服务加固简单易行。

首先创建用户,写一个 sysusers 文件就可以了:

u carbon - "carbon service" /var/lib/carbon

然后,它需要使用文件系统的某些部分。那么别的就用不着访问了,比如 /home。而 /dev、/tmp 这些,自己用自己的就好。连 / 也不让写,也不允许获取任何新特权了。其实使用 carbon 用户它本来就写不了 / 也没有任何特权了,但以防万一嘛,要是哪里来个本地提权漏洞呢?

[Unit]
Description=Graphite/Carbon
After=network.target

[Service]
Type=forking
ExecStart=/usr/bin/carbon-cache.py --config=/etc/carbon/carbon.conf start
User=carbon

PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
CapabilityBoundingSet=

ReadOnlyPaths=/
ReadWritePaths=/run
ReadWritePaths=/var/log/carbon
ReadWritePaths=/var/lib/carbon

[Install]
WantedBy=multi-user.target

限制文件系统的访问,systemd 配置起来很方便,我打包的时候喜欢尽量加上。

完整的 python2-carbon 服务配置和打包脚本在这里

以及,这里是 Arch Linux 中文社区的编译机的 Grafana

Category: Linux | Tags: Arch Linux systemd linux 安全
1
18
2018
9

嗨 Win10,这是我的浏览器

因为腾讯的关系,不得不使用 Windows。不是QQ也不是RTX,这次 Wine 帮不上忙了。那好吧,SSD + 16G 内存,跑个 Windows 虚拟机也没多大问题。

但是问题来了:我在虚拟机里每次点击链接,会调用安装于 Windows 上的浏览器来访问。可是我登录了各种工作账号的浏览器在外面的自由世界 Linux 主系统里啊。

办法也好想:来一个远程调用,让 Windows 调用咱自己的程序打开链接,然后咱自己的程序再把链接传给外面的 Linux 就好了。理想是美满的,可是 Windows 里设置默认浏览器并不是写个 .desktop 文件那么简单!

在 Google 上找了半天资料(真的花掉了半天!),最终终于确认,需要设置以下一大堆注册表键,Windows 才会认可咱自己的浏览器:

Windows Registry Editor Version 5.00

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser]
@="RemoteBrowser"

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\Capabilities]
"ApplicationName"="RemoteBrowser"
"ApplicationIcon"="C:\\RemoteBrowser\\launch.exe,0"
"ApplicationDescription"="RemoteBrowser"

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\Capabilities\StartMenu]
"StartMenuInternet"="RemoteBrowser"

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\Capabilities\URLAssociations]
"https"="RemoteBrowserHTML"
"http"="RemoteBrowserHTML"
"ftp"="RemoteBrowserHTML"

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\DefaultIcon]
@="C:\\RemoteBrowser\\launch.exe,0"

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\InstallInfo]
"IconsVisible"=dword:00000001
"ShowIconsCommand"="\"C:\\RemoteBrowser\\launch.exe\" --show-icons"
"HideIconsCommand"="\"C:\\RemoteBrowser\\launch.exe\" --hide-icons"
"ReinstallCommand"="\"C:\\RemoteBrowser\\launch.exe\" --make-default-browser"

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\shell]

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\shell\open]

[HKEY_LOCAL_MACHINE\SOFTWARE\Clients\StartMenuInternet\RemoteBrowser\shell\open\command]
@="\"C:\\RemoteBrowser\\launch.exe\" \"%1\""

[HKEY_LOCAL_MACHINE\SOFTWARE\RegisteredApplications]
"RemoteBrowser"="SOFTWARE\\Clients\\StartMenuInternet\\RemoteBrowser\\Capabilities"

[HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations]

[HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http]

[HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice]
"Progid"="RemoteBrowserHTML"

[HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https]

[HKEY_CURRENT_USER\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\https\UserChoice]
"Progid"="RemoteBrowserHTML"

[HKEY_CLASSES_ROOT\RemoteBrowserHTML]
@="RemoteBrowser"
"FriendlyTypeName"="RemoteBrowser"
"URL Protocol"=""
"EditFlags"=dword:00000002

[HKEY_CLASSES_ROOT\RemoteBrowserHTML\DefaultIcon]
@="C:\\RemoteBrowser\\launch.exe,0"

[HKEY_CLASSES_ROOT\RemoteBrowserHTML\shell]
@="open"

[HKEY_CLASSES_ROOT\RemoteBrowserHTML\shell\open]

[HKEY_CLASSES_ROOT\RemoteBrowserHTML\shell\open\command]
@="\"C:\\RemoteBrowser\\launch.exe\" \"%1\""

相比之下 Linux 只需要以下这么几行:

Version=1.0
Name=Firefox
GenericName=Web Browser
Comment=Browse the Web
Exec=/usr/lib/firefox/firefox %u
Icon=firefox
Terminal=false
Type=Application
MimeType=text/html;text/xml;application/xhtml+xml;application/vnd.mozilla.xul+xml;text/mml;x-scheme-handler/http;x-scheme-handler/https;
StartupNotify=true
StartupWMClass=Firefox
Categories=Network;WebBrowser;
Keywords=web;browser;internet;

(不过真实的 firefox.desktop 有三百多行,因为各种语言的名字和描述的翻译。)

上边那个注册表文件,引用了一个 exe 文件,和里边的一个图标资源。我不知道图标使用独立的文件可不可以。加个图标并不难,上网搜一下就有了,使用 windres 命令即可。

首先准备好图标文件 icon.ico,然后写一个资源文件:

1 ICON DISCARDABLE "icon.ico"

用 windres 把它编译成 COFF 文件。因为我是在 Linux 上使用 mingw 操作,所以使用的命令叫 x86_64-w64-mingw32-windres。然后把它和其它目标文件链接到一起就可以了。因为我使用的是 Rust,它的 build.rs 脚本不支持目标文件,所以先打包成静态库,然后再链接:

x86_64-w64-mingw32-windres launch.rc -O coff -o icon.res
x86_64-w64-mingw32-ar q libres.a icon.res

然后 build.rs 脚本里说一下:

fn main() {
  println!("cargo:rustc-link-search=native=..");
  println!("cargo:rustc-link-lib=static=res");
}

然后咱的主程序,通过 TCP 把链接发到 Linux,以及 Linux 端的服务,接收链接并打开浏览器,因为很简单很常规,所以这里就不列出来了。有兴趣的去源码仓库看就好了。

exe 编译好之后,扔到之前注册表文件里提及的地方就好。然后双击那个注册表文件将其导入。接下来在默认软件的设置里就能够找到我们的「Remote Browser」了(虽然不知道为什么没有显示指定的名字,而是 exe 文件名)。

另外,那个图标资源,也可以使用 Resource Hacker 把图标文件给弄到 exe 文件里边去。

最后一步,写个用户级的 systemd 服务,把负责在浏览器里打开链接的程序给跑起来~


仓库地址在此。喜欢的话,记得 star 哦~

Category: Linux | Tags: windows 浏览器
12
10
2017
11

在 Linux 下设置录音笔的时间

咱买了一个录音笔,效果比使用笔记本话筒录音好多了还省电。当然啦,我也曾试过使用手机录音,结果是,没能录多久就中断了(Android 就是这么不靠谱)。

我的录音需要记录较为准确的时间信息。录音笔怎么知道现在是什么时间呢?还好它没有跟风,用不着联网!

它带了一个小程序,叫「录音笔专用时间同步工具」(英文叫「SetUDiskTime」,可以搜到的)。是一个 EXE 文件,以及一个 DLL 文件。功能很棒,没有广告,没有推荐,也不需要注册什么乱七八糟的账户,甚至都不需要打开浏览器访问人家官网。就弹一个框,显示当前时间,确定一下就设置好时间了。这年头,这么单纯的 Windows 软件还真是难得呢。

然而,它不支持我用的 Linux 啊。虽然我努力地保证这录音笔一直有电,但是时间还是丢失了几次,它的FAT文件系统也脏了几次。每次我都得开 WinXP 虚拟机来设置时间,好麻烦。

Wine 是不行的,硬件相关的东西基本上没戏。拿 Procmon 跟踪了一下,也没什么复杂的操作,主要部分就几个 DeviceIoControl 调用,但是看不到调用参数。试了试 IDA,基本看不懂……不过倒是能知道,它通过 IOCTL_SCSI_PASSTHROUGH 直接给设备发送了 SCSI 命令。

既然跟踪不到,试试抓 USB 的包好了。本来想用 Wireshark 的,但是 WinXP 版的 Wireshark 看来不支持。又尝试了设备分配给 VBox 然后在 Linux 上抓包,结果 permission denied……我是 root 啊都被 deny 了……

那么,还是在 Windows 上抓包吧。有一个软件叫 USBPcap,下载安装最新版,结果遇到 bug。那试试旧版本吧。官网没给出旧版本的下载地址,不过看到下载链接带上了版本号,这就好办了。去 commit log 里找到旧的版本号替换进去,https://dl.bintray.com/desowin/USBPcap/USBPcapSetup-1.0.0.7.exe,就好了~

抓好包,取到 Linux 下扔给 Wireshark 解读。挺小的呢,不到50个包,大部分还都是重复的。很快就定位到关键位置了:

关键 SCSI 命令

一个 0xcc 命令发过去,设备回复「ACTIONSUSBD」,大概是让设备做好准备。然后一个 0xb0 命令,带上7字节数据发过去,时间就设置好了。简单明了,不像那些小米空气净化器之类的所谓「物联网」,通讯加密起来不让人好好使用。

那么,这7字节是怎么传递时间数据的呢?我首先检查了UNIX时间戳,对不上。后来发送这个字串看上去挺像YYYYMMDDHHMMSS格式的,只是明显不是当时的时间。啊,它是十六进制的嘛!心算了几个,符合!再拿出我的 Python 牌计算器,确定年份是小端序的16位整数。

好了,协议细节都弄清楚了,接下来是实现。我原以为我得写个 C 程序,调几个 ioctl 的,后来网友说有个 sg3_utils 包。甚好,直接拿来用 Python 调,省得研究那几个 ioctl 要怎么写。

#!/usr/bin/env python3

import os
import sys
import struct
import subprocess
import datetime

def set_time(dev):
  cmd = ['sg_raw', '-s', '7', dev, 'b0', '00', '00', '00', '00', '00',
         '00', '07', '00', '00', '00', '00']
  p = subprocess.Popen(cmd, stdin=subprocess.PIPE, stderr=subprocess.PIPE)
  dt = datetime.datetime.now()
  data = struct.pack('<HBBBBB', dt.year, dt.month, dt.day,
                     dt.hour, dt.minute, dt.second)
  _, stderr = p.communicate(data)
  ret = p.wait()
  if ret != 0:
    raise subprocess.CalledProcessError(ret, cmd, stderr=stderr)

def actionsusbd(dev):
  cmd = ['sg_raw', '-r', '11', dev, 'cc', '00', '00', '00', '00', '00',
         '00', '0b', '00', '00', '00', '00']
  subprocess.run(cmd, check=True, stderr=subprocess.PIPE)

def main():
  if len(sys.argv) != 2:
    sys.exit('usage: setudisktime DEV')

  dev = sys.argv[1]
  if not os.access(dev, os.R_OK | os.W_OK):
    sys.exit(f'insufficient permission for {dev}')

  actionsusbd(dev)
  set_time(dev)

if __name__ == '__main__':
  main()
Category: Linux | Tags: linux 硬件 usb scsi
9
28
2017
9

To hup or not to hup

故事起源于同事想在后台跑一个服务:

$ nohup node xxx.js &

一切如愿。

——是吗?

实际情况是,这时退出 bash 是如愿了,但是直接关掉终端窗口的话,那个服务会死掉。

bash 奇怪行为之五

(我好像没有写前四个耶。等有时间了简单写一下吧。)

man bash 然后搜索 SIGHUP,你会发现,其实默认设置,bash 正常退出时,根本不会杀害后台进程。它们会和从脚本里运行时一样欢快地继续跑下去。只有 bash 因为收到 SIGHUP 而退出时,它才会给后台进程发 SIGHUP。

所以,直接 Ctrl-D 或者 exit 退出的话,(处理好了重定向的话,)要不要 nohup 都一样,进程不会死。

zsh 默认退出时会给后台任务发送 SIGHUP(除非你 disown 了)。

但这还是不能解释关窗口的时候,服务为什么会死掉呀?nohup 不是已经忽略掉 SIGHUP 了么?

与众不同的 nodejs

通常情况下,nohup 工作得很好。但是,UNIX 世界里来了位不了解、也不愿意遵循 UNIX 传统惯例的年轻气盛的小伙子。

我还记得 npm 直接往 /usr 下安装东西。

我还记得 npm 把 http_proxy 当 https_proxy 而我的缓存代理不支持 HTTPS,造成无法安装任何东西。

现在,nodejs 将所有信号的处理重置为默认行为,除了它自己想处理的那几个。

「nohup?那是什么鬼?我搞不懂!」nodejs 说,然后它被 SIGHUP 杀死了。

结语

The devil is in the detail!

Category: Linux | Tags: nodejs linux bash shell
9
21
2017
16

使用 Prince 转换 HTML 文档给 Kindle 阅读

ZeroMQ 的指南文档很长很长。我想放在 Kindle 里,上下班的时候看,长知识又不伤眼。

首先尝试 Push to Kindle。就是本博客每篇文章下边都有的那个链接里的东西。试了好几次终于成功了。然而,章节标题看不出来跑哪儿去了也就算了,代码去哪儿了?注意格式啊!

于是换浏览器,HTML 转 PDF。顺手按 F12,把每个标题右边的导航链接删掉了。然后打印~代码格式没有坏哦~然而,还是有很多代码没显示出来,倒是显示了一堆其它语言代码的链接……继续 F12 改样式表修了。这些都是小问题,最大的问题是,在我不断地调整页面大小的时候,我的火狐每次「准备……」的过程都特别漫长,那个负责转换的子进程吃很多很多 CPU,还卡死了所有它负责的标签页……终于,在等待近半小时它还没准备好的时候,我失去了耐心,杀掉了那个火狐子进程,换 Prince 了。这次我体会到多进程架构的好处了:页面卡了,换个标签页打开,分配到另外的子进程的时候就可以正常使用了。

Prince 是个非常不错的 HTML 转 PDF 软件。免费版本会有个它自己的图标放第一页右上角,没啥问题,打印的时候也不会出现。要是你非要去掉它的话,也可以找个 PDF 编辑工具删掉它。

然后是确定页面大小。因为代码的行都比较长,我决定横屏阅读,也就是「landscape」模式。然后拿尺子量了一下,差不多是 9cm×12cm。维基百科告诉我 Kindle Paperwhite 是6英寸的屏幕,但是我没有弄明白它的长和宽到底是多少,所以还是动手测量了。因为 Kindle 上字显示小一些也挺清晰的,所以我把短边乘以了二(好像并不合理啊,因为已经是 landscape 了,应该两边同步放大才对;不过其实我一开始想的是一页占两屏……)。

然后再加上针对 ZeroMQ 文档的修改,得样式表如下:

td + td {
  display: none;
}

.collapsible-block-folded {
  display: none;
}

.collapsible-block-unfolded {
  display: block !important;
}

.collapsible-block + br + span {
  display: none;
}

body {
  font-family: serif !important;
}

@page {
  size: 18cm 12cm landscape;
  margin: 0;
}

然后让我们的王子干活啦:

prince -s zguide.css zguide.html -o zguide.pdf

因为需要反复尝试,所以我已经把 ZeroMQ 那个巨大的 HTML 下载到本地了。

最终成果在这里。因为页边距为零,所以在一般的阅读器里看起来挺难看的,但是在 Kindle 里就挺适合了~

Category: Linux | Tags: kindle prince
9
11
2017
3

等连上互联网之后再来找我吧

最近公司弄了 Wi-Fi 登录。就是那个叫 captive portal 的东西。

Android 早就会在连接 Wi-Fi 时检测网络是不是要登录了,为此 Google 弄了个 /generate_204 的 URL。小米、高通、USTC、v2ex 也都提供了这个东西,方便广大中国大陆 Android 用户使用。(我发现我的 Android 使用的是高通的地址,没有用 Google 的。)

但我使用的 Arch Linux 自行开发的 netctl 网络管理工具没这种功能。火狐倒是不知道什么时候加上了,不过使用的地址 http://detectportal.firefox.com/success.txt 是返回 200 的。

所以我启动火狐就可以看到要登录的提示了。然而问题是,其它程序不知道要登录啊。像 offlineimap、openvpn、rescuetime 这种还好,会自己重试。可每次网络需要登录的时候 dcron 就会给我发一堆邮件告诉我我的 git pull 都失败了……当然还有我老早就注意到的 pkgstats,经常会因为启动过早而无法发送统计数据。

所以呢,得想个办法,等连上互联网之后再跑那些脚本啊服务什么的。

检测是不是连好了很简单,不断尝试就可以了。但我需要一个系统级的 Condition 对象来通知等待方可以继续了。然而我只知道 Linux 有提供信号量。难道要自己弄共享内存来用么?

#archlinux-cn 问了一下,farseerfc 说试试命名管道。我想了想,还真可以。只有读端的时候进程就会阻塞,一旦有写端就能成功打开了。当然没有读端的打开写端会打不开,不过没关系,反正这进程也不能退出,得一直拿着这个文件描述符。

没想到很少用到的命名管道有意想不到的用法呢。我以前还为了不阻塞而专门写了篇文章呢。

于是负责检测网络连通的 check-online 和等待网络连好的 wait-online 都写好了。

check-online 应当是个服务。那就交给 systemd 吧。然后……systemd 不是有个 network-online.target 么?正好可以让 check-online 来达成这个目标呢,多合适呀。

于是服务写好了。测试了几天,大成功!不仅 wait-online 很好地工作了,而且我发现 openvpn 和 pkgstats 自动排到 network-online.target 后边去了。nginx 的 OSCP staple 经常因为 DNS 失败而无法成功,我也可以在联好网之后去 reload 一下它了。(不是强依赖,我可不希望连不上网的时候我本地的 wiki 也访问不了。)

整个项目就叫作 wait-online,在 GitHub 上,欢迎送小星星哦~Arch Linux 包可以从 [archlinuxcn] 仓库 安装 wait-online-git 包。

9
9
2017
5

改了一下 GTK 3 的默认主题

最近开始用 Firefox nightly 了。它使用 GTK 3,于是对 GTK 3 主题的不满也逐渐表现出来了。

首先是选中的文本,以及菜单项。默认的那个蓝色太深了,我还是更喜欢 GTK 2 mist 主题那个浅蓝色。

然后,我是使用亮色主题的,黑黑的 tooltip 提示框太违和。而且边距那么大,多浪费空间啊。改成 GTK 2 时代那种简单的样子好了。

最后,那个又细又黑的滚动条好讨厌。改掉改掉!

最终效果图(好不容易才把这么多东西截到一张图里喵):

GTK 3 Adwaite tweaked

~/.config/gtk-3.0/gtk.css 代码:

/* For Adwaita {{{1
 */
/* scrollbars {{{2
 */
scrollbar {
  border-radius: 0;
  background-color: #eaeaea;
}
scrollbar slider {
  background-color: #bfbfbf;
  border-color: transparent;
  border-width: 4px;
  min-height: 10px;
  min-width: 10px;
  border-radius: 0;
}
scrollbar slider:hover, scrollbar slider:active {
  background-color: #bfbfbf;
}
/* tooltip {{{2
 */
tooltip {
  border: 1px solid #808080;
  background-color: rgba(254,254,228,0.9);
  border-radius: 0;
  padding: 0;
}

tooltip * {
  color: #000;
  padding: 0;
}
/* others {{{2
 */
*:selected, menuitem:hover {
  background-color: #d6e9f8;
  color: currentColor;
}

/* Vim modeline {{{1
 * vim:se fdm=marker:
 */
Category: Linux | Tags: css gtk3

Mastodon | Theme: Aeros 2.0 by TheBuckmaker.com