跟 UMU 一起玩 OpenWRT(入门篇17):卸载 U 盘

需求

在 PC 插入 U 盘/移动硬盘,Windows 会发出令人愉悦的“灯等灯”声,然后 U 盘灯开始牛逼闪闪(如果有灯);安全弹出时,又会发出“的的等”,灯熄灭(有些 U 盘不会灭灯,而是常亮着,不会再闪;移动硬盘一般都会灭灯)。

OpenWRT 这么强大,怎么能不支持?

  • 什么?你说 umount?那 /dev/sda 还能重新挂载呢!那灯还亮着呢!(有些 U 盘弹出后灯常亮,设计好的才会灭灯。)

  • 什么?你说直接拔掉?你赢了!但有时候,稣是在远程操作,要是没人配合拔掉,岂不是要插着耗电?穷人可是交不起电费的……

解决

1
2
3
4
opkg update
opkg install eject

eject /dev/sda

终于安全弹出啦!

注意:弹出后,​/dev/sda 还会存在,但无法再 mount,而且它下面的分区 /dev/sda1 ​等,都会消失。

eject 默认会先后尝试使用 CD-ROM 和 SCSI 命令弹出设备,可以用 -v 参数查看详细流程,一般 U 盘用 -s 参数指定使用 SCSI 命令更为直接。

相关

跟 UMU 一起玩 OpenWRT(入门篇6):挂接 U 盘

在树莓派上编译 go-ipfs

1. 需求

用 PC 当 Server 测试环境,费电!挖出吃灰多年的树莓派 Model B Rev 2 000f,打算用它跑 ipfs!

2. 系统选型

  • 较熟悉的 CentOS、FreeBSD、Ubuntu Server、Windows IoT 的当代主流版本都不支持这款古老的树莓派。

  • ArchLinux 支持,然而稣个人认为 ArchLinux(属于 Linux 中的邪教)不适合当 Server。

  • 尝试刷 OpenWRT,发现即使设置密码,本地控制台也是没密码就能登录。这不太安全,虽然本地就是不安全的,但别的系统可不是这么设计的!

  • 还是官方的 Raspbian Buster Lite 吧!

3. 安装系统

主要参考官方文档:

  • Setup:选个 16GB 的 SD 卡。

  • Installing operating system images:用官方 Raspberry Pi Imager 工具把系统镜像刷到 SD 卡。

  • 接 HDMI 显示,通电。首次启动,系统会自动对 SD 卡的分区进行扩容,使第二个分区扩满未分配空间。

4. 配置系统

通过 sudo raspi-config 做基本配置:

  • 进“本地化”把默认语言 en_GB.UTF-8 去掉,勾选 en_US.UTF-8。

  • 键盘布局改为通用 105 键(国际)美国布局(默认的英国布局下按 | 会变 ~)。

  • 时区改为当地。

  • 改机器名(如果您有多个树莓派,不改会重名),稣将之改为 rp1b。

  • 改 pi 用户的密码。

  • 开启 SSH,插上网线或者 USB 无线网卡,就可以从别处远程登录它了。

5. 配置国内 apt 源

1
2
3
4
5
6
7
sudo mv /etc/apt/sources.list /etc/apt/sources.list.bak
# 增加阿里云源
echo 'deb https://mirrors.aliyun.com/raspbian/raspbian/ buster main non-free contrib
deb-src https://mirrors.aliyun.com/raspbian/raspbian/ buster main non-free contrib' | sudo tee /etc/apt/sources.list.d/aliyun.list
# 增加清华大学源
echo 'deb https://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main non-free contrib
deb-src https://mirrors.tuna.tsinghua.edu.cn/raspbian/raspbian/ buster main non-free contrib' | sudo tee /etc/apt/sources.list.d/tsinghua.list

6. 安装 Go 编译器

打算直接在树莓派上编译,所以要先在树莓派上安装编译环境。不过不要通过 sudo apt install golang 安装,因为截至今天(2020-03-28),这命令安装的是 1.11.6 版,这对 go-ipfs 项目来说太低了。

golang 官网下载 ARMv6 安装包,目前最新版本是 1.14.1

压缩包里是有一个 go 文件夹的,所以只要解压到 /usr/local/ 下即可。

1
2
3
4
5
6
# aria2 比 wget 强大
# sudo apt install aria2
aria2c https://dl.google.com/go/go1.14.1.linux-armv6l.tar.gz
tar -C /usr/local -xzf go1.14.1.linux-armv6l.tar.gz
sudo ln -s /usr/local/go/bin/go /usr/bin/go
sudo ln -s /usr/local/go/bin/gofmt /usr/bin/gofmt

7. 编译项目

1
2
3
git clone https://github.com/ipfs/go-ipfs
cd go-ipfs
make build

有很多依赖库需要下载,开始漫长等待……如果代码都下载完,则 make build 的输出为:

1
2
3
4
5
go version go1.14.1 linux/arm
bin/check_go_version 1.14.1
plugin/loader/preload.sh > plugin/loader/preload.go
go fmt plugin/loader/preload.go >/dev/null
go build "-asmflags=all='-trimpath='" "-gcflags=all='-trimpath='" -ldflags="-X "github.com/ipfs/go-ipfs".CurrentCommit=3561de074-dirty" -o "cmd/ipfs/ipfs" "github.com/ipfs/go-ipfs/cmd/ipfs"

最后一行会卡很久!em……用高性能机器来交叉编译才是正确的方式!

8. 在 macOS 上编译树莓派程序

树莓派的 CPU 架构是 armv6l,所以用以下命令编译:

1
CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=6 make build

在 MBP15 上编译快很多!(前面纯属折腾!)编完复制到树莓派:

1
scp ./cmd/ipfs/ipfs pi@rp1b:/home/pi/

在树莓派上测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
pi@rp1b:~ $ uname -a
Linux rp1b 4.19.97+ #1294 Thu Jan 30 13:10:54 GMT 2020 armv6l GNU/Linux

pi@rp1b:~ $ ./ipfs version
ipfs version 0.5.0-dev

pi@rp1b:~ $ ./ipfs init
initializing IPFS node at /home/pi/.ipfs
generating 2048-bit RSA keypair...

pi@rp1b:~ $ ./ipfs daemon
Initializing daemon...
go-ipfs version: 0.5.0-dev-3561de074
Repo version: 9
System version: arm/linux
Golang version: go1.14
Swarm listening on /ip4/127.0.0.1/tcp/4001
Swarm listening on /ip4/192.168.1.91/tcp/4001
Swarm listening on /ip6/240e:379:254c:9d00:969d:e453:9448:1efb/tcp/4001
Swarm listening on /ip6/::1/tcp/4001
Swarm listening on /p2p-circuit
Swarm announcing /ip4/110.87.124.125/tcp/21551
Swarm announcing /ip4/127.0.0.1/tcp/4001
Swarm announcing /ip4/192.168.1.91/tcp/4001
Swarm announcing /ip6/240e:379:254c:9d00:969d:e453:9448:1efb/tcp/4001
Swarm announcing /ip6/::1/tcp/4001
API server listening on /ip4/127.0.0.1/tcp/5001
WebUI: http://127.0.0.1:5001/webui
Gateway (readonly) server listening on /ip4/127.0.0.1/tcp/8080
Daemon is ready

学习 Rust【4】调用 libc

问题

Rust中如何使用linux的原生api? - 知乎

概述

很多语言调用 C 语言写的模块来弥补自己某些不足。Rust 当然也可以调用 C 语言开发的模块,不过这是不安全的。

代码

  1. 在 Cargo.toml 中加入依赖库:
1
2
[dependencies]
libc = "0.2.68"
  1. Rust 示例代码:
1
2
3
4
5
6
7
8
9
10
11
12
use libc;

fn main() {
let pid = unsafe { libc::fork() };
if pid < 0 {
eprintln!("错误!");
} else if pid == 0 {
println!("子进程空间");
} else {
println!("父进程空间, 子进程 pid 为 {}", pid);
}
}
  1. macOS 测试通过。

家用 WiFi 方案

一、问题

我房子太大,一个无线路由器覆盖不了怎么办? - 知乎

二、经验

  • 1000 多平方米的办公室够大吧?一个小米路由器 Pro 放在中间,办公区域覆盖完全,仅厕所信号较弱。

  • 100 平占地面积,三层楼(无电梯、楼梯洞很大),在二楼放一个 79 块的路由器,日常无痛使用 4 年以上。

WiFi 信号是通过漫反射传播的,不是什么“穿墙”!所以重点不是“太大”,而是钢筋混凝土墙的格局。

震惊!WiFi 信号在空旷的地方轻松覆盖你家十栋大别墅的面积!

真正能靠透射、衍射穿透的是玻璃、木头、塑料之类。5.8G 信号是厘米波,能穿透的厚度也不大,稍微厚点的木门都不行,只能从门下的缝钻进去。您可以在房间里做开关门试验,结论是:穿墙太难了,撞墙倒是会反弹……

5G WiFi 就是 5.8GHz,为和 5G 蜂窝网络区分开,特意采用 5.8G。

三、廉价而靠谱的方案

主路由 + AP 方案!中间当然是千兆网线连接,如果您没有布网,那就跳过,看后面分析为什么其它方案都不靠谱。

1. 硬件

本方案需要的硬件是 N 个刷 OpenWRT 的路由器:

  • 主路由器,自然就是放在多媒体箱里,接光猫或入户网线。

  • AP,其实也是个 OpenWRT 路由器,只是关闭 DHCP 服务,配置和主路由器同网段,然后用 LAN 口接主路由器的 LAN 口。

AP 可以有多个。比如稣家,是长方形结构,距离多媒体箱最远的主卧关上门基本零信号,只能在主卧放一个 AP,然后全家都覆盖完整。

2. 软件

  • 只开 5.8G,尽量不开 2.4G。稣家的“只支持 2.4G 的终端设备”全部集中在大厅区域,所以只有主路由器开启 2.4G。如果开多个 2.4G,注意信道隔离。每个 5.8G 信号都用同样的 SSID,以实现漫游。

  • AP 的 IP 地址可以使用静态,也可以使用 DHCP client,然后在主路由器上绑定地址。AP 有固定 IP 方便登录管理。

  • 如果 AP 需要 IPv6 地址,再创建一个 lan6 接口,物理 Interface 选择 br-lan(和 lan 接口一样,但不能勾选 Bridge interfaces),协议是 DHCPv6 client。

3. 测试

有人说,这样的方案缺乏 AC 管理,会导致终端设备可能不会自动切换信号。稣特地拿出祖传的 iPad2,从大厅漫步到主卧,神奇的事情发生了——居然可以自动切换 5.8G WiFi 信号!终端能不能自动切换,这显然是驱动程序决定的,虽然 iPad2 已经很老,但系统有更新到 iOS 9.3.5……

实在找不到一个不能自动切换信号的设备!好,测试结束。方案完美上线。

4. 为什么这个方案可行?

  • OpenWRT 说它确实可行!

  • 移动中使用设备的机会不高。您不会在家里跑来跑去,同时费劲地使用设备,有可能您根本就不需要自动漫游。

  • 您不会在家里的每个角落使用 WiFi,所以有很多地方并不需要有信号,比如洗手台、走廊、厨房。

四、AC+AP 方案?

买不起!什么?你说某些乐射 AC 很便宜?不好意思,很便宜也不是免费,还耗电!做人,难,做穷人,难上难!

AC+AP 方案的原理:AC 会自动发现并管理 AP,设定 AP 的 RSSI 阈值,将信号不稳定的设备【踢下线】,迫使终端设备重新连接信号最强的 AP,实现 AP 的自动切换。

请注意【踢下线】三个字,用 AC,在终端移动时,一样会断线。既然都会断,那就没有本质的区别,让终端自己选择,体验并没有比较差!

所谓 AC 管理,无非是促使那些不支持 802.11k-2008 的设备重新连接而已。如果您有这样的设备,建议还是换掉他们,或者固定他们,古董啊,可别让它们逃跑了!

五、Mesh 方案?

贵!Mesh 的主节点就是个 AC,绕回 AC+AP 方案。有网线的话,为什么不用 AP 方案?根本就是一样的嘛!

六、电力线?

懒得喷……

七、参考

在 iOS 上通过 802.11k、802.11r 和 802.11v 实现 Wi-Fi 网络漫游

chsh -s zsh

标题是个坑

不要在 Ubuntu 上运行这条命令!都说 CentOS 比 Ubuntu 稳定,总算见识到具体案例!

没有对比就没有伤害

CentOS

1
2
3
4
5
6
$ cat /etc/centos-release
CentOS Linux release 7.7.1908 (Core)

$ chsh -s zsh
Changing shell for root.
chsh: shell must be a full path name

可见,机智的 CentOS,早就料到这个运维事故!

Ubuntu

1
2
3
4
5
6
7
8
9
10
11
$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.4 LTS"

$ chsh -s zsh
chsh: Warning: zsh does not exist

$ cat /etc/passwd
root:x:0:0:root:/root:zsh

SSH 到 Ubuntu Server 上,运行 chsh -s zshexit 后就再也无法登录……

如果您要远程做这个试验,记得 exitchsh -s /bin/zsh,或者 vi 手动纠正 /etc/passwd。

macOS 研究经验【2】:简单的破解

1. Beyond Compare

BC 试用版过期,思考 3 秒钟:稣太穷,买不起!

macOS 和 iOS 一样,App 都是独立存储,找出安装信息保存在哪个文件应该很容易。

确实如此!居然只要两步:

1
2
cd ~/Library/Application Support/Beyond Compare
rm registry.dat

2. X-NG

由于子公司、分公司众多,稣的服务器列表里有好多个项,想备份这个列表,发现还不是很容易!

ServerProfileManager

首先,找到 ~/Library/Application Support/X-NG/-local-config.json,但这个文件里只有当前选择的项。

然后,就看代码吧!Swift 写的,应该还好:

1
2
let defaults = UserDefaults.standard
let keys = [

根据代码线索找到:

1
defaults read ~/Library/Preferences/com.yuzhou.X-NG.plist

哇~全部出来了!

macOS 研究经验【1】:关于 APFS Container

起因

最近 MBP15 的硬盘空间告急,打开“磁盘工具”查看,却发现居然有两个“宗卷”!从没认真研究过 macOS 磁盘管理的稣疑惑了。

“磁盘工具”默认“仅显示宗卷”,“显示所有设备”后是这样的:

disk0

disk1

disk1s5

disk1s1

分析

肉眼观测 disk1 是放在 disk0 里的……嗯,想起容器!但 GUI 有时候是会骗人的,用 diskutil 来检查一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
$ diskutil list
/dev/disk0 (internal, physical):
#: TYPE NAME SIZE IDENTIFIER
0: GUID_partition_scheme *251.0 GB disk0
1: EFI EFI 314.6 MB disk0s1
2: Apple_APFS Container disk1 250.7 GB disk0s2

/dev/disk1 (synthesized):
#: TYPE NAME SIZE IDENTIFIER
0: APFS Container Scheme - +250.7 GB disk1
Physical Store disk0s2
1: APFS Volume Macintosh HD - 数据 209.2 GB disk1s1
2: APFS Volume Preboot 79.9 MB disk1s2
3: APFS Volume Recovery 526.6 MB disk1s3
4: APFS Volume VM 8.6 GB disk1s4
5: APFS Volume Macintosh HD 11.2 GB disk1s5

以上可知,只有 disk0 是物理的,disk1 是由 disk0s2 这个分区虚拟出来的。

下面查看两者详细信息对比:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
$ diskutil info /dev/disk0
Device Identifier: disk0
Device Node: /dev/disk0
Whole: Yes
Part of Whole: disk0
Device / Media Name: APPLE SSD AP0256M

Volume Name: Not applicable (no file system)
Mounted: Not applicable (no file system)
File System: None

Content (IOContent): GUID_partition_scheme
OS Can Be Installed: No
Media Type: Generic
Protocol: PCI-Express
SMART Status: Verified

Disk Size: 251.0 GB (251000193024 Bytes) (exactly 490234752 512-Byte-Units)
Device Block Size: 4096 Bytes

Read-Only Media: No
Read-Only Volume: Not applicable (no file system)

Device Location: Internal
Removable Media: Fixed

Solid State: Yes
Virtual: No
Hardware AES Support: Yes

$ diskutil info /dev/disk1
Device Identifier: disk1
Device Node: /dev/disk1
Whole: Yes
Part of Whole: disk1
Device / Media Name: APPLE SSD AP0256M

Volume Name: Not applicable (no file system)
Mounted: Not applicable (no file system)
File System: None

Content (IOContent): EF57347C-0000-11AA-AA11-00306543ECAC
OS Can Be Installed: No
Media Type: Generic
Protocol: PCI-Express
SMART Status: Verified
Disk / Partition UUID: 1DDCC569-2632-4CD5-88E7-66E2BBE745C9

Disk Size: 250.7 GB (250685575168 Bytes) (exactly 489620264 512-Byte-Units)
Device Block Size: 4096 Bytes

Read-Only Media: No
Read-Only Volume: Not applicable (no file system)

Device Location: Internal
Removable Media: Fixed

Solid State: Yes
Virtual: Yes
Hardware AES Support: Yes

This disk is an APFS Container. APFS Information:
APFS Physical Store: disk0s2
Fusion Drive: No

两者 Virtual 属性的不同,说明前面的猜测是对的。

理论求证

搜到如下参考:

推理正确!

libbloom

概念

Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,具有很好的空间和时间效率,被用来检测一个元素是不是集合的成员。

Bloom filter 采用的是哈希函数的方法,将一个元素映射到一个 m 长度的阵列上的一个点,当这个点是 1 时,那么这个元素可能在集合内,反之则一定不在集合内。

libbloomBloom filter 的 C 语言实现库,其中哈希函数是 MurmurHash2。

特征

如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。

优缺点

  • 优点:插入和查询时间都是常数。它查询元素却不保存元素本身,节省大量的存储空间。当元素是密码时,不保存元素的特征使其具有良好的安全性。

  • 缺点:存在误报(false positive)。当插入的元素越多,错判“在集合内”的概率就越大。另外 Bloom filter 也不能删除一个元素,因为多个元素哈希的结果可能在 Bloom filter 结构中占用的是同一个位,如果删除了一个比特位,可能会影响多个元素的检测。

算法分析

以官方 test-basic 为例,简化的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <assert.h>

#include "bloom.h"

int main()
{
struct bloom bloom;
assert(bloom_init(&bloom, 1002, 0.1) == 0);
assert(bloom.ready == 1);
bloom_print(&bloom);
bloom_free(&bloom);
}

输出为:

1
2
3
4
5
6
->entries = 1002
->error = 0.100000
->bits = 4802
->bits per elem = 4.792529
->bytes = 601
->hash functions = 4

数学原理参考:《Bloom Filter概念和原理

  • bytes 是最容易理解的,4802 位需要 601 字节存储。

  • bits = bits per elem * entries,每个元素需要多少位 * 元素个数。

  • hash functions = ceil(-ln(error) / ln(2))

  • bits per elem = -ln(error) / ln(2)^2

参考

https://baike.baidu.com/item/bloom filter

学习 Rust【3】所有权

摘要

Rust 三大设计宗旨:内存安全、零成本抽象、实用。本文从所有权角度来学习师兄妹的爱恨情仇“内存安全”。

所有权规则

  • 值归变量所有。

  • 当变量超出使用范围时,变量值所占用的内存将被释放。这是类似于 C++ 的 RAII 概念。

  • 变量值可以由其他变量使用,但需遵守由编译器强制要求的若干规则。

前两条是其它语言也有的,没啥好说,重点放在第三条。

四种使用方法和规则

  • 克隆(clone):此处将值复制到新的变量。新变量拥有新的复制值的所有权,而原始变量保留其原始值的所有权。

    你有一本书,稣按照你那本书,买了一样的书。你的书是你的书,稣的书是稣的书。

  • 移动(move):所有权被转移到另一个要使用该值的变量,原始变量不再拥有所有权。

    学姐含情脉脉地把她的书送给稣。

  • 不可变借用(immutable borrow):没有发生所有权转移,但是可以通过另一个变量读取该值。当借用变量超出范围,内存不会被回收,因为借用变量没有所有权。

    学长不太情愿地把书借给稣,并交代:“书借你,只能看,绝壁不要在上面做笔记,被我发现会砍死你的哦!我偶尔会找你查查。”

  • 可变借用(mutable borrow):可以通过另一个变量对该值进行读取和写入操作。当借用变量超出范围,内存也不会回收,因为借用变量没有所有权。

    稣把书借给学妹时说:“这书你随便用,把稣的书当做你自己的书,等到用不上时再还。”

重点在于一个“借”字:书的所有权属于其主人,但主人将书借出之后,自己是无法再在书上做笔记啦!

不可变借用(immutable borrow)规则

  • 借出期间,借方不能写:学长说过,在书上乱画,要砍死稣!

  • 借出期间,所有者不能写:这书学长已经学完,偶尔要复习,但已经不需要写笔记,借出去之后就更不会写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn main() {
let mut owner = format!("学长的书");

// 学长大笔一挥,在书上记了一点心得!
owner.push('.');

// 稣不可变借用学长的书
let borrower = &owner;

// 学长随时可以来找稣翻翻他自己的书
println!("学长读{}", owner);
println!("学长再读{}", owner);

// 没问题,稣可以读学长的书
println!("稣读{}", borrower);

// error: 书还在稣手里,学长不能写
//owner.push('.');

println!("稣再读{}", borrower);

// error: 学长说过,在书上乱画,要砍死稣!
//borrower.push('.');

// 稣已经归还,学长可以做笔记了
owner.push('.');
}

可变借用(mutable borrow)规则

  • 不能多次可变借用:只能有一个独占的学妹(active borrow),稣不能同时承诺给多个学妹“随便用”,不然学妹们可能打起来……

  • 所有者不能再读写:书在学妹手里随便蹂躏,稣虽然心疼,但不能说!等她爽(huan)了再说吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn main() {
let mut owner = format!("稣的书");

// 学妹可变借用稣的书
let mutable_borrower = &mut owner;

// error[E0499]: cannot borrow `owner` as mutable more than once at a time
// 学姐也要借,稣表示:要书没有,要命一条!
//let mutable_borrower_b = &mut owner;

println!("学妹读{}", mutable_borrower);

// error[E0502]: cannot borrow `owner` as immutable because it is also borrowed as mutable
// 学妹暂时完全掌控稣的书
//println!("稣读不鸟{}", owner);

// 学妹大笔一挥,在书上记了一点心得!
mutable_borrower.push('.');
println!("学妹再读{}", mutable_borrower);

// 书已经归还给稣,稣可以做笔记了
owner.push('!');
println!("稣读{}", owner);
}