善意提醒

如果您打开本站很慢,布局排版混乱,并且看不到图片,那么可能是因为您还没有掌握用科学的方法上网的本领。
显示标签为“技术笔记”的博文。显示所有博文
显示标签为“技术笔记”的博文。显示所有博文

2026-07-24

Nube.sh 日本机房与 Vultr LA 之间的 MTU 踩坑与修复小记

昨天偶然之中发现,自己在 Nube.sh 的 Tokyo 可用区购买的 VPS,无法正常访问在 Vultr 上的一台 LA 的 VPS。具体表现为 SSH 端口能通,然而真连接过去的时候,一些操作会中断,无法继续。例如:

  • ls 一个文件多一点的目录,显示到一半就卡住了。
  • vim 编辑文件,必定黑屏,也是卡住,必须关掉 SSH 会话。

我是在 SecureCRT 中通过在 Firewall 中 Select Session 设置了转发,也就是说 SSH 隧道过去的。不清楚直接在命令行上 SSH 过去会如何,但无论如何这个现象都不正常。

这个问题只在这两台 VPS 之间存在,方向倒过来的情况我没试过。感觉跟两者之间的网络状况有一些关系。现在排查这类问题也算简单:直接问 AI,比我自己想要快,也可能更靠谱,至少能得到一些方向性指引。果然,AI 的回答直指问题的核心——MTU。

我的确是在 Nube.sh 的客服群里面见到过有人在反映日本的 VPS 有问题。回头搜了一下(奇怪 Telegram 的搜索功能我记得不太好用来着?),发现今年五月份的时候就有人回答说是 MTU 不够。老板也认可了这个结论,不过后续当客户催促修正时,老板那边没了下文。Nube.sh 的规矩一向是「只认工单」,我估计那个客户没发工单,所以这个事情直到现在应该都还是这个状况。

仅仅是 MTU 的问题的话,是可以自己动手搞定的。AI 同时也提供了方案:

  1. 调整网卡设置,临时或永久地修正。
  2. 调整 iptables 设置,针对性修正。

因为我并没有广泛测试,说不定还有哪些机器和 Nube.sh 日本这台 VPS 之间也有类似问题,所以我就选了方案一,直接改网卡设置了。一劳永逸,至少在我或牛肉老板重置 / 还原 VPS 网络设置前都能有效。

我的操作系统是 Debian 13,修改 /etc/network/interfaces 即可。Nube.sh 在 dhcp 后面加了一句post-up,在它前面再加上一句就行了:

post-up ip link set dev eth0 mtu 1400

实际能正常发送的包的大小,我用 ping 测试了一下,应该是 1448:

ping -c 4 -M do -s 1448 <IP Address>

那也就是说实际上的 MTU 应该是 1448 + 28 = 1476,不过 AI 建议我多留点余量。考虑到也没有太大的副作用,我就按它说的直接设到 1400 了。重启之后查看网卡设置:

ip link show eth0

OK,现在的 MTU 是 1400 了。SSH 恢复正常。完结,撒花。

2026-05-12

为醋包饺子

儿子在问我存起来的老游戏里面,《战国美少女》是什么游戏?
我告诉他说,是经典的日式 RPG。
他又问我,什么是 RPG,以及什么是「日式 RPG」。
在这之前,他只知道 RPG 是 DF5 里面的「火箭筒」,以及他只玩过《Fallout 4》那种美式 RPG。

于是,我打算把游戏拿出来,当场演示给他看。
然后,我发现,在我笔记本电脑的 Win11 上已经玩不了这游戏了。于是我把 VMware 找来,我有一个 WinXP 虚拟机,就是为这种事情准备的。
再然后,我发现我的 WinXP 虚拟机启动很慢,进桌面花的时间比我预期的要长得多,声音也是发抖的。然而游戏还是跑不起来。儿子一看「这么麻烦」,转身就溜了,留下我独自发愁。

WinXP 虚拟机的这个情况,我是知道的。之前在公司办公电脑上也遇到过。当时是开了 WSL2,然后 VMware 就多出来了一个选项。

图片来自网络

对于 Win7 以及更高版本的虚拟机而言,这个选项是有效果的,勾上就基本能解决问题了。但是 WinXP 虚拟机不行,勾上还是很慢。偏偏 BCB5 在 XP 下面活得比较好,各方面都比较舒服。有的同事面对这种问题是换到 Win7 虚拟机下去解决的,我最后是把 WSL2 给关掉了,反正现在用不上。

我这次也想把笔记本电脑上的 Hyper-V 给关掉。怎么关我也知道,但笔记本电脑上是 Win11 家庭版,通常的方式行不通。BIOS 里面看了一下,没有相关的信息,于是去问 Google。也有人在咨询这个问题,答案不一而足,但试了也没用。

其实我看这个「家庭版」不爽很久了。
买回来笔记本的当天,我就纠结了蛮长的时间。当时有在三个选项中来回权衡很久:

  1. 把笔记本重装成 Win10 专业版:我最熟,用起来肯定最顺手。当时我公司电脑和家里 HTPC 都是 Win10 专业版,之前的笔记本也是。这台新笔记本是我第一次用 Win11。然而,始终缩在「过去」总不是一个事。
  2. 把笔记本直接重装成 Win11 专业版:专业版解决了 RDP 的问题。然而原始出厂的东西,预安装的那些就统统没了。我知道上面是有一个 Office 的。当时我还不知道 Windows 的 审核模式 这个东西。而且我担心重装 OS 之后我的质保会出问题。
  3. 保留现在的 Win11 家庭版先用,以后再说:最后我选择了这个在我看来有点憋屈,但最稳健的做法。

现在这台笔记本电脑我已经用了两年多了,整机质保已经过了。主要硬件也扛到了现在,质保对我而言没什么大用处了,而「代价」现在慢慢开始浮现。家庭版没法开 RDP,并且现在 Hyper-V相关的东西也开关不了。但我现在上面东西也比较多,重装肯定是不希望的。那既然微软可以让人付费升级,我有没有办法呢?

问了 Google Gemini,答案是有。那就是连微软自己的技术支持人员也在用的 MAS
用 MAS,你可以把 Windows 版本给变掉。然后 Windows 会变成未激活。接下来怎么办呢?都用上 MAS 了,就别问我这个了。

Win11 专业版下,Hyper-V 可以关掉了。然而我去看的时候,发现本来就是关着的。别的相关内容也没有开。所以「家庭版」还真是干净的。我只是收获了 RDP 而已。
去看了看VMware,发现选项还在。按照 Google Gemini 说的把「内存隔离」给关掉,也没有用。我知道如果 Hyper-V 真的关掉了,那个选项会消失的。所以还是没能成功。

最后我是照着 这个 处理的,看不懂英文版的可以看 中文版,Reddit 的翻译功能还挺不错的,有时候蛮接地气。关键是第 6 步,完事后第一次重启之后黑了屏,吓得我不轻,好在之后无大碍。

重启完之后,我看了一下相关的变化。发现「固件隔离」是被关掉了。可能刚才我最后差的就是这一步吧。
现在 VMware 里面没有那个 Hyper-V 相关的选项了,我的 WinXP 虚拟机,运行性能也终于正常了。

然而我的《战国美少女》还是玩不了。
弹的报错框是 Big5 码的内容。截了图发给 Google Gemini 看了一下,说是路径有问题。
奇怪我记得我以前是玩过的,当时应该没有遇到类似问题。试了一下,手上的《战国美少女2:春风之章》可以直接在 Win10 上玩。
折腾了一大圈,解决了一堆问题,竟然又回到了起点。好吧,算了算了,去网上找了个百度云盘的 资源 补上。有空再给儿子演示看看吧。

此致,敬礼。

2026-05-06

Nube.sh 的 SJC 中国优化线路没了

五一之前,收到 Nube.sh 的 Email,通知说 SJC 的中国大陆优化线路要取消了。

给的时间不多,2026-05-01 实施,2026-04-27 通知。有点猝不及防,去 Telegram 里面看了一下,大概是这么回事:

四月中旬某个机场的中转服务器被拔线,因此拿了 Nube.sh 来跑流量。Nube.sh 的 SJC 中国大陆优化线路的速度不错,产生了很大的按流量付费账单。然而 Nube.sh 的上游是根据「九五计费」规则买的带宽。这半个月以来的高流量,把全月 95% 的总值给顶上去了,但前面闲的半个月 Nube.sh 又没拿到流量费用,所以老板在叫亏连天。最后大概是担心这类事情以后会一再上演,所以索性就停掉算了。

按理说 Nube.sh 之前肯定有机场在跑流量。老板的意思只要是长期跑都是 OK 的,就怕只跑半个月或一周的高流量。估计对方也跟老板线下讲了这个情况,老板合计一下觉得这样下去不是个事,干脆快刀斩乱麻了。

他是快刀了,我也有危机感了。花了两天时间紧急研发了一下黑魔法,总算能够 avoid 掉这次变动的影响了。也好,反正跟这些捞钱的机场长时间跑一条线路总归不是好事。我又不赚钱,无论是山贼还是六扇门我都不想招惹。

图片由 Google Gemini 3 + Nano Banana 生成

到了预定的时间,那条线路好像是断过两天网。我也没去管,后来自己又恢复了。Email 中说如果不管的话就是「国际优化」的线路跑「中国优化」的费用。但我看了一下,Resize 也会导致其它费用规格「重置」,比现在高不少。我现在的 VPS 基础费用还算便宜,可能当时有打折吧,闲置不用反而更划算。反正也只当作备份站点,平时没啥流量,那就先留着吧。我那点用途,产生不了多少流量,一个月下来全部也就 100GB 左右,就算是按「中国优化」的那档收费,也没多少钱,无所谓了。

青山不改,绿水长流。各位绿林好汉,咱们就此别过!

2026-02-04

CEF 的异步 CreateBrowser 造成的问题及解决方案

图片由 Google Gemini 3 Pro + Nano Banana Pro 生成

公司有个软件,用到了 CEF 浏览器。CEFWebClient 是一个 CEFClient 的子类,作为一个子窗口放在另外一个由我们软件控制生命周期的父窗口中的。这个父窗口的创建,有时候是自动的,而且可能会短期内进行多次。因此,我们遇到了一个问题:如果父窗口太快被「干掉」,那么可能会 Crash。

父窗口的这个行为,当然可以被认为是「不正常」。但首先我的问题是「为什么」。为什么会出现这种事情?

一年前,查这个问题费了我不少时间和精力。本文主要是把相关经验给传承下去,因此接下来我就尽量长话短说了。

CEF 中创建浏览器的方法是先 new 一个 CEFWebClient,放在智能指针里面,然后交给 CefBrowserHost::CreateBrowser() 去创建窗口。这一步许多人都知道,教程和文档上都是这样写的。不太为人所知的细节是:CefBrowserHost::CreateBrowser() 可能是异步的。

这里就要介绍一些上下文了。
跟 Demo 不同,我们的软件,把 multi_threaded_message_loop 设置为 true。几乎是必须如此,因为这个程序并不单纯是个浏览器而已,没法把主线程消息循环让给 CEF。
在多线程模式下,CEF 会有自己的一些线程用来干活。包括创建浏览器窗口这种事情,也是在它的线程中,而不是调用线程或主线程。

因此,CefBrowserHost::CreateBrowser() 是在另一个线程中进行的。这一点我一开始确实没想到,建个窗口而已,谁会想到 CEF 要搞成异步的啊?官方文档也没强调这个事实。这导致调用线程在函数调用返回时,浏览器窗口还没真正创建起来,只是把这个任务给安排了下去而已。如果父窗口在浏览器窗口真正创建前被销毁了,实际创建浏览器窗口的时候就会出问题。

我们的调用线程其实就是主线程,一开始尝试了让主线程等着,但不行。不管是用信号同步,还是用个 while 循环暴力堵住,都会导致死锁。很明显,CefBrowserHost::CreateBrowser() 在子线程中先是作了一些准备工作,然后又用到了主线程,毕竟创建窗口只可能在主线程中进行。可能是一个 SendMessage 类似的操作。我们是直接用了 Spotify 预编译好的 CEF 二进制分发包,并没有从 CEF 源代码从头编译,所以没有往里面深究。总之如果不把主线程空出来,后面的事情没法继续。

简单休眠 20ms 再放行貌似就可以解决问题,似乎是因为工作线程那边的准备工作已经完成了,已经能够正确应对父窗口被销毁这种事情了。但 20ms 时间够不够?谁知道呢。肯定不能靠这种方式在生产环境上运作。最好是有个判断标志可以判断出准备工作做完了没有,但目前 CEF 对我们来说是个黑盒,也没留这个窗口,我们只能想办法去探上一探。

CefBrowserHost::CreateBrowser() 的第二个参数,是个 CefRefPtr 智能指针,具备引用计数。这个引用计数,对调试器是可见的。我盯着看了一阵子,发现只要引用计数上升到一定程度再放行,就没有问题。貌似工作线程在「准备阶段」把这个智能指针挂到了主线程够得着的地方了,以便创建窗口时访问。只要这个操作已经完成,此后即使父窗口被析构,引用计数也不会有被降为 0 的担忧,后面就能正常工作下去。

这个引用计数在cef_base.h里面,class CefRefCount的私有成员,

//
// Class that implements atomic reference counting.
///
class CefRefCount {
 public:
  CefRefCount() : ref_count_(0) {}

  ///
  // Increment the reference count.
  ///
  void AddRef() const { base::AtomicRefCountInc(&ref_count_); }

  ///
  // Decrement the reference count. Returns true if the reference count is 0.
  ///
  bool Release() const { return !base::AtomicRefCountDec(&ref_count_); }

  ///
  // Returns true if the reference count is 1.
  ///
  bool HasOneRef() const { return base::AtomicRefCountIsOne(&ref_count_); }

  ///
  // Returns true if the reference count is at least 1.
  ///
  bool HasAtLeastOneRef() const {
    return !base::AtomicRefCountIsZero(&ref_count_);
  }

 private:
  mutable base::AtomicRefCount ref_count_;
  DISALLOW_COPY_AND_ASSIGN(CefRefCount);
};

还好是在头文件里面,而且只是一个权限问题,因此连重新编译 libcef_dll_wrapper.lib 都不需要。魔改了一下,我们就这样用下去了。

至于引用计数要增加到多少才算是可以放行,我们取了一个经验数据。但这个方案我们一直用得有点担心。最后还是遇到了问题。重现出来一查,就是这个经验数据不再适用了。

正确的解决方案,还是要回到 CEF 的官方机制 OnAfterCreated() 上来。
官方是这样说的:一个 CefBrowser 有没有创建成功,一定要等到 OnAfterCreated() 被调用。不管是要干什么正经事情,还是要「去死」,都得等到它调用了以后。
实际试下来,虽然 OnAfterCreated() 的调用比引用计数「到位」要迟,但的确可以保证不出事情。

所以事情其实没有那么复杂:如果 OnAfterCreated() 还没被调用到,那么就不能关闭父窗口。不能关闭怎么办?先把窗口给 SW_HIDE 了,然后向 CEFWebClient 作个标记。等到 OnAfterCreated() 被调用的时候,再向父窗口发个消息让它自己销毁,调 DestroyWindow() 就行。

经验教训是:一切还得按规矩办。自己想土办法,或许能在某些场合解决问题,但终究不是长治久安之策。
但是,虽然没有看或调试源代码,不过这次也算是刺探了一下 CEF 的一些内部运行机制,也是有好处的。

2025-12-22

丢失的 Blogger 铅笔小图标

图片由 Google Gemini 阅读本文后生成,真是厉害

之前在自己折腾 Blogger,大概就是 2023 年或 2024 年把  Blogger 重新翻出来写的时候,一不小心,把每篇 Blog 下面的「铅笔」小图标给整没了。

这个小图标是管理员才能看到的。直接一点就可以进到编辑界面。对于我这种时常在回顾自己文章时发现错别字,需要马上去修改的人而言,真的是很方便。把它弄丢了以后,我每次就得根据文章的标题或标签再去 Blogger 后台找到那篇文章。有时候某些想法只是一瞬间的事情,过了就没了,对于我而言,更可怕的是「过了就忘了」,这也太可怕了!

我一开始还以为是自己之前在编辑主题的时候不小心把它给误删了。但今天想起这事,下决心解决,去看代码的时候,才发现好像不是这么一回事。代码都在里面,但就是不起作用了。

这是原来的代码:

<b:includable id="postQuickEdit" var="post">
  <b:if cond="data:post.editUrl">
    <span expr:class=""item-control " + data:post.adminClass">
      <a expr:href="data:post.editUrl" expr:title="data:top.editPostMsg">
        <img alt="" class="icon-action" height="18" src="https://resources.blogblog.com/img/icon18_edit_allbkg.gif" width="18" />
      </a>
    </span>
  </b:if>
</b:includable>

看到这里我就有点犯嘀咕。cond 明显是条件,该不会是 post.editUrl 没东西吧?
我把 cond 改成一个必定成立的条件,放到页面上一试,还真是。图标出来了,点击没用处,没能跳到编辑页面。问 Google Gemini,它说新一些的主题里面这个图标的确已经被隐藏了。看来 Google 是铁了心要把这个功能干掉了。

我想自己整,但发现真正的编辑页面的 URL 好像有两个 ID 我不知道哪里能拿到。Google Gemini 给出的修改方法语焉不详,而且看起来也没有说到点子上。算了还是求助于传统的 Google 吧。

搜到的第一个贴子是 Google 自己的页面,Blogger 的 Support 论坛:
https://support.google.com/blogger/thread/242102130/editurl-the-url-of-the-edit-form-for-this-article-deleted

里面有提到这位大神,是一个法语的 Blog:
https://bloggercode.orbiona.com/2021/10/faq.html

后面有个英语 Blog 也提到上面那篇:
https://too-clever-by-half.blogspot.com/2022/08/the-case-of-missing-pencil.html

法语对我而言有不少困难,还好有 Google Translate。
我翻到解决方案一节就知道了:看起来那个法语大神给出的就是我想要的东西。
简单地说,把原来的那段代码替换为:

<b:includable id='postQuickEdit' var='post'>
  <!-- /2021/10/faq.html -->
  <b:with value='data:view.isPage ? "blog/page/edit/" : "blog/post/edit/"' var='path'>
    <span expr:class='"item-control " + data:post.adminClass'>
      <a expr:href='data:blog.bloggerUrl path (data:path + data:blog.blogId + "/" + data:post.id)' expr:title='data:top.editPostMsg'>
        <img alt='' class='icon-action' height='18' src='https://resources.blogblog.com/img/icon18_edit_allbkg.gif' width='18'/>
      </a>
    </span>
  </b:with>
</b:includable>

就可以搞定了。

原因据说是 Google 慑于第三方 Cookies 的限制,没法让前台(blogspot.com)和后台(blogger.com)在域名不相同的情况下还能把管理员的登录会话串起来,索性就把整个功能废掉了。

解决方案的原理也很简单:你不让我跳编辑页面,我就自己组 URL。知道 BlogID,也知道 PostID,还有什么做不到的呢?

当然,这里头还有一些诸如「管理员权限的判断」等小东西。按照「八二原则」,真正花时间的往往是在这些事情上。掌握了核心技术,路只走了 20% 而已。这位法语大神甚至已经把「页面」和「博文」的差别也搞定了。我就不折腾了,直接用她的吧。

照例吐槽一下:搜索结果中,简体中文的页面依然是一无所获,繁体中文有两篇讲到了这个事情,其中一篇涉及到了核心问题,不过最完整的解决方案还是法文以及英文的页面。简体中文在世界文化交流领域里面就是一个小角落,不管卖出了多少玩具电动车,都改变不了这个事实。不要再夜郎自大了。

2024-09-29

信创踩坑笔记

图片来自网络

1. 银河麒麟(Kylin V10)

银河麒麟算是好的,毕竟高仿 CentOS。看它的文档,似乎 V4 的时候还是 Ubuntu / Debian 系的,现在的 V10 已经转到 RedHat / CentOS 派系下了。

我们的程序基本都是静态链接,所以就还好。唯一动态链接的是 OpenSSL,这个在《编译自己的 CentOS7 OpenSSL 1.0.2u 动态链接库》这篇 Blog 中已经介绍过了。

1.1. ldconfig

说起来算个坑的,就是它的 SP2 版本的 ldconfig 无法正确读取被 patchelf 改过 SONAME 的库,说什么「已被截断」。搜了一下,说是属于 ldconfig 的 Bug。patchelf 改过 soname 的 so 库,里面的段顺序会变。但规范里面并没写顺序是有保障的,ldconfig 不应该自行假设顺序一定是那个样子。

有人说这是上游供应链(glibc)的 Bug。的确无论 SP3 的哪个版本都是好的。但我看 SP2 和 SP3 的 glibc 的版本也没看出来有什么变化,这里先存疑一下吧。
我的应对方案就是 SP2 下就不改 SONAME 了,难看就难看一些。

1.2. VMware

接下来这一个实际上应该放在最前面,不过只是因为我用了 VMware 来安装这些而已。不见得所有人都会遇到。

银河麒麟 V10 安装在 VMware 上的时候,显示分辨率默认是 800×600。在这种情况下,无法调整显示分辨率,因为「保存」按钮被挡在屏幕外面了。
解决方法是用 xrandr,例如:

xrandr -s 1440x900_60

最后一个参数是刷新率(60Hz)。有些 Blog 上说这里只能用 xrandr 列出来的选项,但其实未必,只要显示器支持就行。
另外,有些 Blog 上说这种方式只能临时调整,重启后效果就没了。的确如此,可是现在屏幕大了,你就可以用 UI 来正式调整显示分辨率了,不是吗?

1.3. 9090 端口

我们有个服务,默认启动在 9090 端口上。结果等产品要上线到生产环境的时候,运维人员去启动的时候得到了报错,一看才发现 端口被人占了
问题是我自己之前搭环境测试的时候,没有遇到这种情况。估计因为我的银河麒麟 V10 是「最小化安装」,而客户那边生产环境则没有这样做吧。

2. 达梦(DM8)

总的来说,在 Windows 上安装达梦 8 的过程要顺畅很多,没遇到什么问题。Linux 下就开始有坑。

2.1 ulimit

准备阶段的第一个坑是 ulimit 的问题,之前已经先写了 Blog,这里就只给出链接了《Linux 下修改 ulimit 不能完全生效的问题》。这个坑严格来说不算麒麟也不算达梦的问题,而且不见得所有人都会遇上。

2.2. cdrom

第二个坑是挂载光驱。挂上来之后没有 x 权限,但不知道怎么回事,用 -o exec 也挂不出来 x 权限。最后只好用命令行强制运行解决:

bash DMInstall.bin

接下来的安装倒是没出多大问题。我在 Linux 上安装达梦的目的只是为了抠出里面的 bin、include 和 drivers 目录来,用来制作 Python 和 PHP 的运行环境而已。所以后续运维的踩坑经验我就无法提供提供了。

2.3. Python

我们的 Python 运行环境是用 Conda 搭的,有 2.7 和 3.5 两个版本。按照达梦官方文档的说法(那文档写得不好,链接恕我不放了,自己搜吧),需要安装达梦客户端,然后再编译驱动。照着操作就可以,没什么大坑。

2.3.1. ldconfig

我是从一台安装了达梦客户端的 Linux 机器上把 bin、include、drivers 这三个目录抠了出来,设了一下 LD_LIBRARY_PATH,然后去 Conda 环境里面编译驱动。这样做是没问题。

但是当我把目录放 /etc/ld.conf.d 里面之后,一跑 ldconfig,sshd 直接挂掉了。数个 SSH 会话同时被踢,吓了我一大跳,还以为被黑客入侵了。

好在 tty 登录没有问题,看起来是 OpenSSL 导致了 OpenSSH 不能用了。一看达梦的 bin 目录,libcrypto.so 和 libssl.so 就这样裸躺在里面,服了。最后还是只能用回 LD_LIBRARY_PATH 方案。

2.3.2. 多线程

接下来我们尝试在 Python 环境中登录达梦数据库。有一个小坑:达梦的 dmPython.connect 会开子线程来进行登录。我们的 Python 环境在 Python 解释器跑起来后把 nproc 改成了 0,结果就 CoreDump 了。看过 CoreDump 里面的 CallStack 才明白原因。看起来 C 代码里面的异常捕获,dmPython 完全没有做嘛。抛个 Python 异常也好啊。

2.4. PHP

达梦的文档在这一部分有比较大的问题。
首先,有点语无伦次。无论是 yum 还是源代码编译,都是指的 PHP 的安装方式,但达梦的部分应该都是一样的。然而文档中却说得好像不一样。
其次,版本混乱。安装代码中同时有 7 和 5 的库,显然是更新文档的时候没有更新完整。
最后,其交代的技术细节存在着问题,至少与事实不符。我按照其中列出的 DPI 库准备的环境,PHP 的 PDO_DM 库完全加载不起来。

我想办法得到了一份正确的 so 依赖列表,比达梦文档中给出的要多,多了不少。主要是因为 libdmcalc.so 和 libdmdta.so 导致的。我觉得我直接给出我的 so 库列表并不是一个好主意,毕竟 DM8 的版本肯定也在不时地变化。更有用的是介绍我的方法。

我的方法也很简单:找个编辑器打开达梦的两个 PDO 的 so 库,然后就搜「.so」。ldd 中看得见的,这样能搜到。看不见的,那种用 dl_open 打开的,这样也能搜到。然后顺藤摸瓜就行。貌似 DPI 的那些 so 库都还不会用 dl_open。
当然,将来达梦如果对 so 的内容进行混淆或加密,那我就不敢说这样能行了。希望它不会犯这个蠢。

后面就还好了。我一度担心 OpenSSL 又惹事,但大概是我们的 PHP 环境比较简单,没惹出什么麻烦。我直接把它们塞进了一个 docker 镜像里面,这部分的工作成果总算是被固化了下来。

2024-09-14

Linux 下修改 ulimit 不能完全生效的问题

前言:本文只打算作为「技术笔记」来写,对关键的要点进行一下点拨就完事,没想写成教程。或许能让熟手解决麻烦,如果是新手的话,最好是先有相关的基础知识和经验。当然,直接喂给AI,让它嚼过再给你慢慢解释,可能也是一个办法。

这些天还是在折腾信创的事情。安装了银河麒麟 V10 SP2 的一个默认设置的版本,带了可视化界面,准备用于安装「达梦」。
达梦的安装手册写得有一些问题,缺了一些要点,不过对于有一定经验的人而言,还算能解决。手册中有一个要求,就是要把 ulimit 里面的 open files 数量调大。原始的 1024,看起来不太够用。

图片来自网络

对 Linux 特别是 CentOS7 熟悉的人,应该反应过来了。不要直接改 ulimit,应该去改 /etc/security/limits.conf:

* soft nofile 65535
* hard nofile 65535

改完之后,重新登录的账号就已经是新的 open files 上限了。简单重启一下就应该万事 OK 了。

可是,不,没有那么简单!(莫名其妙的英式中文)


在我看来,改完重启以后,对于非 root 用户似乎完全没有效果。
root 账号好像真的是 OK 了。但达梦的安装手册提到需要创建专用的 dmdba 账号用于 DBMS 的安装。我在可视化界面登录 dmdba 账号后,打开「终端」窗口,ulimit 还是 1024。

测试下来,发现:

  • ssh 上来的 session 有效果;
  • su 切换过去的账号有效果。

这说明对 /etc/security/limits.conf 的修改还是有效果的,只是不知道为什么在某些场合下不能生效。
百思不得其解。最后找到这篇 Blog,是博客园的。标题起得有点不好找:《systemd service 设置 limit,不生效问题》。它引用的原文的 URL 已经失效了,所以我还是得把要点直接再说一下。简单来说就是:

  1. 要修改用 systemctl 启动的服务的 ulimit,需要修改 /etc/systemd/system.conf
  2. 要修改从可视化界面登录的用户的 ulimit,需要修改 /etc/systemd/user.conf

实际上的情况比这个要复杂一些。第二点不是原文中的描述,是我自己试出来的。我目前也无意去成为一个 Linux 或银河麒麟方面的专家,所以就暂时不作更多的探索了。

另外,修改了 /etc/systemd/system.conf 以后,需要先重启 systemd:

systemctl daemon-reexec

然后用 systemctl 启动/重新启动的服务,才能用上新的 ulimit 设置。
毕竟咱们改的是 systemd 的配置文件,对吧。

2024-09-13

SSD散热铜片导致的故障

我的 HTPC,一开始真的只是打算用来当作 HTPC 用,所以 SSD 只有 256GB。后来儿子要玩游戏,因此很快就不够用了。于是大约两年前,我换了一条 aigo 的 NVMe M.2 SSD,P2000,1TB 的容量。不是什么好东西,不过因为我在公司里面用过同品牌同型号的产品,心中有点数(相比之下,aigo P3000 Pro 简直就是巨坑),所以也就跟着买了。

用到现在倒也都没有出什么问题,但散热真的是个问题。之前的 256GB SSD 因为有马甲,还不觉得散热有问题。但这块 P2000 是个「裸条」,我一开始复制大文件,比如电影,温度就往上窜。温度高到了一定程度以后,速度就掉下来了,后来还开始掉盘。于是我又去买了一个散热铜片,粘上以后好了很多,不再有散热问题了。

图片来自网络,非同款,非广告

这个散热铜片有单面款和双面款。我的 HTPC 是一个很小的 ITX 机箱,而且主板的 M.2 插槽在背面。如果贴双面,会不会接触到主板背面的针脚或元件?我的确有这个担心。看别人的评测,说还有一定高度,不会碰到。我自己试下来,也的确如此,于是就这样用了下去。快两年以来,都没有什么问题。

以上是前言。


最近,我一直在与这台 HTPC 的「关机」方面的顽疾作斗争。
一开始是开机以后自动关机,偶尔有之。最近则变成关机关不掉,关机变重启。重启有的时候来得很快,有的时候时间都说不清。比如昨天,我关了机器去上班,下午三点多,家里都没有人的时候,它自己开机了。要真是能远程唤醒也就罢了,可当我正经来设置的时候,又没法用。
于是昨晚我把 BIOS 设置给 LOAD DEFAULT 了。以前一直没能狠下这个心,终于世界清净了。

然而,今天早上,我再想开机的时候,发现机器开不起来了。

我绞尽脑汁地回想我到底做了什么导致的这个后果?
不是 BIOS 设置的问题,因为现在甚至不给我进 BIOS 的机会。CPU 风扇转一小会儿就断电停了。
拔掉内存,可以长期通电,可见不是电源问题。
CPU 烧了?过热保护?但从加电到断电的时间非常短,CPU 再热也不至于这么一小会儿都撑不住。何况风扇并没有停。不过保险起见,我还是把前不久换的散热器拆了,重新打了硅胶。还是没有效果。

把配件全拆了,就剩主板和电源,裸接,还是点不亮。那么,主板、CPU,还有就是背面的 SSD 了。
我很不报希望地把 SSD 卸了,再加电。这次亮了,进 BIOS 了。


带着难以置信的心情,我反复试了几次,能够确认就是 SSD 导致的问题。
难道是 SSD 坏了?就算不认盘,也不至于连 BIOS 也进不去吧?不过 NVMe 走的路线不同,或许会导致 CPU 故障类似的样子。
我把 SSD 拆下来,放在 NVMe M.2 移动硬盘盒上,用笔记本读。识别起来毫无困难,里面内容也没问题,看起来 SSD 没坏。我算是松了一口气。

好吧,唯一可以怀疑的,就是这个散热片了。我再次看了看主板背面的空间,感觉有可能接触到。身边没有趁手又够薄的绝缘片,于是我还是费了半天劲把散热片拆了下来。还好 P2000 没有缓存芯片,背面没有元件。而且上次换散热器的时候,送了一片用来刮硅胶的薄塑料片……

拆了这块粘在 SSD 背面的铜散热片以后,果然故障就消失了。虽然总算是解决了,但还得装回去,最后这一趟折腾总共花了我两个半小时,还一手的伤口。迷你,不是没有代价的。


复盘一下,多半是因为热胀冷缩的关系,铜散热片不知道怎么就跟主板背面的针脚或元件接触上了,于是主板直接短路保护了。我估计针脚的可能性大一些。之前我 HTPC 一直没关,基本上都是热机的状态,所以工况跟昨晚不太一样。

这块散热铜片,我是不打算装回去了。SSD 上的芯片都在正面,正面的散热铜片还在,背面只有 PCB 板而已。而且背面的散热铜片以前跟主板的间隙就很近,即使没短路,我觉得也不会有什么散热效果。
只是,早知道如此,当初还不如直接买单面的铜片,还便宜一些。

2024-09-10

把 Google Code Prettify 应用至 Blogger

图片来自网络

记得以前给自己的 Blogger 部署过 Google Code Prettify,不过后来可能是因为主题被重置过,效果没了。今天把它重新找了回来,日后也会逐渐套用至需要的 Blog 上。本文就作为相关内容的技术笔记吧。

部署

在 Blogger 后台中,点「主题背景」,然后进行「自定义」,去「修改 HTML」。
在 head 标签中放置:

<script src='https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js'/>

然后用下列标签把代码括起来,就可以实现本文中的代码块效果:

<pre class="prettyprint">……</pre>

pre 标签中的代码按原始格式存放即可,不过小于号(<)和大于号(>)仍然要进行转义。


以下是一些常用到的 option,更多的内容烦请移驾 官方Github页面

主题

在引用 run_prettify.js 的时候,URL 后面加一个参数 skin 即可。例如:

<script src='https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?skin=desert'/>

目前有五种主题,预览图点 这里

不标记为代码

用一组带 nocode 样式的 span 标签括起来即可,还可以加上自己的样式。例如

<span class="nocode" style="color: red;">注意</span>

语言指定

在 pre 标签中指定 lang-* 样式,例如:

<pre class="prettyprint lang-sh">
if [ ! -d '/myfile' ]
then
    mkdir /myfile
fi
</pre>
<pre class="prettyprint lang-cpp">
class A
{
public:
    int a;
}
</pre>

内置支持的语言有 ["bsh", "c", "cc", "cpp", "cs", "csh", "cyc", "cv", "htm", "html", "java", "js", "m", "mxml", "perl", "pl", "pm", "py", "rb", "sh", "xhtml", "xml", "xsl"],还有一些可以通过 extensions 进行支持,参见官网页面。

自动换行

有些代码有可能把屏幕宽度撑得很大。要想自动换行的话,在 head 标签中添加:

<style>
pre {
  white-space: pre-wrap;
  word-wrap: break-word;
  word-break: break-all;
}
</style>

这里的效果是按字母换行,可能会切断单词。如果要按单词换行,去掉 word-break 这一行。详细的解释参见我的另一篇 Blog《关于表格内文字换行的再研究》。

2024-09-09

编译自己的 CentOS7 OpenSSL 1.0.2u 动态链接库

图片由 Google Gemini 生成

因为老板想从「信创」分一杯羹,于是我们现在需要把一个 CentOS7 上面跑着的系统,移植到「银河麒麟」上去运行。

对于「国产」操作系统了解不多,不敢妄自确定方案。先去 下载 了银河麒麟的数个版本,主要都是服务器操作系统 V10,包括 SP2 的「CentOS 兼容版」,海光版,以及 SP3 2303 以及 2403 的海光版本。考虑到海光 CPU 也是 x86 架构,理论上应该可以跑在普通的 Intel / AMD CPU 的机器上,实际测试下来也是如此。

不得不说,我一开始对于情况设想得比现状要严峻一些。实在是没有想到银河麒麟 V10 对于 CentOS 抄得如此彻底,怪我太高看了国人的本领。无论是「CentOS 兼容版」还是海光版,我们在 CentOS7 上编译的东西,基本上拿上去就可以直接跑。
当然,从好处想,这样做的确提升了这些「国产」操作系统的兼容性,极大拓展了生态链。一定程度上也算是利国利民的好事情。除了钱以外的事情,我也就不吐槽了。

之所以说是「基本上」可以直接跑,是因为银河麒麟 V10 大概是从 CentOS8 开始「魔改」的,所以上面搭载的 OpenSSL 是 1.1.1 版本。有一些是 1.1.1c,有一些是 1.1.1f,总之不是 CentOS7 的 1.0.2。用 yum list 看了一下,源里面也只有 1.1.1 的版本可供安装。

我们的程序是在 CentOS7 上编译出来的,用了 so 版本的 OpenSSL 1.0.2,所以缺了这个东西还没法运行。尽管我们可以把自己的代码改成去链接 OpenSSL 1.1.1,或者索性静态链接进去,但还有些第三方厂商提供的库也是基于 OpenSSL 1.0.2 的,而且不算少。总之这个问题是绕不开的,只能迎头而上去解决它。
另外,我们发现那个「CentOS 兼容版」的默认安装会在 /lib64 下面形成 OpenSSL 1.0.2o 的库。yum list 里面还是没有,说明应该是某个软件包自行装上的。既然那个软件包可以这样做,我们也可以。把自己编译的 OpenSSL 1.0.2 的库放进系统里面去,然后 ldconfig 就好。

以上交代了背景,接下来就说说看具体怎么做,以及介绍一下踩过的坑。


编译 OpenSSL 很简单,从 OpenSSL官网 上下载 1.0.2u的tar.gz包(最后一个版本,1.0.2 已经停止维护了),解压以后编译:

wget https://openssl.org/source/old/1.0.2/openssl-1.0.2u.tar.gz
tar -xvzf openssl-1.0.2u.tar.gz
cd openssl-1.0.2u
./config --prefix=/usr/local/openssl-1.0.2u --openssldir=/usr/local/openssl-1.0.2u shared
make
make install

以上的操作,随处都可以搜到,AI 也能提供。不过编译出来的 openssl 动态链接库,有两个问题:

  1. 拿这两个编译出来的 so 库去用,会报「no version information available」。事实上 ldd 的时候就会报这个错。
  2. 编译出来的文件名是 libcrypto.so.1.0.0 和 libssl.so.1.0.0,而不是我想要的 libcrypto.so.1.0.2u 和 libssl.so.1.0.2u。

前者问题大一些,后者往小了说只是自己看着顺不顺眼的问题。一个一个解决吧。

问题1

拿着「no version information available」去搜,得到的信息不太具有参考价值。问 AI 也一样。
大多数的回答是说错用了别的操作系统编译的 OpenSSL 库,可能很多人遇到的情况也的确是这个,但显然我们遇到的情况并不是。

唯一找到 一篇相关文章,跟着评论找到了知乎上的 一篇文章。作者是想自己编译 1.0.2u 的库,替换掉 CentOS7 上的 1.0.2k 的版本。
顺带说一句:以前 CentOS7 还在维护,即使是 1.0.2k 的库,CentOS 也会补上安全漏洞。现在 CentOS7 不维护了,或许将有必要这样去做。但 1.0.2u 也已经是 2019 年发布的版本,换句话说,OpenSSL 1.0.2 在更早的时间就停止维护了。所以原文作者这样去做可能也没有什么意义。

该文作者的思路和实际操作都没有什么问题,尊重他人的劳动成果,我就不原样贴一遍了。需要的朋友可以根据上面的链接自己去看。虽然原文不知道为什么贴了 Markdown 的内容,在知乎上看不出样式,至少 PC Web 版上看不出,但内容总归能看懂。
我遇到的坑是:按照该文操作的「1.4」步骤得到的函数名称中有「@库名」的内容,例如:

sk_find@libcrypto.so.10;
sk_set@libcrypto.so.10;
asn1_add_error@libcrypto.so.10;
……

对于 Version Script,这是不可接受的写法。我的处理方法是把这里面的「@libcrypto.so.10」直接删掉了。原文假设所有的函数都是「@@」形式的默认导出,而不是「@」一个符号,这个假设是不对的。
这里是我从 CentOS7 中的 OpenSSL 1.0.2k 提取出来的 Version Script,既然 CentOS7 和 OpenSSL 1.0.2u 都已经停止维护,那这个文件应该算是 Stable 了。可以 点此下载

把下载得到的 version.map 放在解压生成的 openssl-1.0.2u 目录下。随后,./config 的时候换成下面这句:

./config --prefix=/usr/local/openssl-1.0.2 --openssldir=/usr/local/openssl-1.0.2 shared -Wl,--version-script=/root/openssl-1.0.2u/version.map -Wl,-Bsymbolic-functions zlib-dynamic

最重要的参数就是 -Wl,--version-script=version.map 这句。再编译生成的 so 库就不会被报「no version information available」了。而且这里似乎必须得写绝对路径。

问题2

直接改文件名是不行的。因为文件里面有 SONAME。做符号链接以及 ldconfig 的时候都会原形毕露。我们需要把文件内部的 SONAME 也一起改掉才行。

据说在编译前手动调整一些文件也可以解决这个问题。不过我是选择在编译之后用 patchelf 来解决这个问题的。为此当然首先要安装 patchelf:

yum -y install patchelf

然后就开始改文件了:

patchelf --set-soname libssl.so.1.0.2u libssl.so.1.0.0
patchelf --set-soname libcrypto.so.1.0.2u libcrypto.so.1.0.0
mv libssl.so.1.0.0 libssl.so.1.0.2u
mv libcrypto.so.1.0.0 libcrypto.so.1.0.2u
patchelf --replace-needed libcrypto.so.1.0.0 libcrypto.so.1.0.2u libssl.so.1.0.2u

最后一句也是必须的,因为 libssl.so 会引用 libcrypto.so。

2024-07-05

Dbgview 与小狼毫输入法冲突

今天在调试一段代码的时候,偷懒用了 OutputDebugString,于是想打开 Dbgview 看输出。双击了没反应,试了试 dbgview64,还是没反应。有点奇怪,心说是不是旧版本不能用了,看看 SysInternals 有没有新版本可以下载,点开 Chrome,发现 Chrome 启动也没反应了。心里有点慌,想开任务管理器看看到底怎么回事,结果任务管理器也打不开,这才真的有点发慌了。

一番折腾没有效果,包括找了别的电脑 RDP 远程桌面过来,也进不了桌面,还把主机卡在登录界面进不去了。还好右下角的重启按钮还是有反应,而且没让我等多久。
重启完以后,第一时间去开任务管理器,然后重来了一遍。之前以为是偶然现象,确实很久没重启了,但发现这次 Dbgview 还是卡住,还是只能重启。反复折腾了几次,都是如此。上网一搜,还真 有人遇到类似问题,一看「对方」——小狼毫。

图片来自网络

这和我的情况对上号了,我也在用这个小狼毫作为输入法。于是我把这个输入法的主进程 WeaselServer.exe 杀掉以后再去开 Dbgview,工作得非常正常。于是案子算是破了。

接下来我好好调查了一番这个事情。除了原 Github 贴子反馈的事情,我还发现了一些情况:

如果先有 WeaselServer.exe,再去启动 Dbgview.exe,Dbgview 就会卡住,界面出不来。如果事先开着任务管理器,就会看到 Dbgview.exe 进程出现,并未「无响应」,CPU 占用率也不高。此时如果杀掉了 WeaselServer.exe,系统会立刻恢复正常。杀掉 Dbgview.exe 也行,但系统恢复要慢上一点,似乎在等待着什么。

如果是先有 Dbgview.exe,再去启动 WeaselServer.exe,则会在 Dbgview 上看到大约 100 条左右的日志出现。多也没有很多,而且后续并没有更多的日志出现。
诡异而关键的一点来了:此时 DbgView 也能在界面上通过鼠标正常操作,只是不能碰键盘。如果一动键盘,哪怕仅仅单独碰了一下 Ctrl 或 Shift 键,都会导致 DbgView 无响应。

而只要 Dbgview.exe 开始卡住,接下来 Windows 里面就会无法新建进程。在别的已经启动的进程里面,也不能去碰键盘,一碰就卡。包括任务管理器,我前几次被迫重启,就是栽在这里——我实在是太喜欢用快捷键了。

在测试时,有碰到过一次 Process Explorer 能够「例外」,也就是说操作键盘不会被卡住。但接下来我想要再重现,发现它的「防护结界」失效了。所以目前还算是没找到规律。

这问题看上去是 Dbgview.exe 和 WeaselServer.exe 这两个进程在键盘消息的处理方面有所冲突。原 Github 贴主提到的「过滤掉“.cc:”相关日志」的临时解决方案,并不能解决我遇到的问题。我试过,即使让 DebugView 停止 Capture 也不行。不过,只要注意启动顺序,并能忍住不要去用键盘,就还暂时可以让它们俩一起工作,相安无事。

我不知道作者会不会去修正这个问题,以及什么时候修正。有点担心,恐怕他缺乏足够的动力和精力去做这种事情。现在真的没什么好输入法可以用了么?


最后的更新:2024 年 08 月 20 日,我在 Gmail 上收到 Github 的动态。一句「无法重现」随后便把这个 issue 给关闭了。我其实是很愿意配合的,但是对方大概已经下定了决心,只能遗憾了。

2024-07-03

从 KuaiCheDao 搬迁至 Nube.sh

之前为了让太太看 Netflix,经人介绍在 KuaiCheDao 上买了一个 SJC 的 VPS。价格不算非常便宜,但也还好,而且「质量」的确不错。带宽和速度都不错,当时说是家宽,用下来感觉跟其它 VPS 提供商的东西的确有些不一样。自己的 VPS,IP 地址比较爱惜,后续很多时候都派上了不小的用场,包括 AI。

前些天,收到了邮件,说这个 VPS 即将关闭,在 7 月后就没法续费了。替代方案是迁移去 Nube.sh。
说起 Nube.sh,我知道是同一个老板折腾出来的东西,想从 VPS 变成 Cloud,也就是向 Vultr 之类看齐,貌似费了不少劲。之前他一直没弄好 Nube.sh 的 SJC 地域,我也就没去关注。现在要强制迁移 KuaiCheDao 上的客户,想必是已经搞定了。于是周末我就去尝试把 VPS 迁移了。

图片来自网络,经过格式转换

先说说这个 Nube.sh 本身吧。

打开页面,就直接提示我用 Google 账号登录。我懒得注册,直接允许了,确实比较省事。
控制台比较简单,功能或许尚算够用。地域的选择位于页面上方中部,感觉属于框架的部分。我一开始没看到,疑惑了半天结果还是下错了单。而且一旦下单就立即开始部署,说起来也算是太过「流畅」,但这种既没有「确认」界面,而且页面下方还有用户没明确选择的项目也没给提示的 UI 设计,结果就是我稀里糊涂 $0.02 一键就出去了。这个必须吐槽一下。

老板给了 $0.2 的授信额度,可以用于摸索和测试,包括测试 IP 和网络的情况。基本上这个额度够用了。
最小的实例一小时才 $0.0027,月租不到 $2,额外的 IP 和存储费用还不到 $0.3。流量每 GB 只要 $0.0009,按原来 KuaiCheDao 的额度 2TB 计算相当于 $1.8。而且 Nube.sh 这边算 Outgoing 不算 Incoming,反正比阿里云便宜多了。
当然,现阶段价格有折扣。恢复正常以后,按照 KuaiCheDao 我原来的 VPS 规格算下来大约是一个月 $5 不到。相比起来,费用方面还是会更便宜一些。不过可想而知,老板肯定能更方便地超售。

IPv6 用起来貌似没遇到太多问题,目前能正常用。KuaiCheDao 那边曾经遇到过问题,而且修好以后还有过反复,后来我索性把 IPv6 给禁用掉了。
拿到的 IPv4 地址也看了一下。type 也是 hosting,不是 isp,有些遗憾。虽然 VPS 肯定是在 SJC,但 IP 是香港的 Cogant,不过用 ipinfo 去看确实是在 SJC 没错。
我 KuaiCheDao 的 VPS 地址印象以前中是 isp,现在发现也变 hosting 了。用起来倒是还没什么感觉,反正太太现在更多的都是看我下载到本地的片子,而不是在线看 Netflix 了。试了一下 ChatGPT 可以用,那就还算 OK。

测试完成后,用银联卡充了 $10。VPS 部署完先放了起来,等我 KuaiCheDao 到期后就把线路切换过去。
只希望这个 Nube.sh 不要像 KuaiCheDao 那样被人攻击。

2024-07-01

微软更新弄丢了我的VS2013窗口布局

早上来公司,打开电脑显示屏,发现似乎有些不一样。周五下班时挂着在运行抓 Dump 的一个程序,不见了。

心中一喜,还以为抓住 Dump 了。再一看,Visual Studio 不对劲。心一沉,去 Windows 日志里面一看,果然今天凌晨一点多的时候,机器被重启了。再去看 Windows 更新日志,原来今天凌晨安装了 KB5039299,然后就被微软自动重启了。

微软还挺贼,重启完了以后,还把我的应用程序都给「恢复原状」。包括像 Chrome 和 Visual Studio。可里面的工作状态终究是没法真的恢复,而且把虚拟机也给我关了,简直气死我了。我已经想尽了办法阻止它自动重启,而且也禁止了 Windows 在登录时重启应用,可这货在这一点上简直比中国 AI 还蠢,横竖不听,我行我素。

别的我就忍了,大不了当作周末断过电。可微软还干了一件出格的事情:它把我的 VS2013 的窗口布局给重置掉了。我一共开了 6 个 VS2013,其中 5 个都被重置过了。我小心翼翼地最后关闭那个看上去没被重置的 VS2013 窗口,然而没用,新打开的都是那种资源管理器在右边的布局形态。

记得以前也处理过类似的事情,后来发现有捷径。在网上搜了一阵子,总算捡回了记忆。

工具 -> 导入和导出设置
从「工具」菜单中选择「导入和导出设置」,然后选择「导入选定的环境设置」,再接下来选择「否,仅导入新设置,覆盖我的当前设置」。
最近没有备份过,但我对工作环境并没有什么特别的定制,所以重新选 Visual C++ 就好。如果对于习惯的开发环境特别在意,平时还是注意导出并备份吧。保存路径在自己动手的过程中就可以看到。

还是更习惯 VC++ 的传统窗口布局
我还是更习惯传统的资源管理器在左边的那种 VC++ 的窗口布局方式,可能也有一些老筒子跟我是一样的习惯吧?

每个周一都很忙,真希望微软不要再添乱了。

2024-06-20

Nginx 更新遇到 GPG 签名变更

图片来自网络

这些天对自己的 Debian 系统进行 apt update 的时候,总是遇到报错:

W: An error occurred during the signature verification. The repository is not updated and the previous index files will be used. GPG error: http://nginx.org/packages/mainline/debian buster InRelease: The following signatures were invalid: EXPKEYSIG ABF5BD827BD9BF62 nginx signing key <signing-key@nginx.com>

W: Failed to fetch http://nginx.org/packages/mainline/debian/dists/buster/InRelease The following signatures were invalid: EXPKEYSIG ABF5BD827BD9BF62 nginx signing key <signing-key@nginx.com>
W: Some index files failed to download. They have been ignored, or old ones used instead.

我的 Nginx 是按照 官方指引 从来源安装的 mainline 版本,不是直接用的 Debian 的 apt 源内置的 Nginx。Debian 的 Nginx 我嫌它版本低了。

当时瞄了一眼,知道是说 GPG 的签名出了问题。这种问题可大可小,如果是之前的版本被人植入了恶意代码,就像之前的 XZ/liblzma后门事件,那么一整个源可能会被撤销。
但我也看到了「EXP」字样,所以似乎只是简单的 Key 过期而已。并且同期并没有大的信息安全新闻爆出,不像是安全事故。鉴于我当时的时间紧张,并没有当场去处理它,反正 Nginx 更新得并不是很频繁。

今天花了点时间来仔细看了一下,果然是 Nginx 的 PGP Key 在 6 月 14 日过期了。Nginx的官方Blog 登载了 这一则通知,同时也提供了更新的方法。
按照官方的指引,只需要执行下列命令即可:

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | sudo tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

如果你是像我一样在 root 下面操作,并且没有安装 sudo,那么去掉 sudo 就好:

curl https://nginx.org/keys/nginx_signing.key | gpg --dearmor | tee /usr/share/keyrings/nginx-archive-keyring.gpg >/dev/null

更新完 Key 以后,可以用下列命令来验证新 Key 的过期时间:

gpg --dry-run --quiet --no-keyring --import --import-options import-show /usr/share/keyrings/nginx-archive-keyring.gpg

据 Nginx 这篇 Blog 中称,今后将以大概每两年一次的频率对他们的 PGP Key 进行更新。果然这次得到的新 Key 的到期时间就是 2027-05-24。


2024-06-18

Proxmox VE 更新出了问题

图片来自网络

在公司用 Proxmox VE 搭了一个「超融合」环境,开了一些 VPS 给同事用。因为没打算花钱,所以一直用的 未订阅方式 进行更新。

今天跑 apt dist-upgrade 的时候,遇到一大串内容:

root@pve:~# apt dist-upgrade
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Calculating upgrade... Done
The following package was automatically installed and is no longer required:
  proxmox-kernel-6.8.4-3-pve-signed
Use 'apt autoremove' to remove it.
The following packages will be REMOVED:
  proxmox-ve pve-manager
The following packages have been kept back:
  pve-container
The following packages will be upgraded:
  libpve-cluster-api-perl libpve-cluster-perl libpve-notify-perl libpve-rs-perl
4 upgraded, 0 newly installed, 2 to remove and 1 not upgraded.

我也没有细看,一个回车按下去,报了一些错:

Do you want to continue? [Y/n] y
W: (pve-apt-hook) !! WARNING !!
W: (pve-apt-hook) You are attempting to remove the meta-package 'proxmox-ve'!
W: (pve-apt-hook) 
W: (pve-apt-hook) If you really want to permanently remove 'proxmox-ve' from your system, run the following command
W: (pve-apt-hook)    touch '/please-remove-proxmox-ve'
W: (pve-apt-hook) run apt purge proxmox-ve to remove the meta-package
W: (pve-apt-hook) and repeat your apt invocation.
W: (pve-apt-hook) 
W: (pve-apt-hook) If you are unsure why 'proxmox-ve' would be removed, please verify
W: (pve-apt-hook)    - your APT repository settings
W: (pve-apt-hook)    - that you are using 'apt full-upgrade' to upgrade your system
E: Sub-process /usr/share/proxmox-ve/pve-apt-hook returned an error code (1)
E: Failure running script /usr/share/proxmox-ve/pve-apt-hook

我这个笨蛋还是没有细想,按照提示操作了,随后重启了这台宿主机。
然后上面的 VPS 就启动不起来了。既没有自动启动,也无法打开 Web 控制台进行操作。netstat 一看,HTTP 端口监听没有了。

心里慌了,连忙上网搜,才发现 好多人都在叫唤。只不过我是其中心比较大,真的操作了的那个白痴。更多人是在报错那步停下来了。

这下怎么办呢?翻了下日志,看到 proxmox-ve 和 pve-manager 被 remove 了。我想把 proxmox-ve 给装回来,又有报错,看来不行:

root@pve:~# apt install proxmox-ve
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
Some packages could not be installed. This may mean that you have requested an impossible situation or if you are using the unstablebr distribution that some required packages have not yet been created or been moved out of Incoming.
The following information may help to resolve the situation:

The following packages have unmet dependencies:
  pve-container : Depends: proxmox-backup-client (>= 3.2.5-1) but 3.2.3-1 is to be installed
E: Unable to correct problems, you have held broken packages.

还好,讨论帖 里面有人给出了抢救措施:

apt install pve-manager=8.2.3

操作以后,Web 控制台出现了。我进去启动了 VPS,用起来没有什么问题。
先这样吧,接下来就是等官方消息了。
我们应该都是没花钱的用户,也许官方不一定会有修正?

-- 更新于 2024.06.19:

官方已经为未订阅 apt 源 修正了这个问题

2017-12-27

Linux 关机权限的特殊情况

很多 Linux 教程都说 shutdown / reboot 需要 root 权限。其实这不是完全正确的。

正常想来没错:shutdown / reboot 如果可以不用 root 权限那还了得?然而其实至少在 RHEL 7.2 上并不绝对是这样子。当出现以下的特殊情况时:
  1. 是当前唯一登录的用户;
  2. 直接调用 reboot,或 shutdown 带了 now 参数;
  3. 登录会话来自本地物理终端。
同时满足以上所有条件的话,就可以无需 root 权限以任何用户的身份关机或重启服务器。

我用 RHEL 7.2 默认安装的「基础设施服务器」进行的测试。Debian 9 没有这个问题。我想这样的做法大概思路是:「如果你都摸到物理服务器边上,急着要马上关机,并且我认为这不会影响到其他人,那么当然可以让你关机,因为就算不让你关机你也可以拔电源线对吧?」

当然,这种想法应该没有考虑到服务器上运行爬虫之类的情况。我没有做更多测试,也许如果有一个别人的 daemon 进程就会使得结果不一样。不过我想这个事情可以提醒我们的是:Linux 的事情不要想当然,也不要网上说什么都信。不要把一切都交给系统默认安全性设置,对于普通用户还是乖乖地把权限控制严格点比较好。

2017-05-22

VMware + Ubuntu 声卡失效事件

在公司和在家里,都用 VMware 各自安装了一台 Ubuntu 14.04 LTS 来玩。在家里的一台用得没有什么问题。在公司的那一台,周末打算加班的时候装个网易云音乐来听歌的时候,发现没有声音,才注意到 VMware 上有一条报错提示:
使用的设备标识号已超出本地系统范围。 声音将中断。
公司电脑上没有接音箱,所以以前曾经禁用过宿主机的 Windows Audio 服务。我以为是这个原因,去看了一下,Windows Audio 服务现在是启用中。把宿主机重启过,故障依旧。于是循例开始 Google。

Google 上搜到的中文内容,主要分为两派。一派说把 pulseaudio 卸载了就好了。我半信半疑地 apt-get remove pulseaudio 之后,嘿,还真的可以播放出声音了。不过更大的问题来了:系统设置丢了好多图标。回想起 apt-get 提醒我说要卸载掉的东西有一大堆,看来依赖于 pulseaudio 的东西不少。这条路应该不是什么正路。真是还好 VMware 有快照。

另一派说把宿主机上的立体声混音设备启用,故障就解决了。附和的人不少,看来有不少人都是这种办法解决的。具体页面有很多,随随便便就能搜到,我就不给出了。然而当我按照附带截图的操作指南去做的时候,问题又来了:我根本没有立体声混音设备。

这是怎么回事?我这人也不习惯卖关子。要说还是英文信息有用。英文页面上也有少数几个人抱怨遇到与我同样的问题(中文页面上我没有看到过)。最后还是 VMware 官方社区给出了有用的 解答

要简单解释一下的话,其实就是这么一回事:我这个宿主机上的 Win7 当初装起来之后,偷懒没有安装 Realtek 官方的声卡驱动程序,而是直接 Windows Update 安装了微软给出的驱动。估计微软的这个驱动是个阉割版,缺一些东西,装了之后虽然使用起来没什么问题,但像我这次遇到的什么立体声混音设备,大概就是被阉割掉的内容之一。所以 VMware 找不到指定的设备,于是就没法让 Ubuntu 中的声音设备正常工作了。

总之,按照 VMware 官方解答的指引,我去 Realtek 官网上下载并安装了声卡驱动,现在 VMware 里面的 Ubuntu 可以欢快地播放音乐了。

2017-05-21

正确地获取 Windows 的版本号

以前,想要获取 Windows 的版本号很简单,有个 Win32 API 函数名字叫做 GetVersion,望文生义,接下来要做的事情就是去 MSDN 上查下用法就可以了。

现在,GetVersion 会被报告成「过期函数」了。也许还能用,但(据 MSDN 说)起码在 Win10 上是别指望得到预期的结果了。道理也很简单,Win10 都搞滚动升级了,版本号规则肯定也和之前不一样了,你还指望这么老的函数能兼容么?

别痴心妄想了,GetVersionEx 也一样过期。那么,眼下有什么好办法吗?

一般来说,拿 Windows 版本号可能有两种用途:

  • 我想看看你 Windows 版本达到我要求没。
  • 我就是想知道你 Windows 版本号是多少。

对于前者,微软现在在 MSDN 上是这样推荐的:它做了一组 Version Helper functions,你如果想知道当前 Windows 的版本是不是某个特定的发行版,调这组函数就可以。我们来看看这组函数中三个典型:

  • IsWindowsXPOrGreater
  • IsWindowsXPSP3OrGreater
  • IsWindowsServer

不需要更多说明,我们从名字中就可以看出,这组函数可以用于判断 Windows 的大版本,Service Pack 的版本(结合大版本),以及能知道是不是服务器版操作系统。通常情况下,这些函数大概是够了。

但是,有的时候我们并不关心版本号高低,我们只是想要一个版本号(例如记录日志时)而已。微软对此的建议是:用 GetFileVersionInfo 去获取一个系统 DLL(例如 Kernel32.dll)的文件版本号(原文看 这里)。

相关的代码虽然能找到,MSDN 上也有官方例子(有点小 Bug),但比起一行 GetVersion 来代码量实在是不能算很少。由此可见,处理「过期函数」真的没有想象中那么容易。最后我还是提供一下我从项目代码中挖出来的一个实现吧。别照抄,如果你不想引入 STL 的话:

#include <windows.h>
#include <Strsafe.h>

#pragma comment(lib, "Version.lib")

// 获取文件版本
std::wstring GetFileVersionString(const std::wstring& strFilePath, bool bStrVer = false) {
    DWORD dwVerInfoSize = GetFileVersionInfoSize(strFilePath.c_str(), nullptr);
    if (dwVerInfoSize) {
        std::vector<BYTE> vecVerData(dwVerInfoSize);
        if (GetFileVersionInfo(strFilePath.c_str(), NULL, dwVerInfoSize, &vecVerData[0])) {
            LPCVOID pBlock = &vecVerData[0];

            UINT cbTranslate;
            TCHAR SubBlock[MAX_PATH];
            struct LANGANDCODEPAGE {
                WORD wLanguage;
                WORD wCodePage;
            } *lpTranslate;

            // 阅读语言和代码页列表
            VerQueryValue(pBlock,
                L"\\VarFileInfo\\Translation",
                (LPVOID*)&lpTranslate,
                &cbTranslate);

            if (bStrVer && lpTranslate) {
                // 读取第一种语言和代码页的文件版本
                for (size_t i = 0; i < (cbTranslate / sizeof(struct LANGANDCODEPAGE)); ++i) {
                    StringCchPrintf(SubBlock, sizeof(SubBlock) / sizeof(TCHAR),
                        L"\\StringFileInfo\\%04x%04x\\ProductVersion",
                        lpTranslate[i].wLanguage,
                        lpTranslate[i].wCodePage);

                    LPVOID lpBuffer = nullptr;
                    UINT dwBytes;
                    if (VerQueryValue(pBlock, SubBlock, &lpBuffer, &dwBytes) && lpBuffer && dwBytes > 0) {
                        std::wstring strVersion(reinterpret_cast<TCHAR*>(lpBuffer));
                        return strVersion;
                    }
                }
            }

            // 未找到任何字符串版本
            VS_FIXEDFILEINFO* lpffi = nullptr;
            UINT uLen = 0;
            // 注意:这里的第二个参数 "\" 是固定写法,表示查询根块
            if (VerQueryValue(pBlock, L"\\", (LPVOID*)&lpffi, &uLen) && lpffi && uLen >= sizeof(VS_FIXEDFILEINFO)) {
                std::wstringstream wos;
                wos << HIWORD(lpffi->dwFileVersionMS) << L"." << LOWORD(lpffi->dwFileVersionMS) << L"."
                    << HIWORD(lpffi->dwFileVersionLS) << L"." << LOWORD(lpffi->dwFileVersionLS);
                return wos.str();
            }
        }
    }
    return L"";
}

// 获取 OS 版本信息
std::wstring GetOSVersion(const std::wstring& strWinSysDir, bool bStrVer) {
    std::wstring strWinSysFilePath = strWinSysDir;
    if (!strWinSysFilePath.empty() && strWinSysFilePath.back() != L'\\') {
        strWinSysFilePath += L'\\';
    }
    return GetFileVersionString(strWinSysFilePath + L"Kernel32.dll", bStrVer);
}

2017-05-20

SDL 检查不报错事件调查报告

Visual Studio 2013 VC 项目默认是启用 SDL 检查的。通常而言,这会使得一些「过期」函数在编译时被报告 Error。比如 strcpy 和 inet_addr 之类都会遇到这个问题。

理论上讲,这些过期函数的确不安全,或者说容易被不安全地调用。微软也很「贴心」地在编译器的报错信息中给出了解决方案,比如用 strcpy_s 和 InetPton 来替换,都不用你去搜解决办法了。所以按照我的习惯,一般是就地解决掉这些问题再往下走。

不过呢,可能有的人性子比较急,也有的时候是从旧项目移植,想快点编译完先跑一下看看效果。改代码毕竟要时间,从 strcpy 改成 strcpy_s 可能还好,而从 inet_addr 改到 InetPton 就真的没有想象中那么轻松。所以编译器也给出了另一种建议,你可以设上几个 Macro,SDL 也是可以被忽略的。

到此为止都还比较和谐,大家都是有商有量地做事情。然而当我腾出时间准备把过期函数扫扫干净,把同事临时加的 Macro 去掉之后一编译,问题来了——SDL 这次怎么不报错了?

再三确认过 vcxproj 中已经没有了相关的 Macro,并且 SDL 的确是打开了。但这次编译就是不会报错,似乎对我面前的 strcpy 视而不见。为什么呢?

Google 上搜了一大圈也没有方向。最后还是被我排除法硬试出来的——平台工具集如果选了 v120_xp,那么 SDL 即使打开,有些过期函数也不会报错。是不是 SDL 就此失效,我不清楚,因为我没法去覆盖所有的过期函数。我估计,微软是这样想的:你既然打算让这个程序跑在过期的 OS 上,那函数过期不过期已经不重要了。

其实还有一个更重要的原因:strcpy_s 还好,但要是把 inet_addr 真的换成了 InetPton,你就会发现在 WinXP 下你的程序根本就跑不起来。实际上,WinXP 就不支持 InetPton。MSDN 上的信息表明,最低也要 Vista 才可以。

我们的程序暂时还不能抛弃 WinXP 用户,但若要完全不知道用了哪些过期函数我又心有不甘,于是我打算做一个 #if……#else……#endif 来解决这个过期 OS 兼容的问题。搜了一下,正确的姿势应该是:
#if (_WIN32_WINNT >= 0x602)
#else
#endif
最后我是在 Debug 版本上用了平台工具集 v120,在 Release 时还是用了 v120_xp。过期函数只会局限在以上 Macro 范围内,有限度地使用。

另外,WinXP 也不支持条件变量 CONDITION_VARIABLE,所以这个服役期超长的操作系统是真的应该淘汰了。我不得不说一句:WannaCry,干得好!

2017-05-18

当 Win7 Windows Update 遭遇 0x80073712

Windows Update 一直以来都以会遇到各种 Error 代码而闻名。今天又遇到一例,记录一下。

起因是 WannaCry。我有一堆各种 OS 版本的虚拟机,其中一台 Windows7 SP1 x86 使用得很不频繁,昨天打开一看,上次 Windows Update 已经是 2016 年 09 月的事情了。虽然 NAT 挡在宿主机后面其实不会有啥问题,但是按照我的习惯,下班前还是让它去打了补丁。曾经在上一家公司的遭遇一直在提醒我:有人的虚拟机中了震荡波,然后不知情的时候被做了快照,于是每隔一段时间测试机房就会忙活一阵子(测试机为了测试程序的补丁管理功能是不打补丁的)。

今天早上一来,红色儿的,4 个成功 2 个失败。我也没太放在心上,公司网络有时候会断,说不定是下载失败。再来了一次,在下载到 11% 的时候又失败了。我把 VPN 开起来(曾经有不开 VPN 打补丁会下载失败的经历),上了个厕所回来,然而这次还是失败,我看了下 ErrorCode:80073712。每次都是这个。好吧,开始 Google

官方网页 推荐的做法大概是这样的:对于 Win7 而言,首先请先尝试用 SFC 修复一下。如果还不行,那么请下载工具 System Update Readiness tool 进行修复。

SFC 这货其实没啥鸟用,反正我每次用都没啥好结果。这次也不例外,扫描到 44% 时告诉我:虽然我们发现有错,但是无法修复,你去看日志吧。

试了下再次 Windows Update,还是报 0x80073712。好吧,只好试试看那个修复工具了。下载下来两百多 MB,安装了老半天。再次 Windows Update,这回进度开始超过 11% 了,我长舒一口气。终于 OK 了。

顺便瞄了一眼同页面上对 WinXP 的问题处理建议,仅仅提到 SFC。看来真的是该放弃这破烂了。