Saturday, March 15, 2025

systemctl is-enabled is strange

The is-enabled command of the systemd systemctl command is purported to:

Checks whether any of the specified unit files are enabled (as with enable). Returns an exit code of 0 if at least one is enabled, non-zero otherwise.

But read the documentation carefully because, in my opinion, it doesn't do this.

In particular, its exit code cannot be relied on to indicate if a unit is enabled.

The text output is more useful but to use this in automation requires capturing the text output and parsing it to determine whether the unit might be enabled.

But look at the table of text outputs. Look at 'static'. The documentation says:

The unit file is not enabled, and has no provisions for enabling in the [Install] unit file section.

But it also says that for such a unit the exit code of 'is-enabled' will be 0, indicating the unit is enabled.

This is inconsistent: is the unit enabled as indicated by the exit code? Or is it "not enabled, and has no provisions for enabling" as indicated by the specification of the text output "static"?

In my opinion, if the unit is not enabled then the exit code should be non-zero, to be consistent with the intent that a 0 exit code indicates an enabled unit.

Similarly for "indirect":

The unit file itself is not enabled, but it has a non-empty Also= setting in the [Install] unit file section, listing other unit files that might be enabled, or it has an alias under a different name through a symlink that is not specified in Also=. For template unit files, an instance different than the one specified in DefaultInstance= is enabled.

But again, despite "The unit file itself is not enabled", the exit code will be 0. So, is it enabled per the exit code or not enabled per the definition of the output message?

There are other cases with similar ambiguities. Some have descriptions that are so laden with esoteric systemd jargon that they are effectively meaningless to the ordinary reader, leaving it unknown if the unit is enabled and how it will behave in any particular circumstance.

And even parsing the text message is not always adequate to determine if a unit is enabled. Consider "symlink"

The name is an alias (symlink to another unit file).

So, regardless of whether that other unit file is enabled or not, the exit code will be 0 and the text message will be "symlink". That's not very helpful. In the case of a symlink, it will be necessary to follow the link and determine whether that linked unit is enabled or not: which is the purported purpose of the 'is-enabled' command - but it just doesn't bother to do it.

For some particular types of unit files (e.g. service units that are not symlinks, aliases, dynamic, etc.) then the exit code and output text both provide unambiguous indication of whether the unit is enabled, but for others, not so much.

So, check to be sure the unit you are checking is one of the types for which 'is-enabled' provides unambiguous, correct output, or write your own function that doesn't depend on the 'is-enabled' command.
 

Wednesday, March 5, 2025

Armbian Build Framework

 I have a NanoPi R2S Plus. Armbian supports many of the NanoPi boards but doesn't have a configuration specific for the NanoPi R2S Plus. I have been running the NanoPi R2S configuration. They are similar boards but the Plus adds eMMC memory and has a different chip for the WAN network port so neither of these work.

Recently I started looking to add support for the NanoPi R2S Plus to Armbian to enable the eMMC and WAN port. It was the first time I had used the Armbian Build Framework. Previously I had only downloded pre-built images. What I discovered is that the build framework is very flexible but, therefore, complex and there is little documentation. This makes it difficult to work with: simple objectives are difficult to achieve.

Given the lack of documentation, copying working examples is the best way to get things done. There is support for over 300 boards in armbian/build. Configuration for some is seemingly simple while others are very sophisticated. But even the simple ones have a lot going on under the covers: the build framework itself is very sophisticated.

The problems with the NanoPi R2S Plus eMMC and WAN port were rooted in the device tree. But there is not one but two device trees to deal with: the device tree of the Linux kernel affects the booted system but u-boot has its own device tree that must be complete enough to boot Linux. In my case, I wanted to be able to boot from eMMC so needed to add that to both the u-boot and Linux device trees.

I had to add a new board. That required adding a configuration for it in the config/boards directory. To start, I copied the configuration for the NanoPi R2S to create config/boards/nanopi-r2s-plus.csc. This was the first step: the exact same configuration, just a different name. And learning to build an image with the compile.sh script.

Then I had to modify the device tree for the kernel, to enable the eMMC and WAN ports. FriendlyElec provides images for the NanoPi R2S Plus that have working eMMC and WAN port, including a Debian Bookworm image and images based on OpenWRT, one of which was pre-installed on the system. So I began to learn enough about device trees to be able to read and compare these and understand how the device tree for the NanoPi R2S is assembled by the Armbian build framework.

The syntax of the device tree is simple enough but the semantics and relations to the hardware and Linux kernel and device drivers is still bewildering. The device tree solves a complex problem and is necessarily complex itself. To master it, one must know intimate details of the hardware and its configuration and of the Linux drivers that read the device tree to get the information they require about the hardware. While I can compare and cut and paste the device tree source files, I am a long way from understanding the end-to-end interactions between the device drivers and the hardware. Fortunately, with all the examples available, it wasn't necessary to develop the device tree from hardware specs and the device driver code. All I had to do is copy the bits relevant to the eMMC and WAN port from the images where they worked into the Armbian build framework configuration I was creating for the NanoPi R2S Plus. Not quite trivial, but nearly so. 

My first attempt to change the kernel device tree failed, yielding an image that wouldn't boot. It couldn't find an image to boot and failed back to TFTP/BOOTP. I never did find out what went wrong but I think a collection of incompatible patches applied to the kernel that, at the time, I didn't know persisted between builds.

I scrubbed my clone of armbian/build and second attempt produced a working image that I progressed through several modifications to get first the eMMC and then the WAN port working. This time, every image booted and worked as expected.

In addition to the new board configuration, I had to create device tree source (dts) file for the board. I did this two ways: first making one that included the dts for the NanoPi R2S in armbian/build then overrode the bits related to the eMMC and WAN port. But while doing this I saw that the Linux kernel itself has support for the NanoPi R2S Plus. It has working eMMC but the WAN port doesn't work. So I made a second version of dts for NanoPi R2S Plus that includes the dts from the kernel then overrides the bits related to the WAN port.

Ultimately, I only created two files: the board configuration and the dts.

The board configuration is 'config/boards/nanopi-r2s-plus.csc':

# Rockchip RK3328 quad core 1GB 2 x GBE USB2
BOARD_NAME="Nanopi R2S Plus"
BOARDFAMILY="rockchip64"
BOARD_MAINTAINER=""
BOOTCONFIG="nanopi-r2s-rk3328_defconfig"
KERNEL_TARGET="current,edge"
KERNEL_TEST_TARGET="current"
DEFAULT_CONSOLE="serial"
MODULES="g_serial"
MODULES_BLACKLIST="rockchipdrm analogix_dp dw_mipi_dsi dw_hdmi gpu_sched lima hantro_vpu"
SERIALCON="ttyS2:1500000,ttyGS0"
HAS_VIDEO_OUTPUT="no"
BOOT_FDT_FILE="rockchip/rk3328-nanopi-r2s-plus-a1.dtb"

This is a copy of the configuration for NanoPi R2S. I changed the BOARD_NAME and BOOT_FDT_FILE. 'dtb' is 'device tree blob' - a binary form of the device tree. The build framework creates this from the device tree source (dts) files. So I had to create the dts file that corresponds to the specified dtb.

First I created patch/kernel/archive/rockchip64-6.12/dt/rk3328-nanopi-r2s-plus-a1.dts. 

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2019 FriendlyElec Computer Tech. Co., Ltd.
 * (http://www.friendlyarm.com)
 */

/dts-v1/;
#include <dt-bindings/input/linux-event-codes.h>
#include "rk3328-nanopi-r2s.dts"

/ {
    model = "FriendlyElec NanoPi R2S Plus";
    compatible = "friendlyelec,nanopi-r2s-plus", "friendlyelec,nanopi-r2", "rockchip,rk3328";

    gpio-keys {
        compatible = "gpio-keys";
        #address-cells = <1>;
        #size-cells = <0>;
        autorepeat;

        pinctrl-names = "default";
        pinctrl-0 = <&gpio_key1>;

        button@0 {
            gpios = <&gpio0 RK_PA0 GPIO_ACTIVE_LOW>;
            label = "reset";
            linux,code = <BTN_1>;
            linux,input-type = <1>;
            gpio-key,wakeup = <1>;
            debounce-interval = <100>;
        };
    };

    vcc_rtl8153: vcc-rtl8153-regulator {
        compatible = "regulator-fixed";
        gpio = <&gpio2 RK_PC6 GPIO_ACTIVE_HIGH>;
        pinctrl-names = "default";
        pinctrl-0 = <&usb30_en_drv>;
        regulator-always-on;
        regulator-name = "vcc_rtl8153";
        regulator-min-microvolt = <5000000>;
        regulator-max-microvolt = <5000000>;
        off-on-delay-us = <5000>;
        enable-active-high;
    };
};

&mach {
    hwrev = <0>;
    model = "NanoPi R2S";
};

&emmc {
    status = "okay";
};

&i2c0 {
    status = "okay";
};

&leds {
    status = "okay";

    led@2 {
        gpios = <&gpio2 RK_PB7 GPIO_ACTIVE_HIGH>;
        label = "lan_led";
    };

    led@3 {
        gpios = <&gpio2 RK_PC2 GPIO_ACTIVE_HIGH>;
        label = "wan_led";
    };
};

&rk805 {
    interrupt-parent = <&gpio1>;
    interrupts = <RK_PD0 IRQ_TYPE_LEVEL_LOW>;
};

&vccio_sd {
    status = "okay";
};

&io_domains {
    vccio3-supply = <&vccio_sd>;
};

&sdmmc {
    vqmmc-supply = <&vccio_sd>;
    max-frequency = <150000000>;
    sd-uhs-sdr50;
    sd-uhs-sdr104;
    status = "okay";
};

&sdmmc_ext {
    status = "disabled";
};

&sdio_pwrseq {
    status = "disabled";
};

&pinctrl {
    pmic {
        pmic_int_l: pmic-int-l {
            rockchip,pins = <1 RK_PD0 RK_FUNC_GPIO &pcfg_pull_up>;
        };
    };

    rockchip-key {
        gpio_key1: gpio-key1 {
            rockchip,pins = <0 RK_PA0 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };

    usb {
        otg_vbus_drv: otg-vbus-drv {
            rockchip,pins = <1 RK_PD2 RK_FUNC_GPIO &pcfg_pull_none>;
        };

        usb30_en_drv: usb30-en-drv {
            rockchip,pins = <2 RK_PC6 RK_FUNC_GPIO &pcfg_pull_none>;
        };
    };
};

/delete-node/ &rtl8211e;

&gmac2io {
    phy-handle = <&ethpy1>;
    snps,reset-delays-us = <0 15000 50000>;
    tx_delay = <0x22>;
    rx_delay = <0x12>;

    mdio {
        compatible = "snps,dwmac-mdio";
        #address-cells = <1>;
        #size-cells = <0>;

        ethpy1: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <0x1>;
            realtek,ledsel = <0xae00>;
        };
    };
};


This is a copy of the dts for the NanoPi R2s, with modifications to make the eMMC and WAN port work. 

In the node for '&emmc' I changes status from "disabled" to "okay". That's all it took to get the eMMC working - all the relevant content was already there, just disabled because the NanoPi R2S doesn't have eMMC memory.

The changes to get the WAN port working are a little more complicated but I was able to copy them from the dts in the FriendlyElec kernel source. The changes are at the end of the file. First '/delete-node/ &rtl8211e;' which deletes the node for the rtl8211e chip that's in the NanoPi R2s, then the '&gmac2io' node that overrides some parameters and adds a new 'ethpy1' node for the rtl8211f chip that is in the NanoPi R2S Plus. I couldn't have written these myself. I don't know enough about the hardware or the device drivers, despite having downloaded technical papers that describe the differences between the rtl8211e and rtl8211f. But copy/paste is easy. I only had to figure out what were the relevant bits, which is simple, in hindsight, though it took me a while to learn enough about the device tree syntax.

This version is based on the support for NanoPi R2S in Armbian build. Through a chain of includes, it includes several other files from Armbian build.

My second implementation was based on the support in the kernel. I created patch/kernel/archive/rockchhip64-6.12/dt/rk3328-nanopi-r2s-plus-rev00.dts:

// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
 * Copyright (c) 2025 Ian Goodacre
 */

/dts-v1/;
#include "rk3328-nanopi-r2s-plus.dts"

/delete-node/ &rtl8211e;

&gmac2io {
    phy-handle = <&rtl8211f>;
    snps,reset-delays-us = <0 15000 50000>;
    tx_delay = <0x22>;
    rx_delay = <0x12>;

    mdio {
        rtl8211f: ethernet-phy@1 {
            compatible = "ethernet-phy-ieee802.3-c22";
            reg = <0x1>;
            realtek,ledsel = <0xae00>;
        };
    };
};

This includes the dts for NanoPi R2S Plus from the kernel, in which the eMMC is already enabled but the nodes for the WAN port are still for the old rtl8211e chip. So, again, I had to delete the node for rtl8211e, add a node for rtl8211f and override a very few parameters in the gmac2io node.

I chose the name of the dts to be clear what board it was for and with 'rev00' to avoid name conflict with the dts in the kernel and following the pattern in the FriendlyElec kernel of 'revXX' for various versions supporting various boards. The 'patch' gets added to the kernel source without changing any of the existing files.

With these changes I build a kernel that booted from SD card and had eMMC and WAN port both working.

Finally, I copied the dts file to patch/kernel/archive/rockchhip64-6.13/dt/rk3328-nanopi-r2s-plus-rev00.dts. 6.12 is the 'current' kernel and 6.13 is the 'edge' kernel, at least, currently. Then I build an 'edge' image and all was well.

The final complication is that I want to boot from eMMC rather than SD card. But u-boot device tree is separate from the kernel device tree and it, like the kernel device tree, was for the NanoPi R2S which doesn't have eMMC. So I had to modify the u-boot device tree. I found this harder because the way the u-boot source is patched is different and there was a little bug in the tool for generating the patch file that held me up for most of a day. But, ultimately, it isn't much more difficult to patch the dts in u-boot than in the kernel.

But, along the way, someone pointed out the more recent version of u-boot has native support for NanoPi R2S Plus. The configuration for NanoPi R2S was based on u-boot v2022.07 but v2025.01 has the support. It took me a while to learn enough to get the build for NanoPi R2S Plus to use the more recent version of u-boot but in the end the changes were simple.

I changed the board config: config/boards/nanopi-r2s-plus.csc:

# Rockchip RK3328 quad core 1GB 2 x GBE USB2
BOARD_NAME="Nanopi R2S Plus"
BOARDFAMILY="rockchip64"
BOARD_MAINTAINER=""
BOOTBRANCH="tag:v2025.01"
BOOTPATCHDIR="v2025.01"
BOOTCONFIG="nanopi-r2s-plus-rk3328_defconfig"
KERNEL_TARGET="current,edge"
KERNEL_TEST_TARGET="current"
DEFAULT_CONSOLE="serial"
MODULES="g_serial"
MODULES_BLACKLIST="rockchipdrm analogix_dp dw_mipi_dsi dw_hdmi gpu_sched lima hantro_vpu"
SERIALCON="ttyS2:1500000,ttyGS0"
HAS_VIDEO_OUTPUT="no"
BOOT_FDT_FILE="rockchip/rk3328-nanopi-r2s-plus-rev00.dtb"
 

BOOTBRANCH, BOOTPATCHDIR and BOOTCONFIG all affect the u-boot build. BOOTBRANCH specifies the branch of the u-boot repository, overriding the 'default' v2022.07 for rockchip64. And BOOTPATCHDIR specifies what directory to get u-boot patches from. The default directory contains patches for many boards, some of which are incompatible with v2025.01, causing build failure. There aren't many patches in the 'v2025.02' directory (patch/u-boot/v2025.01). All I had to do was add a directory for the NanoPi R2S Plus: patch/u-boot/v2025.01/board_nanopi-r2s-plus and put a patch file in it. The directory name is important. It is specific to the board, because it begins with 'board_' and the remainder matches the name of the board configuration file (I think that's where 'nanopi-r2s-plus' comes from).

In patch/u-boot/v2025.01/board_nanopi-r2s-plus I created patch file 001-add-trust-ini.patch:

diff --git a/trust.ini b/trust.ini
new file mode 100644
index 0000000..4af021a
--- /dev/null
+++ b/trust.ini
@@ -0,0 +1,15 @@
+[VERSION]
+MAJOR=1
+MINOR=0
+[BL30_OPTION]
+SEC=0
+[BL31_OPTION]
+SEC=1
+PATH=bl31.elf
+ADDR=0x10000
+[BL32_OPTION]
+SEC=0
+[BL33_OPTION]
+SEC=0
+[OUTPUT]
+PATH=trust.bin

This adds file 'trust.ini' to the root of the u-boot source, which is required to build the u-boot artifact. Before I added this patch the build failed because it couldn't find it. I just copied the patch from the configuration for v2022.07.

And with these changes, the build succeeded. It isn't tested yet, but I think it is getting close.

It took me a few weeks to work through all this, from setting up the build environment through learning about device trees, RockChip SOC booting, u-boot and the Armbian build framework. It looks simple in hindsight, but initially I was almost completely in the dark, stumbling about, breaking things.

Thursday, February 20, 2025

Rust in the Linux kernel

I have been seeing a lot of news about drama and conflict related to Rust in the Linux kernel recently.

My first experience with Rust was some years ago when I was debugging and enhancing Anki. Rust had been introduced in the back end and some of the bugs I was working to understand and resolve were in the Rust code, so I had to learn enough about Rust to set up a development environment and to be able to read, understand and modify the Anki Rust code. It was an unpleasant experience. I found the Rust documentation to be inadequate, leading to many days of research and experimentation to learn enough about what the Rust toolchain was doing. I was able to hack at the Anki Rust code sufficiently to find and fix several bugs but it did not leave me enamoured with Rust. The experience made me sympathetic to the concerns of the Linux kernel maintainers who are concerned about the complexity and supportability of the Linux kernel if Rust is included.

The Linux kernel documentation has a section on Rust. It begins with an overview that includes:

If you are an end user, please note that there are currently no in-tree drivers/modules suitable or intended for production use, and that the Rust support is still in development/experimental, especially for certain kernel configurations.

Given the intensity of the disputes I had been hearing about, I wondered if this was obsolete and in fact Rust was now essential to the kernel.

 So I downloaded the current mainline kernel and built it without installing any of the Rust toolchain. No problem, so evidently Rust is not yet required to build a kernel.

Then I wanted to see what it was like building a kernel that included the Rust support.

I was doing this on a system running Debian 12 (Bookworm).

I installed the required Rust packages from the Debian repositories but they were too old:

$ make LLVM=1 rustavailable
***
*** Rust compiler 'rustc' is too old.
***   Your version:    1.63.0
***   Minimum version: 1.78.0
***
***
*** Please see Documentation/rust/quick-start.rst for details
*** on how to set up the Rust support.
***

I found a page that described how to install Rust on Debian 12:

https://idroot.us/install-rust-debian-12/

I downloaded https://sh.rustup.rs, reviewed it then ran it, proceeding with standard installation.

Through a sequence of trial and error making the rustavailable target, I installed various other prerequisites:

$ cargo install bindgen-cli

$ sudo apt install libclang-dev clang

$ rustup component add rust-src

After these I get 'Rust is available!'

But also:

*** libclang (used by the Rust bindings generator 'bindgen')
*** version does not match Clang's. This may be a problem.
***   libclang version: 15.0.6
***   Clang version:    14.0.6

While I had installed Debian libclang-dev and it was version 14, somehow libclang 15 was installed and found. I guess it came along with the installation of Rust but I didn't investigate to determine where version 15 was found and how it was installed.

So, the rustavailable target reports Rust is available but when I make the menuconfig target, there is no option under General setup for Rust support.

Fortunately I found

 
This provided the configuration requirement missing from the Rust Quick Start Guide in the Linux kernel documentation:

'Module versioning support' in section 'Enable loadable module support' must not be enabled. Somehow my configuration had it enabled: I had copied the configuration from /boot, so either it is enabled there or it is a new configuration option enabled by default. I didn't determine where the setting came from but I disabled it and then 'Rust support' appeared in 'General setup'.

So I enabled Rust support and re-ran make and saw it building various targets in the 'rust' directory.

There were no obvious errors during the build, but I haven't tried running the built kernel and wouldn't know how to test the bits written in Rust if I did. I haven't learned much about what is written in Rust. But, superficially, it seems I now have the Rust build tools set up well enough that I can build a kernel including components written in Rust.

That I could not use the Rust packages from Debian 12 (Bookworm) is a reflection of how young and volatile the Rust language and build tools are. This makes me feel that Rust is not yet mature enough to be writing essential components of the Linux kernel in it, but that isn't happening yet. Rust in the Linux kernel is, it seems to me, still experimental. If the experiment succeeds, perhaps by then the Rust language and build tools will have stabilized sufficiently that the version of Rust in stable version of Debian will be sufficient.

In summary, Rust is not yet an essential part of the Linux kernel. The integration of Rust is still experimental.

Despite being experimental or, at least, still optional, according to
The Road to Rustification: Lessons from the Linux Kernel Development Process, the Rust code is already 15% of the kernel codebase with an expectation that it will become 25% in 2025. 

I understand that Rust offers memory safety that is not guaranteed by the existing C code and build tools and that errors in memory management are a significant percentage of all the errors in the Linux kernel. I haven't come across an explanation why, of all the new languages that guarantee memory safety, Rust was selected for inclusion in the Linux kernel. 
 
Evidently, Rust has advocates with resources to push it forward into the kernel. But where is the record of comparison of Rust with the other options for ensuring memory safety and avoiding or eliminating a common type of error from the kernel? Where is the evidence that Rust is the best choice?

Maybe Rust, like C, isn't the best choice. Maybe it is just a choice with enough support that it is being implemented, despite concerns and objections from some quarters. But if there is some other language that would be better, more effective, easier and less contentious, it is irrelevant if no one is advocating it and willing to do the work to make it available and use it in the kernel.

I have great respect for the people who build and maintain the Linux kernel. Their achievements to date are amazing. While past success is no guarantee of future performance, as they say in the financial industry, I think there is good reason to trust and respect the decisions of the kernel maintainers.

And, ultimately, Linux is open source. Rather than complaining about the team at kernel.org, the Rust advocates can always do as they wish with the fork of the kernel and, benefiting from the superiority of Rust, they may so outperform the current development effort in speed and quality that soon everyone will follow them. There is no good reason for dispute and disrespect. They are free to do better.


 

Friday, June 21, 2024

ECMAScript module equivalent of CommonJS module usage: const x = require('myModule')(options);

A common, concise idiom of CommonJS modules that export factory functions is:

const instance = import('myModule')(myOptions);

This is concise and minimizes consumption of the name space.

With ECMAScript modules and the import declaration, the same or similar is not possible because the import declaration is not an expression that returns a value and it must be at the top level of the module: they cannot be within a block or function, including an immediately invoked function expression (IIFE).

Assuming the module in question exports a factory function as its default export, then the nearest equivalent would be something like:

import myFactory from 'myModule';

const instance = myFactory(myOptions);

If a named export the something like:

import { exportName as myFactory } from 'myModule';

const instance = myFactory(myOptions);

This is a little less concise: two lines of code instead of one, and an additional name consumed in the module name space. But otherwise gives the same result.

Something more equivalent is possible with the dynamic import keyword. This is function-like and can occur within a function, including an IIFE.

const instance = (await import('myModule')).default(myOptions);

This is only a little less concise than the CommonJS syntax. Similar construct is possible with named exports, by simply replacing 'default' with the name of the named export.

Using dynamic import keyword instead of the import declaration, one gives up the static analysis made possible by the import declaration, and there may be other benefits of the declaration over the keyword.

Sunday, June 16, 2024

JavaScript testing with Tape

 I mostly use tape for testing my JavaScript packages. Today I was updating dependencies and update of tape was among them.

The link from npmjs.com to GitHub was broken, yielding a 404 page. So I logged an issue and within a few minutes the links were fixed.

So, the GitHub repository for tape has moved to https://github.com/tape-testing/tape but all is well. Jordan is still actively maintaining the package.

It remains my favourite test package for JavaScript.

First release from the current repository was Nov 26, 2012. Well over a decade ago. It is good to see that it is still maintained and with attention to backwards compatibility.

Thus far, all my packages are CommonJS.

For linting, I use eslint with configuration @ig3/eslint-config-entrain. 

For coverage, I use c8.

For test runner I use multi-tape. Pending an update of multi-tape dependencies, I have published @ig3/multi-tape.

Typical test script is: "eslint . && multi-tape test/*.js"

@ig3/eslint-config-entrain is based on neostandard and eslint version 9. Typical eslint.config.js is:

 

'use strict';

const eslintConfigEntrain = require('@ig3/eslint-config-entrain');

module.exports = [
  ...eslintConfigEntrain,
];

Or sometimes something like:

'use strict';

const eslintConfigEntrain = require('@ig3/eslint-config-entrain');

module.exports = [
  ...eslintConfigEntrain,
  {
    ignores: ['public/js/**'],
  },
];

Setup is:

$ npm install -D @ig3/eslint-config-entrain c8 tape multi-tape

 Change the test script to:

"eslint . && c8 multi-tape test/*.js"

 And create eslint.config.js as above.

 

Tuesday, March 26, 2024

Thunar sort order

For many years I have run Debian with xfce4 desktop.

I have always hated the way Thunar sorts files by name. The developers would say it is not a Thunar issue but rather a Gtk issue. But I really don't care. I hate it whatever the root cause. It is made worse by the fact that there is absolutely no configuration possible other than collation options (LC_ALL and LC_COLLATE) but these don't fix the problem.

So, today I dug in to fix it.

I tried several other file browsers, but they all have slight variations of the same nonsense and I didn't find any that sort file names sensibly (e.g. like ls does).

There are many bug reports against Thunar, Nautilus and several others, reporting that available sorting options are not satisfying.

There are so many, it makes me wonder why there isn't a simple plug-in option, to separate sorting from other aspects of the file browser, allowing people to easily develop the sorting algorithm they need. Gtk could provide this but so could the various file browsers. It seems all I investigated in any detail delegate sorting to Gtk and refuse to add any features to work around its limitations and stupidity (i.e. 'natural' sorting).

I read many bug reports and posts, with the conclusion that there is no configuration option to fix it.

I tried building Thunar from source, thinking I would hack on it to fix the sorting or add an interface to an external sort implementation, but there were too many dependencies and I got tired of installing them.

But in reading through all the bug reports, I came across GlibSortFileNameHackLibrary so I gave it a try. I cloned the repo and tried to build it.

On Debian Bookworm, despite having built various other packages, I still had to install libglib2.0-dev:

$ sudo apt install libglib2.0-dev

Then:

$ make all

This terminates with an error compiling test.c, but that's only a test program. The library builds OK.

Then, from a command window:

$ thunar -q; LD_PRELOAD=./glibSortFileNameHack.so thunar

This launched a new Thunar instance and files were sorted sensibly (as opposed to the Gtk idea of 'naturally'). It was wonderful! Finally, I can find files in Thunar without wasting time scrolling up and down to try to figure out where they have been misplaced.

I couldn't find trace of a Thunar background process running after I logged in (i.e. ps -ef showed nothing with 'thunar' in the command line). So I guessed there isn't one and didn't worry about why.

I use Whisker menu but don't really know how to configure it. When I tried prefixing the command for the file browser launcher with LD_PRELOAD=/path/to/glibSortFileNameHack.so, I got an error that LD_PRELOAD isn't executable. 

So, I made a bash script:

#!/bin/bash
LD_PRELOAD=/home/ian/lib/glibSortFileNameHack.so thunar

And I changed the launcher to run the script.

This seems to work fine. At least, I haven't noticed any problems yet.

Now every Thunar instance sorts files sensibly.

Kudos to Alexandre Richonnier for publishing GlibSortFileNameHackLibrary. It hasn't been updated in 9 years, but it still works a treat!

Sunday, April 2, 2023

@ig3/srf - Spaced Repetition Flashcards

It is 2 years since I gave up on Anki and wrote @ig3/srf.

I have used it to study almost very day, for 2 years now. I have changed the scheduling algorithm several times. Sometimes little tweaks of parameters and sometimes fundamental changes to the algorithm.

The most recent big change was to eliminate the percentage of correct answers as a factor in calculating the new interval. Instead, for each review of a card with interval longer than the learning threshold, the interval and due date of every card with interval longer than the learning threshold is adjusted, according to the difference between percent correct and the target percent correct. This decreases the delay of feedback. It's early days, but it seems to be working.

Overall I am very pleased with @ig3/srf. I am limited by my poor memory but not overwhelmed and not feeling like a failure. At least, not most days. There are some days, usually after a few days without sleep, that it truly seems hopeless. But then I get some rest and get back on track.

I have put a lot of time into development @ig3/srf over the past two years, but I am confident that it was less effort for a better outcome than trying to maintain my Anki plugin to improve Anki's algorithm. I don't regret my decision at all.

Labels