antony-jr / QAppImageUpdate

Qt5 library and plugin for updating AppImages. :heart:
https://antony-jr.github.io/QAppImageUpdate
BSD 3-Clause "New" or "Revised" License
28 stars 13 forks source link

Fails with InvalidMagicBytes when AppImageLauncher is installed #29

Closed d1vanov closed 4 years ago

d1vanov commented 4 years ago

Select your issue type: (check at least one)

Describe your issue: I've been trying to use this library in my project to enable checking for AppImage updates and installing them from within the app. It works well in general but recently I found out that checking for updates functionality breaks when AppImageLauncher is installed.

AppImageLauncher is a project aiming to provide better integration of AppImages into the Linux desktop. I don't know in details how it works but it seems like it registers an interpreter for AppImage file type and hence gets the ability to intercept their launches. For example, when you try to launch some AppImage downloaded from the Internet, AppImageLauncher asks you whether you want to integrate this AppImage into the system (i.e. move it to ~/Applications) or just run it. Another thing that AppImageLauncher seems to be doing is changing the actual path to the AppImage from the application's POV: when AppImageDeltaRevisioner object is constructed without arguments, the path to AppImage it guesses automatically is not the actual path to the AppImage file but something like this instead:

/run/user/1000/appimagelauncherfs/0001.AppImage

I don't quite know what this executable is but it doesn't seem to be an actual AppImage. At least it doesn't contain A and I bytes which AppImageUpdaterBridge expects to be in place. For the actuall AppImage of my app I do see these bytes in place:

$ head -n 1 Quentier-x86_64_ef0106efad9cea0824a19d60aef4af2e.AppImage 
ELFAI>�F@@(�@@ @@@@@ <...>

For this executable I don't:

$ head -n 1 /run/user/1000/appimagelauncherfs/0001.AppImage
ELF>�F@@(�@@ @@@@@ <...>

So it's no surprise that check for magic bytes fails.

It's not immediately clear how AppImageUpdaterBridge can be adapted to AppImageLauncher presence. Perhaps some research is required first to figure out what exactly AppImageLauncher does to the actual AppImages when they are launched. Then AppImageUpdaterBridge could be able to detect that AppImageLauncher is installed (for example, looking at whether AppImageLauncher executable is within PATH) and act accordingly i.e. find some other way to figure out AppImage's type.

Ways to Reproduce the issue (optional): Install AppImageLauncher, take any executable using this library and try to check for updates.

d1vanov commented 4 years ago

And one more not quite related thing: I've been looking into the source code around the checks for magic bytes and found this if condition:

    if (magicBytes[0] != 'A' && magicBytes[1] != 'I') {
        /*
         * If its not an AppImage then lets check if its a linux desktop file , If so then parse the 'Exec'
         * to find the actual AppImage.
        */

It seems to be that the condition should be logical or instead of logical and:

    if (magicBytes[0] != 'A' || magicBytes[1] != 'I') {

Otherwise if at least one of these two magic bytes matches the expected one, the body of if condition won't be entered although it should be.

antony-jr commented 4 years ago

Will look into it asap.

EDIT:

It seems to be that the condition should be logical or instead of logical and

Yes it should be. Thanks for catching it.

antony-jr commented 4 years ago

@d1vanov It seems AppImageLauncher uses a FuseFS for some reason, it's mapping the actual file to this virtual file which does not have the magic bytes.

d1vanov commented 4 years ago

I suspected it does something like that. Any idea how to work around it? As a user of the library, I can try to detect AppImageLauncher presence from my app (e.g. via running which AppImageLauncher in QProcess) and advice user to use other means to update the AppImage rather than from within the app. It's sort of a poor man's workaround for this problem. But maybe there's some better way?

antony-jr commented 4 years ago

I think I might have to do some hacks to detect if the current appimage directory is AppImageLauncher's virtual directory then read the map file to find the actual path.

And the map file says everything,

0001.AppImage -> /home/antonyjr/AppImages/BlueGlow-121e837-x86_64.AppImage
0000.AppImage -> /home/antonyjr/Applications/AppImageUpdater-26aa32d-x86_64-revised-on-2020-02-15T01-10-08_8ce1736c63e34e0fb4f469e424a4ff41.AppImage
antony-jr commented 4 years ago

I think a simple regex matching would be enough and running some stats would confirm if it is a FuseFS of AppImageLauncher.

antony-jr commented 4 years ago

Also I think I might use QApp's arguments to construct an executable, it only fails inside firejail so I removed it long ago. I think we might just do that in cases like these.

antony-jr commented 4 years ago

@d1vanov I think it's fixed, could you confirm this issue in the latest commit?

The fix was simple, I did a regex match on the AppImage path, if it matches the wildcard filename ( /run/user/*/appimagelauncherfs/*.AppImage ) then it is a virtual file. I found the path was constant from AppImageLauncher source code, except for the wildcard.

If it is a virtual file, I derived the AppImage path from QApp's arguments.

d1vanov commented 4 years ago

Just checked, with the latest fix checking for updates works when AppImageLauncher is installed! :+1: Thank you very much for such a quick fix! :smiley: