💾 Archived View for gemini.hitchhiker-linux.org › gemlog › daily_driving_void.gmi captured on 2024-06-19 at 22:49:11. Gemini links have been rewritten to link to archived content

View Raw

More Information

⬅️ Previous capture (2023-06-14)

-=-=-=-=-=-=-

Daily driving Void

2023-04-07

I've had Void as a second boot option for months now, and before that I had a fairly long running VM. I've been interested in moving to Void for a while for a couple of reasons, but the biggest would be that I wanted a distro based on Musl libc.

I already made this particular jump on one machine, the Raspberry Pi 4 which runs this gemlog, my Gitea instance and an Apache server. It's been rock solid and I haven't had to do anything other than install updates since the first week I brought it up. I haven't had that kind of stability in a long time.

Clearing some blockers

One of the things that had been holding me back for a while was that the versions of Gtk and LibAdwaita in the Void repositories were behind what I needed to compile some of my projects, namely Eva, Gfret and Vapad. I've been used to getting library updates on Arch very soon after they become available. That highlights one of the differences between the Arch and Void release models. Both are rolling, but Void is more "Rolling Stable" for lack of a better term. More on that below. Anyway, short of compiling libraries myself this one was beyond my control. In the future I'll hold off updating my code until the libraries are available on Void, which coincidentally matches up better with what a lot of the LTS distros are doing.

The other issue I was having it turns out was my own fault. It's no secret I do most of my coding these days in Rust, and it has definitely spoiled me in a lot of ways. The version of Rust in the Void repos was, up until last week, 1.64. There were a number of really cool features release in Rust 1.65 which I bought into right away. The biggest two were let..else and labeled blocks.

// old syntax
let thing = if let Some(t) = some_function_returning_option() {
    t
} else {
    return Err(NoThingError);
};
// with let..else
let Some(thing) = some_function_returning_option() else {
    return Err(NoThingError);
};

The other feature, labeled blocks, is pretty much the same thing as Zig's named blocks, and it's a geat feature. It allows one to return early from a code block with a value, including breaking out of an outer loop in nested loops. The syntax is a little odd, but I've already used it in a couple of places.

let liff = 'outer: loop {
    loop {
        break 'outer 42;
    }
}

Anyway, I really wanted those newer features and not having them was going to be a deal breaker for me. Ordinarily I'd be using Rust from the official installer, rustup, but I was getting weird random issues every time I tried using the toolchain provided that way. I thought it had something to do with Musl, and that the Void devs had found a workaround. Turns out it was just me not realizing that I had previously installed Rust via the distro packages and had neglected to remove the `rust-std` package. Doh.

Note that about the same time I fixed that issue, the distro packages were updated. But I'm sticking with the rustup toolchain because the distro packages are missing rustfmt and clippy, which are too nice to not use.

Rolling release vs rolling stable

This seems like a subtle distinction, but in practice is has some noticable effects. In Arch, every package gets updated as soon after a new stable version is released as it can be packaged and tested. This includes libraries and kernels. This is rolling release.

In Void, those libraries don't get updates beyond path releases until the applications that are using them require the new versions. Void also uses the most recent LTS series kernel. This leads to less churn overall, while still having the benefit of a system that never has to be reinstalled or go through an upgrade from one OS version to another.

This leads to some interesting contradictions. On Arch, I had gtk-4.10 for months, while all of the Gnome applications that were installed only needed gtk-4.8. But when Gnome43 came out, it took almost two months for the dev team to build, test and release it. Void, on the other hand, waited until the desktop was being updated and updated all of the libraries at the same time. So when Gnome44 came out last week Void got a large influx of updates and the entire desktop was available within days. It's probably not as well tested as Arch, but considering the manpower constraints how could it be?

Note: Fedora and Ubuntu both time their releases to occur shortly after Gnome major releases, so when the next major version of those distros ship it can take advantage of what is at the time the latest version. So the way Void works is actually pretty similar, except no need to download an iso and upgrade from one distro release to another or to change repositories.

At any rate, I really rather like this "Rolling Stable" approach. It definitely calmed things down when it came to running the OS as a server, and it seems to work out nicely on the desktop as well.

Some interesting things I've encountered in Musl

As most OSS these days is written for Linux, and the vast majority of distros ship Glibc rather than Musl, it's only natural that some differences would be noticable, particularly if like me you tend to experiment and write a fair bit of low level code. Just this morning, I was working on bringing up an aarch64 cross compiler toolchain (as per the instructions in my post about building cross compilers) and Binutils failed to build. The offending code was in the `gprofng` module, where it was looking at the members of the `sigevent` structure individually, and expecting one to be there that does not exist in Musl. Rather than investigate and patch I just disabled gprofng via the configure script. Luckily that was a thing that I could do. This sort of thing is not always the case.

Another difference I encountered when writing some code which read usernames and validated passwords using the libc `pw` and `spw` structs. My original pass at this (using unsafe Rust) compiled and ran fine with Glibc. With Musl, it compiled and errored at runtime because some of the data I was trying to read was a null pointer (I did at least have proper error handling and null checks in the code). Anyway the reason it was happening was that Musl re-uses the same memory address for each subsequent construction of `pw`. When I looked at the POSIX spec, sure enough it said that some Libc implementations do this and it is fine. There were two possible solutions. One was to copy the bytes from that memory location into an owned piece of memory. The other method was to move all of the logic using the data in the first `pw` instance to before the creation of the second `pw` instance. The second method, requiring one less allocation, is what I used. This is a pretty nice demonstration of the sort of trouble you can get into when writing `unsafe` Rust, but because the language has trained me to account for all code paths instead of just the happy path it was a pretty easy diagnosis and fix.

That's two examples of the sort of things I've encountered in Musl, and I think they're pretty indicative of the sort of subtle differences one might encounter. That is to say, you're only going to encounter them if you're poking at the system. The average person really isn't going to notice any difference whatsoever because everything pretty much just works. A look at the relative sizes of tthe code is enlightening.

# Musl, according to Tokei
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 GNU Style Assembly    312         7938         7165          256          517
 Autoconf               35         6624          236         6273          115
 C                    1583        65083        50968         7551         6564
 C Header              668        40754        35683          301         4770
 Makefile               12          314          201           34           79
 Shell                   4          925          638          172          115
===============================================================================
 Total                2614       121638        94891        14587        12160
===============================================================================

# Glibc
===============================================================================
 Language            Files        Lines         Code     Comments       Blanks
===============================================================================
 GNU Style Assembly   2345       463470       328440        93738        41292
 Autoconf               94       112969       112642          182          145
 C                   10186      1015110       708606       209622        96882
 C Header             3341       416395       304183        79574        32638
 C++                    35         2447         1660          525          262
 Happy                   1          387          330            0           57
 JSON                    2          100          100            0            0
 LD Script               3           25            7           17            1
 Makefile              202        21349        15600         2869         2880
 Module-Definition      21         2958         2759            0          199
 Perl                    3          864          635          142           87
 Python                 68        18705        14720         2383         1602
 Shell                  83         8713         6102         1671          940
 TeX                     1        11672         7162         3697          813
 Plain Text              8        63381            0        63159          222
===============================================================================
 Total               16393      2138545      1502946       457579       178020
===============================================================================

I've compiled both Musl and Glibc numerous times while playing around with HitchHiker. Glibc takes a solid 15 minutes start to finish, while Musl is done in around a minute or less. Also, why do I want or need both Perl and Python in a Libc distribution? They might not be available (much less wanted) while bootstrapping a system from source.

The coding *style* is also enlightening. Drew Devault did a great writeup on one of his investigations into why a certain piece of code segfaulted with Glibc and not with Musl.

Maybe, just maybe, the behavior of this function should not depend on five macros, whether or not you’re using a C++ compiler, the endianness of your machine, a look-up table, thread-local storage, and two pointer dereferences.

Drew Devault: A tale of two libcs

Anyway, even though I'm not writing much C these days it cannot be denied that C is the foundation upon which Linux (and most other modern software) is built. So for a long time I've really wanted to switch to a distro that uses what I firmly believe is a better Libc implementation. Void scratches that itch quite nicely. Bonus points because it uses Runit for Init, and I'm a big fan of service supervision suites like Runit and S6.

Tags for this page

Arch

Void

Linux

Distros

Home

All posts

All content for this site is licensed as CC BY-SA.

© 2023 by JeanG3nie

Finger

Contact