7
22
2019
6

fcitx 扩展:使用键盘粘贴选区(以及X选区原理科普)

之前的文章中介绍过,X Window 中有个很方便的名叫 PRIMARY 的「剪贴板」,选中即复制,中键即粘贴。

然而问题来了:我在输入的过程中需要之前选中的内容怎么办?又或者我的内容是通过 xsel、Vim 等程序以键盘的方式选中的,我还要继续打字,要怎么粘贴才比较流畅呢?

我之前的解决方案是 fcitx 自带的 fcitx-clipboard 扩展。启用之后可以按 Ctrl-; 来显示几条最近复制的内容。如果启用了的话,第一条、第三至最后,都来自常见的 CLIPBOARD 选区,而第二条(如果用了够久,第一条及本条有的话)是 PRIMARY 选区的内容。于是我只需要按 Ctrl-; 2 就可以粘贴了。

这么用了很久之后,我读到了一篇讲 X Window 选区是如何工作的文章。然后突然意识到一个问题:fcitx-clipboard 是什么时候请求选区内容的呢?

这里先科普一下好了。X Window 的每一个选区,是由某个窗口作为所有者持有的,并且在被请求时输出内容。「复制」的时候,内容并不会被存起来放在某处,而是窗口跟 X 服务器说,「我现在是这个选区的所有者了!」至于是哪个,取决于应用程序(及用户的操作)。通常用户明确的「复制」操作(Ctrl-C 快捷键、菜单项等)会使用 CLIPBOARD 选区,而只是选中内容的话,会使用 PRIMARY 选区。我还没有见到有程序使用别的选区的。

如果使用的是 PRIMARY 选区,这个时候,旧的所有者(如果存在的话)会失去选区。在 Vim / GVim 里,可视区域会由「Visual」高亮组变为「VIsualNOS」高亮组(在我的主题里就是变灰了),而多数终端,选中的区域会失去高亮。下一次,有程序想要「粘贴」PRIMARY 选区,就会跟 X 服务器讲,请把 PRIMARY 选区的内容,以某个指定的格式写到指定窗口的指定属性上。然后 X 服务器把请求传给选区所有者,选区所有者就去按要求写属性。当然选区所有者也可能会拒绝请求,比如它拥有的是文本而请求方想要图片。当然后来大家要粘贴的内容比较大了,于是就有了 INCR 机制来一点一点地传数据。

这样的流程,也可以解释,为什么我往 Telegram 里粘贴图片,GIMP 却莫名其妙地挂掉了……

所以啦,在一个程序里复制之后,退出那个程序,你就粘贴不出来东西啦。可这样岂不是很不方便?是啊,所以又有了剪贴板管理器。它们的工作原理我还没有研究,猜想是支持 SAVE_TARGETS 的话,就等着对方退出之前把内容传过来,不支持的话一复制就传过来。

fcitx-clipboard 是这么干的:选区一有变化,它就获取其纯文本格式的内容,符合一定条件的就存起来。所以,当我在 Vim 里用键盘不断进入可视模式选东西进行各种操作时,因为我设置了 clipboard=autoselect 选项,Vim 会不断地通告「我拥有 PRIMARY 选区啦!」「我这边的 PRIMARY 选区又更新啦!」结果就是,fcitx-clipboard 会不断地把我在 Vim 中选中的内容给拿过去。

就那么点数据,本地传来传去当然没啥问题。但是,当我通过 ssh 使用的时候,我发现我在 Vim 里每一次扩大可视区域都如此地艰难。不得已只好关了 autoselect 选项。当时我还以为就选点文本,怎么就这么慢呢,谁曾想到,每一次更新可视区域,fcitx-clipboard 都会把我选中的文本请求一份……

那么好吧,不用 fcitx-clipboard 了。于是问题又回到了原点:怎么通过键盘粘贴 PRIMARY 选区呢?用程序把鼠标移过去点中键是不行的,因为程序不会知道当前光标在哪里。通过 xdotool type 也行,但是这样一个个字地输入,而且还不仅仅是 ASCII,鬼晓得有多少程序跟 Minecraft 一样会处理不过来而丢字?而且,怎么判断当前是否是输入文本的状态也是个问题。所以我还是走输入法这条路了。

其实这事完成并不难,从肥猫的傲娇扩展开始改,照猫画虎地注册热键,然后请求选区,提交文本。可我遇到一个很奇怪的 bug:扩展加载了,但是热键不生效。为了调试这问题,我通过手机 termux ssh 连过来,tmux attach 上,然后用蓝牙键盘对着屏幕里那只由于 fcitx 被 gdb 停下来了因此从电脑收不到键盘事件的 tmux 调试好久,最终发现热键怎么没注册上,才找到配置文件里一处没有被更新到的 tsundere 字样……

这个扩展名叫 fcitx-paste-primary,源码放 GitHub 上了。Arch Linux 用户可以通过 AUR 或者 archlinuxcn 仓库安装 fcitx-paste-primary-git。

对了,差点忘了说,这个扩展「粘贴」的时候,只是把会被粘贴的文本提交给应用程序,程序并不会认为是真的粘贴,所以在一些需要区分的程序里会出现问题。比如 Vim 和 zsh,都会把来自 fcitx-paste-primary 的文本当作用户输入而非粘贴而可能造成问题。

Category: Linux | Tags: fcitx X Window X window
4
9
2019
4

T470p 使用N卡运行 Xorg

这么做的原因是:这样 minecraft 帧率高,不卡顿。

  • intel 显卡:帧率低,好像是20fps左右吧。开不了光影
  • optirun:坏了
  • primusrun:帧率高了一些,不多
  • nvidia-xrun:丝般顺滑,只是切换回我之前跑程序的 Xorg 时,发现我的 Awesome 已经没了。一开始是黑屏,经过配置之后倒是能得到 LightDM 的登录画图。另外 nvidia-xrun 无法卸载模块,因为被 Xorg 使用了,需要停止 lightdm。

那么,既然 nvidia-xrun 效率不错,我要是把整个桌面都搬上去呢?经过了一些折腾之后,取得了不错的结果。一个意料之外的好处是,播放视频、网页浏览器里滚动页面时常出现的画面撕裂好了~

当然这样做会费电,降低续航时间。不过既然是 T470p,一开始我就没打算整天带着它到处跑,所以无所谓啦。需要的时候再切回去好了。有个叫 optimus-manager 的软件,看介绍是帮助这么切换的。不过我对一切自动化程度太高的软件都心存疑虑,不确定它到底干了什么,会不会和我其他的配置相冲突。所以以后再看看啦。

最终的配置方案是这样的——

首先,把 bumblebeed.service 关掉并禁用。

然后,Xorg 配置一份,放 /etc/X11/xorg.conf.d/ 下就好。这份配置来自于惠狐的《Archlinux 下 Intel 和 NVIDIA 双显卡 de 折腾笔记》一文。

Section "OutputClass"
    Identifier "intel"
    MatchDriver "i915"
    Driver "modesetting"
EndSection

Section "OutputClass"
    Identifier "nvidia"
    MatchDriver "nvidia-drm"
    Driver "nvidia"
    Option "AllowEmptyInitialConfiguration"
    Option "PrimaryGPU" "yes"
    ModulePath "/usr/lib/nvidia/xorg"
    ModulePath "/usr/lib/xorg/modules"
EndSection

lightdm.conf 里在 [Seat:*] 里加一个 hook 配置,否则会黑屏的:

display-setup-script=/usr/local/bin/lightdm-setup

这个脚本内容如下:

#!/bin/bash -e

xrandr --setprovideroutputsource modesetting NVIDIA-0 || exit 0
xrandr --auto

写了一个 systemd service,用来启用 N 卡。因为默认它是关的。

[Unit]
Description=Switch On nvidia card
ConditionPathExists=/proc/acpi/bbswitch
Before=display-manager.service

[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo ON > /proc/acpi/bbswitch"

[Install]
WantedBy=graphical.target

我之前在 ~/.xprofile 配置了视频的硬件加速,现在得删掉。GM108M [GeForce 940MX] 这个显卡的视频加速没法用的。

设置内核模块的选项 options nvidia_drm modeset=1,不然 xrandr --scale 时结果会不对。

暂时就这些了。


2019年07月20日更新:我又换回 Intel 显卡了。虽然这样性能差一点,滚动、视频时画面有点撕裂,外接屏幕中鼠标会闪,但是它稳定可靠啊!Nvidia 的驱动实在是崩得太闹心了(而且我那卡不支持视频硬解)。

2019年09月03日更新:我用上了 NVIDIA 新的 PRIME 方案,效果很好~

Category: Linux | Tags: linux 硬件 显卡驱动
4
3
2019
0

正确的隐藏挂载点的方法

脚本需要挂载文件系统,但是不希望外部看到。正确的做法是:

mount --make-rprivate /

然后该干嘛干嘛。当然如果你不知道在执行之前先调用 unshare 或者等价的系统调用,说明这篇文章不适合你阅读。

错误的做法是在挂载的时候加 --make-private 或者把 / --make-private。这个标志(MS_PRIVATE)的意思是挂载/卸载事件在这里停止传播,而不是这个挂载点的事件是否传播出去。至于为什么需要使用 --make-rprivate(增加了 MS_REC 标志),暂时我还不理解。

这个用法是从 unshare 工具的 strace 结果里挖掘出来的。因为我的目的跟 unshare -m 一样嘛,当然首先想到的是看看它是怎么干的了。你问我为什么不用 unshare -m?你自己写脚本的时候试试看啰?

Category: Linux | Tags: linux 文件系统
3
31
2019
8

迁移系统到 SSD

最近一段时间,不知道是磁盘、缓存相关算法的更新,还是我开的服务太多,又或者是新软件占用内存太高,我的系统越来越卡了,尤其是更新系统的时候(备份系统时也特别卡,然后我用限制内存占用的办法解决了)。我当然知道最主要的原因是因为机械硬盘的处理能力就那么多,于是经过一些了解和计划之后,还是决定换 SSD 了。

刚才查看了一下历史数据。从去年七八月份起,平均内存使用量从2G多升高到了3G多。大概是火狐更占内存了吧。我都尽量减少内容进程数量了……也可能是 PHP / MediaWiki 的锅,因为使用 SQLite 存储时,经常发生错误也是这段时间的事情。不过也可以理解为由于磁盘负载重导致的。算了不管了。

准备工作

当然首先要去买块 SSD 啦。我买的是 LITEON T11 Plus 512,512GB,800块。实际操作系统得到的空间是 477GiB,因为硬盘产业还在沿用1000进制的单位词头。它比我预期的要小不少呢,不过拿在手里感觉比一般同样大小的电路板要重。

拆开我的 T470p,把空闲接口旁边的螺丝下下来,然后 SSD 标签朝外插进去。我也不清楚这个接口叫什么。插进去之后它是翘起来的,难怪要用螺丝固定。然后用下下来的螺丝固定好,再把机器装好,就好了。启动系统,可以看到 /dev/nvme0n1 设备在了~GNOME 磁盘软件不能读取到 SMART 信息,用 smartctl -a /dev/nvme0n1 命令就好了。

设备没问题了,接下来当然是备份系统啦。

开始迁移

备份妥当之后,我就开始格式化 SSD。计划是 EFI 分区 512M,400G 给我的 Arch Linux,然后剩下 76G 左右的空间预留给我的 Win10。

然后这 400G,首先上一层 LUKS 加密,然后格式化为 btrfs 文件系统。其实我想要 btrfs 很久了,快照、去重、压缩都挺棒的。但是听说它的性能比较差,而我已经在受磁盘 I/O 能力不足的苦了,所以到现在有了 SSD,是时候换 btrfs 了!

其实之前 zfs(zfsonlinux)也是候选项,并且已经在工作本上使用过了。然而最近我的 zfs 备份两度出现问题(磁盘掉线之后 zfs 元数据损坏,导致一整个 zfs 文件系统一写就卡住;近期莫名其妙 rsync 跑着跑着就卡在那里不动了,磁盘也没有什么活动),再加上之前遇到的各种大小问题(ARC 被算进内存使用量中;挂载期间一旦磁盘离线就卡死;克隆出来的文件系统无法摆脱原文件系统;不支持 overlayfs;因为是树外模块所以需要专门准备的支持 zfs 的系统来执行安装),以及 TRIM 支持刚刚才加入,我已经停用 zfs 并将其排除考虑范围了。

然后就是规划子卷。参考了 openSUSE 的方案,最终决定分为这么几个子卷:/, /var/cache, /var/tmp, /var/log, /var/lib/lxc/lxc-debian/rootfs, /var/lib/lxc/lxc-centos6/rootfs, /home/lilydjwg, /home/lilydjwg/.cache。主要考虑的是快照。另外我给 /var/log/journal 和 /var/lib/postgres chattr +C 禁用了 CoW。这样也会禁用压缩,不过本来它们基本上就没什么可压缩的。需要排除的有:我的公开第三方源码和各类大文件用的 /ldata 还是放在机械硬盘上、/var/cache/pacman/pkg 缓存不要、/var/lib/pacman.fs 不用单独放连续的文件里了、/home/lilydjwg/.cache 缓存不要、/home/lilydjwg/.debug 这个 perf top 用的目录会有 libc 的硬链接,rsync 时会失败所以就不要了。

最终的同步命令如下:

sudo systemd-run -p MemoryMax=64M --scope \
  rsync -aviHAXKhPS --inplace --delete --exclude='*~' --one-file-system \
  / /mnt/root --exclude=/var/cache/pacman/pkg --exclude=/home/lilydjwg/.cache \
  --exclude=/var/lib/pacman.fs --exclude=/ldata --exclude=/home/lilydjwg/.debug

同步好之后,重启进入 live 系统再同步一次以保证最新数据也同步好了。然后把部分被排除的目录再同步一下:~/.cache/winetricks 这个以后不一定能够下到、~/.cache/sxiv 都是有效缓存(我有清理)而且生成耗 CPU、/var/lib/pacman 这个是被 --one-file-system 排除掉的。

然后是在 /etc/default/grub 里更新内核命令行 cryptdevice=/dev/disk/by-partlabel/ssd:ssd:allow-discards。这个 allow-discards 会轻微地降低安全性,不过在中国没什么用的。更新 /etc/fstab。

然后还有 /boot 要处理。其实就是把内核和 initrd 复制过去,然后重新安装 grub、生成 grub 配置。位于机械硬盘上的旧文件之后再删掉即可。

重启,使用 fallback 版 initrd 进入系统,开始修复各种问题。

首先是更新默认的 initrd。不过在更新它之前,我要修改一下我自己的 hook。之前这个 hook 里只有 partprobe 我解密之后的机械硬盘分区,因为我在它上边又分了 xfs 和 swap 两个区。现在因为 encrypt hook 解密的是 SSD 上的分区,所以这个机械硬盘上的加密分区的解密也要自己做。其实也很简单,给这个加密分区添加一下文件密钥,然后

cryptsetup open --type=luks --key-file=/etc/keys/hdd.luks /dev/disk/by-partlabel/main main

就可以了。不需要输入两次密码。

/ldata 使用 automount 延迟挂载,所以需要写 ldata.mount 和 ldata.automount 两个文件,然后 enable ldata.automount 那个。不知道写在 /etc/fstab 里是不是也行。然后把机械硬盘里的目录结构调整一下,把原来 /ldata 下的东西上移一级,旧的 / 里的其它东西都放到隐藏的 .oldroot 里去好了。

swap 本来我是保留着的,不过发现这样子我会时不时听到机械硬盘启动了。而且因为机械硬盘启动比较费时,所以系统会卡好一会儿(大概有一两秒)……所以我默认就不开 swap 了,但是 resume hook 还是保留,需要的时候打开 swap 就可以休眠了。这个 resume hook 也是我需要在启动的时候就解密机械硬盘上的加密分区的原因。

加了一个每周运行的 fstrim -v / cron 任务。没有使用 fstrim.timer 是因为它会 trim 所有设备。而我可不希望它去 trim 我挂载的机械硬盘上的 loop 设备,会造成大量碎片的。

还有一些小问题要处理。chattr +i /etc/resolv.conf 以避免 DNS 服务器被不知不觉修改了。我有用 dnsmasq 的所以这个文件不用动。我有一个 MediaWiki 实例的文件是使用 overlayfs 的,它现在挂载提示「failed to verify upper root origin」。后来才发现相关目录上有同步到几个 trusted. 开头的、overlayfs 使用的扩展属性。是它还挂载的时候被同步到的,不知道为什么最后一次同步时没有被清除掉。手动使用 setxattr 删除掉就好了。

rsync 还出了另外几个莫名其妙的问题。我在 /usr/local/sbin 下有个最近新加的文件的执行权限消失了,造成使用它的 systemd 服务失败。另外有个最近被删除的配置文件竟然还在。我不是有指定 --delete 选项吗?火狐缓存的网站图标也都没有了,需要访问之后才会重新出现。~/.cache 下有很多 root 所有的空目录,也许是我哪次忘记 --exclude 它然后又中断才加上?

Wine 有几个文件有几十 KiB 大的 user.wine.sd 扩展属性。太大了以至于 btrfs 里放不下,报「No space left on device」错误。我刚看到时还吓一跳,以为是我的 SSD 满了,仔细一看才发现只是扩展属性写不下而已。

我于是又带 --dry-run 参数同步了一次,确定再没有什么需要的东西被落下。这次 rsync 出现这些问题很是奇怪,不过我没有留日志,加上操作的时候其实是有不少修修改改的,所以就不深究了吧。

修好所有发现的问题,再次重启之后,systemctl status 和 systemctl --user status 没有失败项了~撒花 O(∩_∩)O~

后记

现在我的系统超快的!比如启动时间:

>>> systemd-analyze
Startup finished in 9.257s (firmware) + 1.466s (loader) + 15.110s (kernel) + 6.945s (userspace) = 32.780s 
graphical.target reached after 6.945s in userspace

firmware 和 loader 咱管不了。kernel 那儿包含了我输入密码解密,以及解密和探索机械硬盘上的分区,所以花了些时间。userspace 那里你别看花了好几秒,其实大部分时间都是花在联网上了。不依赖网络的服务在差不多一秒的时间内就全部启动好了。

之后我还要更新备份脚本,因为我用了 --one-file-system 而现在它们在不同的子卷上。再写一下每日快照的脚本,就不用一不小心删错文件啥的都要去备份里找了。

关于写入量,smartctl -a /dev/nvme0n1; sleep 300; smartctl -a /dev/nvme0n1 统计了一下,因为我开了 collectd 收集一些系统数据,每分钟大概会写入 60MiB 的数据。算下来,一年要写 20T 左右。这块 SSD 标称的是 280TBW,也就是可以写 280TB 的数据。这么算起来能用十年,所以就这样吧,不用再优化了。顺便说一下,SMART 信息里的「Data Units Written」数据,乘以 512000 之后是字节数。

就这样啦。最后还要说一句:SSD 超快的!

3
2
2019
2

使用 cgroups 限制指定进程的内存使用

最近我的系统有这么个问题:在备份或者系统更新等高 I/O 负载的时候,系统整体性能下降严重,界面经常卡到动不了。经过分析发现此时比平常多了许多磁盘读操作。平常的时候磁盘读操作是很少的,会有大量的缓存命中,反倒是写操作一直都有(因为我本地搭了个监控系统)。啊对,分析用到的磁盘性能数据就是来自于这个监控系统。

所以原因很清楚了:备份和系统更新不仅造成了大量缓存未命中,还占用了本来存放着热数据的缓存,导致常规使用的缓存命中率也急速下降,结果我的机械硬盘就忙不过来了。

那么,要是能够限制这些操作占用的缓存,性能不就能好一点吗?那些新读进来的数据反正是短期内再也用不到了,缓存起来也只是浪费有限的内存空间啊。

研究了一下 /sys/fs/cgroup/memory/memory.stat,看起来 cgroups 内存限制是包含缓存部分的,于是就试试呗。正好 systemd 直接就能设置了:

$ sudo systemd-run -p MemoryMax=512M --scope pacman -Syu

本来我是设置的 256M 限制,结果发现 dkms 编译内核模块的时候超级慢,还用掉了不少 swap……于是分了 512M。效果还是不错的,常规操作偶尔还是卡一卡(毕竟还是有一些 I/O 操作),但比起不限制的时候要少很多。

要注意一点的是,不使用 cgroups v2 的话(Arch Linux 默认),这个命令不能加 --user 以在用户级的 systemd 下跑的。而使用 cgroups v2 的话,lxc 和 docker 都跑不了……

备份也是类似的,而且因为 rsync 自己用不到多少内存,这个效果更好:

$ systemd-run -p MemoryMax=256M --scope ./backup-my-system

终于又一次在半小时内完成了备份 QAQ 之前动不动就一两小时的。

我也不知道为什么这个问题近期才出现,总之现在是缓解了。(接下来有空继续计划换 SSD 硬盘的事情~

2020年10月06日更新:现在知道系统在内存不足、使用 swap 的时候出现的卡顿现象可能是这个 bug,并且在 5.5 版本中被修复。我在迁移到 SSD 之后也一直禁用 swap 也是因为这个问题。现在更新到 5.8 之后开启 swap,并没有在 swap 时发生明显的卡顿了。PS: 关于 swap 是干嘛的,可以参看 farseerfc 的这篇《【譯】替 swap 辯護:常見的誤解 》以及后续

Category: Linux | Tags: linux systemd cgroups
2
16
2019
4

在 Linux 下整理磁盘碎片

磁盘碎片其实有两种:文件碎了,和空闲空间碎了。使用 FIEMAP 命令可以获取到文件在磁盘(的逻辑地址上)的分布情况。也是 filefrag -v 命令输出的东西。比如我的 pacman.log 就很碎:

Filesystem type is: 58465342
File size of /var/log/pacman.log is 11052443 (2699 blocks of 4096 bytes)
 ext:     logical_offset:        physical_offset: length:   expected: flags:
   0:        0..    2015:  170210423.. 170212438:   2016:
   1:     2016..    2017:  170567879.. 170567880:      2:  170212439:
   2:     2018..    2027:  170569969.. 170569978:     10:  170567881:
   3:     2028..    2030:  170574582.. 170574584:      3:  170569979:
   4:     2031..    2031:  170574631.. 170574631:      1:  170574585:
   5:     2032..    2033:  170592662.. 170592663:      2:  170574632:
....
 123:     2683..    2687:   56903805..  56903809:      5:   56906403:
 124:     2688..    2698:   56903011..  56903021:     11:   56903810: last,eof
/var/log/pacman.log: 125 extents found

整理的办法也很简单,复制一下,基本上就好了。只要剩余空间足够,小文件会变成一整块,大文件也是少数几块。如果非要弄一整块大的,比如我存放 pacman 数据库的那个小文件系统,可以用 fallocate -l 200M pacman.fs2 这样子的命令分配空间,然后把数据 dd 进去(cp 不行,因为它会先截断文件再写入,之前分配的空间就释放掉了)。

介绍完毕,重点来了:怎么找到那些被写得很碎很碎的文件呢?

对每个文件调用 filefrag 肯定太慢了,所以我写了个库和工具 fiemap-rs 直接调用 FIEMAP。它提供两个工具。一个是 fraghist,统计碎片数量分布直方图,用来了解一下某群文件有多碎。另一个是 fragmorethan,用来寻找碎到一定程度的文件。运行起来是这样子的:

/var/log:
# Number of samples = 712
# Min = 1
# Max = 297
#
# Mean = 11.338483146067423
# Standard deviation = 40.138129228003045
# Variance = 1611.0694179238724
#
# Each ∎ is a count of 13
#
  1 ..  31 [ 658 ]: ∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎∎
 31 ..  61 [  11 ]:
 61 ..  91 [   9 ]:
 91 .. 121 [  10 ]:
121 .. 151 [   6 ]:
151 .. 181 [   5 ]:
181 .. 211 [   3 ]:
211 .. 241 [   2 ]:
241 .. 271 [   3 ]:
271 .. 301 [   5 ]:
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 271
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 277
/var/log/journal/00000000000000000000000000000000/system.journal: 274
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 297
/var/log/journal/00000000000000000000000000000000/system@xxx.journal: 274

我系统上最碎的两群文件是 journal 日志和 python2-carbon 的数据文件。carbon 优化做得挺不好的,明明是预分配的固定大小文件啊,不知道怎么的就弄得很碎了。部分程序的日志(如 pacman、getmail)和火狐的 SQLite 数据库也挺碎的。后边这些我已经处理掉了所以示例输出只好用 journal 的啦。

找到想要整理的过碎的文件之后,复制一下就好啦:

for f in $(<list); do sudo cp -a $f $f.new; sudo mv $f.new $f; done

啊对了,工具的编译方法是,获取源码并安装 Rust 之后,在项目根目录里 cargo build --release 然后就可以在 target/release 下找到新鲜的可执行文件了~顺便说一下,这东西是支持 Android 的哦。

Category: Linux | Tags: linux 文件系统 Rust
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 安全

| Theme: Aeros 2.0 by TheBuckmaker.com