Big milestone: AppImageLauncher 1.0.0 released

After more than half a year, the first stable version of AppImageLauncher has been released. This marks a big milestone in the software’s history, as up to now, a continuous release scheme was used without any kind of real version scheme.

Within the last weeks, a lot has changed about AppImageLauncher. Read on to see what’s new, and find out how to install it!

If you read my last article on AppImageLauncher, you might’ve seen there’s been some work going on in the background, and a completely new user interface was designed. However, version 1.0.0 released tonight still contains the old user interface. You may ask now: where’s the cool new UI?

Unfortunately, during development, the branch diverged quite far from the master branch. I had tried to merge the branch back, but the workflows had changed and backwards compatibility was not given. As I didn’t have much time to invest any more, it was decided to abort this merge process, and leave the old UI in place.

I am still unhappy about the current user interface, and there’s preparations ongoing to at least use the new user interface. The user interface is, to some extent, a standalone unit, and can be adapted to work with the existing code base. So, even if we can’t merge the new code base completely back into master (unless I get a chance to invest more time into the project), you can expect to see the new interface in a future release.

So far, I’ve only talked about what could not be realized. I’ve mentioned a few big changes, though.

AppImageLauncher has always been using some hacky and AppImage type specific methods to be able to run AppImages. These were necessary due to the deep integration with the system using binfmt_misc (also the reason you actually need to install the software through your system’s package manager). By enabling this integration, AppImageLauncher is called every time an AppImage is launched, which allows for providing first-run assistance (and, in the future, hopefully features like proper, user-controlled sandboxing, etc.). However, it also means AppImageLauncher can’t simply run ./my.AppImage to launch an AppImage, because, you guessed it, AppImageLauncher would be called on said AppImage by the system again, resulting in an infinite loop. It needs to circumvent this integration in order to actually launch an AppImage previously launched with itself.

The methods used thus far involved shipping an additional runtime for type 2 AppImages that lacks the magic bytes which triggered binfmt_misc, and using that to run all type 2 AppImages; for type 1, the AppImage was simply copied to a tempdir, patched to remove the magic bytes, and then run. While the former method was really efficient and turned out to work really well (despite the need to build, ship and keep the runtime up to date), the latter was simply unacceptable in terms of performance and I/O, and was due for a change for ever since I implemented it. Thankfully, only a small share of all AppImages is still using the really old type 1, so this was also okayish from a “real world user” perspective. It’s better to have some support than no support for type 1, right?

Replacing this system of hacks took about 6 months of thinking to come up with a better idea, and a day to implement a first prototype. The new and currently implemented way to bypass the binfmt_misc integration is to ship a custom FUSE filesystem which I simply called AppImageLauncherFS. The filesystem provides virtual files which map to the real AppImages. On read() calls, the filesystem can identify the block in which the magic bytes are, and live-patches those out. Otherwise, it just performs a read() on the real file, and returns the actual contents directly.

Of course, there is an impact on the reading performance, and a slight CPU overhead. But from real-world tests, the additional delay is very small, and hardly noticable.

So, as you see, a workaround replaced two workarounds. Still better than having more than one solution, but not perfectly elegant either. I think there’s clearly a need for an “official” method to bypass binfmt_misc, but that’s a topic for another day.

I don’t want to forget to mention: yes, we’ve thought about calling the linker directly like /lib/ld-linux.so.2 my.AppImage. However, our magic bytes are inserted in some less meaningful space in the ELF header which works a bit like “linker might interpret them, but doesn’t have to”. Most real-world linkers and interpreters just ignore broken values, but some do recognize our magic bytes as such values, and as they’re invalid reject the binary. Those interpreters involve most (especially older) versions of QEMU, some Docker host systems and, as shown above, the ld-linux.so.2 binaries on most systems. Failures like these are probably okay from a standard point of view, and in my opinion the issue is a flaw in our specification. But there’s a slight feeling of “Seriously? Come on…” every time we hit that bug.

That said, I’m glad this idea came up to my mind during a boring talk (which allowed for tinkering a bit with FUSE even), and spending a couple of hours that evening on building my first ever FUSE filesystem (in C++, even!) evolved to become a core component of AppImageLauncher. The benefits are clear, the method is also future-proof (read: it’s easy to support future AppImage or other bundle types, as it’s independent from any implementation details on their side) and the code base fortunately is quite small and keeps maintenance efforts relatively low, compared to the old system(s).

A few minor bug fixes and improvements to the overall robustness of the code also went into this release. It took three beta releases to fix the remaining bugs created after the huge change of switching to AppImageLauncherFS. A couple of very kind people have helped in testing the software on a couple of distributions, and we can consider the release fairly stable.

This doesn’t have to mean anything, though! The past months have shown some exotic bugs, and nobody can ever make guarantees for bug free software. So, if you encounter any bug, please feel free to open an issue on GitHub. If you have questions or want to provide some feedback, please feel free to pass by in the #AppImage channel on Freenode (webchat) and leave comments or discuss ideas. You can also contribute: send code or help translating AppImageLauncher.

P.S.: Big thanks to Scarlett Gately Moore for her great help during this weekend. We put in some serious effort to finish the release, eliminate bugs and package it properly for Debian and its derivatives, and in the end I was able to make an actual stable release. I really enjoyed working with you!