<?xml version="1.0" encoding="UTF-8"?><feed xmlns="http://www.w3.org/2005/Atom"><id>https://blog.tusooa.xyz</id><title>编码 - 何事西风不待人</title><link href="https://blog.tusooa.xyz"/><subtitle>迷糊萝莉</subtitle><updated>2021-02-04T19:38:47.000Z</updated><entry><id>https://blog.tusooa.xyz/2021/02/04/From-a-weird-Dolphin-bug/</id><title>From a weird Dolphin bug</title><link rel="alternate" href="https://blog.tusooa.xyz/2021/02/04/From-a-weird-Dolphin-bug/"/><published>2021-02-04T19:38:47.000Z</published><content type="html">&lt;div&gt; &lt;p&gt;One day when I opened Dolphin I found that it is reporting the file does
not exist when I tried to open one with a Chinese file name. &quot;Well, that&apos;s
bad,&quot; I thought, and instead opened it in the terminal. Recently I went
through the bug list for Dolphin and KIO and saw that encoding-related
bugs are all mentioning that file cannot be trashed. Which is not the case
for me. So I opened a &lt;a href=&quot;https://bugs.kde.org/show_bug.cgi?id=432406&quot;&gt;new bug&lt;/a&gt; and started investigating it.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;某日我打开 Dolphin，发觉它在我试图打开一个文件名带中文的文件的时候告诉我那文件不存在。
「行吧，太差了。」我想着，然后在终端里打开了那个文件。最近我遍览了 Dolphin 和 KIO 的
bug 列表，看着编码相关的 bug 全在说文件不能被扔垃圾桶。而我这里却不然。所以我开了个
&lt;a href=&quot;https://bugs.kde.org/show_bug.cgi?id=432406&quot;&gt;新 bug&lt;/a&gt; 开始钻研它。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;A note: if your Qt is not configured to use icu, then this does
not apply to you.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;提示：如果您的 Qt 没有被配置为使用 icu，那么这事对您便不适用。&lt;/p&gt; &lt;/div&gt;
&lt;a&gt;&lt;/a&gt;
&lt;h1&gt;&lt;div&gt; &lt;span&gt; From Dolphin to KIO &lt;/span&gt;&lt;span&gt; 从 Dolphin 到 KIO &lt;/span&gt; &lt;/div&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;So I checked Dolphin&apos;s code, found that the actual code for opening a file
&lt;a href=&quot;https://invent.kde.org/system/dolphin/-/blob/24f04d75e17010ff50fcacaf2a602f4a08222449/src/dolphincontextmenu.cpp#L498&quot;&gt;is with KIO&lt;/a&gt;. The implementation
&lt;a href=&quot;https://invent.kde.org/frameworks/kio/-/blob/726cfd722a20ec6afdfcbc045621c1f67564c391/src/widgets/kfileitemactions.cpp#L422&quot;&gt;is in &lt;code&gt;KFileItemActions&lt;/code&gt;&lt;/a&gt;. However, that seems not to
be the real source of error. The KFileItemList there is already wrong,
with all non-ascii characters turned into question marks. Further I looked
into the logic of obtaining the content of a directory: KIO invokes a
plugin called &lt;code&gt;file.so&lt;/code&gt;, for local filesystem, and that, which is actually
a kdeinit program, run as another process, does the real work. Then the
data is transferred back to Dolphin using a QDataStream. And in &lt;code&gt;file.so&lt;/code&gt;
it seems that everything looks correct.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;所以啊我就查了查 Dolphin 的代码，发觉真正打开文件的代码
&lt;a href=&quot;https://invent.kde.org/system/dolphin/-/blob/24f04d75e17010ff50fcacaf2a602f4a08222449/src/dolphincontextmenu.cpp#L498&quot;&gt;在 KIO 里&lt;/a&gt;。实现
&lt;a href=&quot;https://invent.kde.org/frameworks/kio/-/blob/726cfd722a20ec6afdfcbc045621c1f67564c391/src/widgets/kfileitemactions.cpp#L422&quot;&gt;在 &lt;code&gt;KFileItemActions&lt;/code&gt; 里&lt;/a&gt;。然而，那好像并不是
真的错因。那里的 KFileItemList 已经错了，非 ascii 的字符全变问号了。再往下，我
看了看获取目录内容的逻辑：对于本地文件系统，KIO 调用了一个唤作 &lt;code&gt;file.so&lt;/code&gt; 的插件，
而那插件实际上是个 kdeinit 程序，作为另一个进程，做了实事的了。然后数据通过一个
QDataStream 传到 Dolphin 里。在 &lt;code&gt;file.so&lt;/code&gt; 里，一切看着都很对。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;Why is that? Is it the QDataStream is bad? I looked into the
code for how QDataStream transfers strings and, no, it cannot be.
Also I noticed that the filename is displaying correctly in
Dolphin, just I cannot open it. I got some support on the #kde-devel
channel, and someone reminded me of the &lt;code&gt;LegacyCodec&lt;/code&gt; in &lt;code&gt;file.so&lt;/code&gt;.
That is not the cause of the problem, of cource, as in there it is
nothing wrong. But it reminded me of the &lt;code&gt;QTextCodec&lt;/code&gt; problem. There
is something Qt used to convert its UTF-16 representation into other
encodings. And that is called &lt;code&gt;QTextCodec::codecForLocale()&lt;/code&gt;. An
observation is that in &lt;code&gt;file.so&lt;/code&gt;, the codec name is called &lt;code&gt;UTF-8&lt;/code&gt;;
while in Dolphin, it is called &lt;code&gt;US-ASCII&lt;/code&gt;, unless I set &lt;code&gt;LC_ALL&lt;/code&gt;
to a UTF-8 locale.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;为啥啊？QDataStream 坏了吗？我看了下 QDataStream 是怎么传字串的，啊这，
不可能错的。又注意到文件名在 Dolphin 里显示啥问题都没有，只是我打不开。
在 #kde-devel 上得到了些帮助，有人提醒我 &lt;code&gt;file.so&lt;/code&gt; 里有一 &lt;code&gt;LegacyCodec&lt;/code&gt;。
当然这不是真的错因，因为那边没啥问题啊。但是这教我注意到 &lt;code&gt;QTextCodec&lt;/code&gt; 的问题了。
有个玩意儿是 Qt 用来把它自己的 UTF-16 表示法转成别的编码的。那玩意儿叫
&lt;code&gt;QTextCodec::codecForLocale()&lt;/code&gt;。观察到 &lt;code&gt;file.so&lt;/code&gt; 里的 codec 名字叫
&lt;code&gt;UTF-8&lt;/code&gt;；跑 Dolphin 里它变成 &lt;code&gt;US-ASCII&lt;/code&gt; 了，除非我把 &lt;code&gt;LC_ALL&lt;/code&gt; 设成一个
UTF-8 的 locale。&lt;/p&gt; &lt;/div&gt;
&lt;h1&gt;&lt;div&gt; &lt;span&gt; icu and Qt bugs &lt;/span&gt;&lt;span&gt; icu 和 Qt 的 bug 们 &lt;/span&gt; &lt;/div&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;I digged
into the code for that, and it turns out that it ultimately uses
&lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; to obtain the name of the encoding.
That function is from &lt;a href=&quot;http://site.icu-project.org/&quot;&gt;icu&lt;/a&gt;. In their bug tracker, I found
&lt;a href=&quot;https://unicode-org.atlassian.net/browse/ICU-12789?jql=text%20~%20%22ucnv_getDefaultName%22&quot;&gt;one&lt;/a&gt; that says:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;You may need to call &lt;code&gt;setlocale(LC_ALL, &quot;&quot;);&lt;/code&gt; from your own code before ICU.&lt;/p&gt;
&lt;/blockquote&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我钻进代码里一看，它用了个叫 &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; 的去获取编码的名字。
这函数 &lt;a href=&quot;http://site.icu-project.org/&quot;&gt;icu&lt;/a&gt; 里的。在伊们的 bug 追踪器上，我看到&lt;a href=&quot;https://unicode-org.atlassian.net/browse/ICU-12789?jql=text%20~%20%22ucnv_getDefaultName%22&quot;&gt;一个&lt;/a&gt;说：&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;在 ICU 之前，您可能得从您自己的代码里调用 &lt;code&gt;setlocale(LC_ALL, &quot;&quot;);&lt;/code&gt;。&lt;/p&gt;
&lt;/blockquote&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;And that reminds me. Does Qt call it for us? I searched on Qt&apos;s
bug tracker and got &lt;a href=&quot;https://bugreports.qt.io/browse/QTBUG-57522&quot;&gt;this&lt;/a&gt;:&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;When using ICU, Qt may call &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; without calling &lt;code&gt;setlocale(.., &quot;&quot;)&lt;/code&gt; first, which is required before calling &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt;.
One such possible path is:
&lt;code&gt;QString::fromLocal8Bit() -&amp;gt; QTextCodec::codecForLocale() -&amp;gt; QIcuCodec::defaultCodecUnlocked() -&amp;gt; ucnv_getDefaultName()&lt;/code&gt;
&lt;code&gt;setlocale()&lt;/code&gt; is called in &lt;code&gt;QCoreApplicationPrivate::initLocale()&lt;/code&gt;, which may not have been called.&lt;/p&gt;
&lt;/blockquote&gt;&lt;p&gt;But unfortunately, it seems the Qt person does not want to fix it.
Ok, I guess, then what is calling &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt;? At least,
if they do not fix it, I &lt;em&gt;must&lt;/em&gt; fix it.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;这又给我提了个醒了。Qt 给咱们调用这玩意儿了吗？我搜搜 Qt 的 bug 追踪器，看到&lt;a href=&quot;https://bugreports.qt.io/browse/QTBUG-57522&quot;&gt;这&lt;/a&gt;：&lt;/p&gt;&lt;blockquote&gt;
&lt;p&gt;用 ICU 时，Qt 可能会调用 &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; 而先前却没有调用过 &lt;code&gt;setlocale(.., &quot;&quot;)&lt;/code&gt;。后者则是在调用 &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; 之前必须得调用的.
一个可能的途径是：
&lt;code&gt;QString::fromLocal8Bit() -&amp;gt; QTextCodec::codecForLocale() -&amp;gt; QIcuCodec::defaultCodecUnlocked() -&amp;gt; ucnv_getDefaultName()&lt;/code&gt;
&lt;code&gt;setlocale()&lt;/code&gt; 是在 &lt;code&gt;QCoreApplicationPrivate::initLocale()&lt;/code&gt; 里调用的。但这玩意儿可能还没被调用呢。&lt;/p&gt;
&lt;/blockquote&gt; &lt;/div&gt;
&lt;h1&gt;&lt;div&gt; &lt;span&gt; How to track down a function call &lt;/span&gt;&lt;span&gt; 怎么追踪一个函数调用 &lt;/span&gt; &lt;/div&gt;&lt;/h1&gt;
&lt;div&gt; &lt;p&gt;I did not know. First intuition was to search for &lt;code&gt;fromLocal8Bit&lt;/code&gt; in the KIO source.
Yields nothing useful. Then it occurred to me that I could use &lt;code&gt;LD_PRELOAD&lt;/code&gt; trick.
From &lt;code&gt;proxychains&lt;/code&gt; to &lt;code&gt;valgrind&lt;/code&gt;, they all use &lt;code&gt;LD_PRELOAD&lt;/code&gt; to override an existing
function. If I override &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; to have it call &lt;code&gt;std::terminate()&lt;/code&gt;,
I could get a backtrace using gdb. So here it is:&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;我不知道啊。第一个直觉是在 KIO 源码里搜 &lt;code&gt;fromLocal8Bit&lt;/code&gt;。并没有用。然后我突然想起来
&lt;code&gt;LD_PRELOAD&lt;/code&gt; 的伎俩了。从 &lt;code&gt;proxychains&lt;/code&gt; 到 &lt;code&gt;valgrind&lt;/code&gt;，都用了 &lt;code&gt;LD_PRELOAD&lt;/code&gt; 去覆盖
一个已有的函数。如果我把 &lt;code&gt;ucnv_getDefaultName()&lt;/code&gt; 覆盖了，让它调用 &lt;code&gt;std::terminate()&lt;/code&gt;，
我便能用 gdb 整个 backtrace 出来。所以就这样了：&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;#include&lt;/span&gt;&lt;span&gt; &amp;lt;exception&amp;gt;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;extern&lt;/span&gt;&lt;span&gt; &quot;C&quot;&lt;/span&gt;&lt;span&gt; const&lt;/span&gt;&lt;span&gt; char&lt;/span&gt;&lt;span&gt; *&lt;/span&gt;&lt;span&gt;ucnv_getDefaultName&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;void&lt;/span&gt;&lt;span&gt;)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;{&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    std::terminate&lt;/span&gt;&lt;span&gt;();&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    return&lt;/span&gt;&lt;span&gt; &quot;&quot;&lt;/span&gt;&lt;span&gt;;&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;Compile that file with &lt;code&gt;g++ ucnv-override.cpp -shared -fPIC -o ucnv-override.so&lt;/code&gt;
and set &lt;code&gt;LD_PRELOAD=/path/to/ucnv-override.so&lt;/code&gt;. Use gdb to invoke dolphin.
Got &lt;a href=&quot;https://bugsfiles.kde.org/attachment.cgi?id=135421&quot;&gt;a backtrace&lt;/a&gt;. And it&apos;s there:&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;用 &lt;code&gt;g++ ucnv-override.cpp -shared -fPIC -o ucnv-override.so&lt;/code&gt; 编译那文件，
设下 &lt;code&gt;LD_PRELOAD=/path/to/ucnv-override.so&lt;/code&gt;。用 gdb 启动 dolphin。
弄出&lt;a href=&quot;https://bugsfiles.kde.org/attachment.cgi?id=135421&quot;&gt;一个 backtrace&lt;/a&gt;。搁这儿呢：&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;#26 0x00007ffff582370b in QLoggingCategory::init(char const*, QtMsgType) () from /usr/lib64/libQt5Core.so.5&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#27 0x00007ffff772d29b in __static_initialization_and_destruction_0 (__initialize_p=1, __priority=65535)&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    at /home/tusooa/Code/kio/src/widgets/kdirmodel.cpp:39&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#28 0x00007ffff772d2ce in _GLOBAL__sub_I_kdirmodel.cpp(void) ()&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;    at /home/tusooa/Code/kio/build/src/widgets/KF5KIOWidgets_autogen/include/moc_kdirmodel.cpp:155&lt;/span&gt;&lt;/span&gt;
&lt;span&gt;&lt;span&gt;#29 0x00007ffff7fe1c1e in ?? () from /lib64/ld-linux-x86-64.so.2&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;No statement from &lt;code&gt;main&lt;/code&gt;, no statement from Dolphin. It is in the
initialization of a global variable. And that is:&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;没有 &lt;code&gt;main&lt;/code&gt; 的语句。没有 Dolphin 的语句。这玩意是在初始化全局变量的。
呐看：&lt;/p&gt; &lt;/div&gt;
&lt;pre&gt;&lt;code&gt;&lt;span&gt;&lt;span&gt;static&lt;/span&gt;&lt;span&gt; QLoggingCategory &lt;/span&gt;&lt;span&gt;category&lt;/span&gt;&lt;span&gt;(&lt;/span&gt;&lt;span&gt;&quot;kf.kio.widgets.kdirmodel&quot;&lt;/span&gt;&lt;span&gt;, QtInfoMsg);&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;
&lt;div&gt; &lt;p&gt;The &lt;code&gt;QLoggingCategory&lt;/code&gt;. But Krita uses that for debug too, while I
do not have any issues dealing with Chinese file names? It turns out
that we have been using the &lt;code&gt;Q_LOGGING_CATEGORY&lt;/code&gt; macro, and &quot;The
implicitly-defined QLoggingCategory object is created on first use,
in a thread-safe manner.&quot; (from &lt;a href=&quot;https://doc.qt.io/qt-5/qloggingcategory.html#Q_LOGGING_CATEGORY-1&quot;&gt;Qt doc&lt;/a&gt;) So in that case,
the initialization is always run after entering &lt;code&gt;main()&lt;/code&gt;, where
&lt;code&gt;setlocale()&lt;/code&gt; has been called already.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;这 &lt;code&gt;QLoggingCategory&lt;/code&gt;。但 Krita 也用它做调试啊，我也没发现处理中文文件名有
啥问题啊？原来是我们用的是 &lt;code&gt;Q_LOGGING_CATEGORY&lt;/code&gt; 宏，而「这隐式定义的
QLoggingCategory 对象在初次使用时给创建的，这过程还线程安全。」（来自
&lt;a href=&quot;https://doc.qt.io/qt-5/qloggingcategory.html#Q_LOGGING_CATEGORY-1&quot;&gt;Qt 文档&lt;/a&gt;）所以那情况下，初始化肯定在进了 &lt;code&gt;main()&lt;/code&gt;，&lt;code&gt;setlocale()&lt;/code&gt;
被调用过了的时候才运行了的。&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;So, replace that with &lt;code&gt;Q_LOGGING_CATEGORY(category, &quot;kf.kio.widgets.kdirmodel&quot;, QtInfoMsg)&lt;/code&gt;,
and everything went back to normal. 200 IQ.&lt;/p&gt; &lt;/div&gt;
&lt;div&gt; &lt;p&gt;所以啊，把那换成 &lt;code&gt;Q_LOGGING_CATEGORY(category, &quot;kf.kio.widgets.kdirmodel&quot;, QtInfoMsg)&lt;/code&gt;，
啥东西都正常了。智商 200。&lt;/p&gt; &lt;/div&gt;</content></entry></feed>