Derivation for binary using fusermount - Stuck on 'attached data header'

I’m trying to write a derivation for ProgrammingDinosaur/autoortho4xplane.

Attempt with buildFHSEnv - Fails due to SUID wrappers

Currently, I have managed to get the program running with the following derivation:

{ lib
, stdenv
, fetchzip
, buildFHSEnv
, fuse3
, glib
, zlib
, mesa
, libGL
, libGLU
, libxkbcommon
, wayland
, xorg
, fontconfig
, freetype
, zstd
, dbus
}:

let
  baseDrv = stdenv.mkDerivation rec {
    pname = "auto-ortho-base";
    version = "1.3.3";
    src = fetchzip {
      url = "https://github.com/ProgrammingDinosaur/autoortho4xplane/releases/download/${version}/autoortho_linux_${version}.tar.gz";
      sha256 = "sha256-slH7Po9LYS1JjObKvMrRTmpI0RjnbbkI1Lik0yCUCr0=";
      stripRoot = false;
    };

    buildInputs = [ fuse3 ];

    installPhase = ''
      runHook preInstall
      mkdir -p $out/bin
      cp autoortho_lin_${version}.bin $out/bin/auto-ortho
      runHook postInstall
    '';

    dontFixup = true;

    meta = with lib; {
      homepage = "https://github.com/ProgrammingDinosaur/autoortho4xplane";
      description = "AutoOrtho for X-Plane — automatic orthophoto streaming";
      platforms = platforms.linux;
    };
  };
in
buildFHSEnv {
  name = "auto-ortho";

  targetPkgs = pkgs: with pkgs; [
    baseDrv
    fuse3
    glib
    zlib
    mesa
    libGL
    libGLU
    libxkbcommon
    wayland
    xorg.libX11
    xorg.libXext
    xorg.libXrender
    xorg.libXtst
    xorg.libXi
    xorg.libXrandr
    fontconfig
    freetype
    zstd
    dbus
  ];

  extraBwrapArgs = [ "--bind-try /etc/fuse.conf /etc/fuse.conf" ];

  runScript = "${baseDrv}/bin/auto-ortho";
}

However, when trying to use the main function of the program you get an error when fusermount is called.

INFO:config_ui_qt:Mounting sceneries...
INFO:autoortho:Running in single-threaded mode.
INFO:autoortho:Running mounts in non-blocking mode.
INFO:autoortho:Running in FUSE mode.
INFO:autoortho:AutoOrtho:  root: .../Steam/steamapps/common/X-Plane 12/Custom Scenery/z_autoortho/scenery/z_ao_eur  mountpoint: .../Steam/steamapps/common/X-Plane 12/Custom Scenery/z_ao_eur
Linux detected
INFO:getortho:chunk_getter: <getortho.ChunkGetter object at 0x7f3115cc4440>
INFO:autoortho_fuse:ROOT: .../Steam/steamapps/common/X-Plane 12/Custom Scenery/z_autoortho/scenery/z_ao_eur
INFO:getortho:Maptype override set to BI
INFO:getortho:Will use Compressor: ISPC
INFO:getortho:Cache dir: /home/matth/.autoortho-data/cache
INFO:getortho:Target zoom level set to ZL16
INFO:getortho:Started tile clean thread.  Mem limit 4294967296.0
INFO:autoortho_fuse:MOUNT: .../Steam/steamapps/common/X-Plane 12/Custom Scenery/z_ao_eur
INFO:autoortho_fuse:Starting FUSE mount
INFO:autoortho_fuse:Loading FUSE with options: allow_other, foreground, nothreads
DEBUG:fuse:Set libFUSE callback for 'getattr' to wrapped <bound compiled_method FUSE.getattr_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound method AutoOrtho.getattr of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'readlink' to wrapped <bound compiled_method FUSE.readlink of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.readlink of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'mknod' to wrapped <bound compiled_method FUSE.mknod of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.mknod of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'mkdir' to wrapped <bound compiled_method FUSE.mkdir of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.mkdir of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'unlink' to wrapped <bound compiled_method FUSE.unlink of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.unlink of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'rmdir' to wrapped <bound compiled_method FUSE.rmdir of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.rmdir of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'symlink' to wrapped <bound compiled_method FUSE.symlink of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.symlink of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'rename' to wrapped <bound compiled_method FUSE.rename_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.rename of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'link' to wrapped <bound compiled_method FUSE.link of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.link of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'chmod' to wrapped <bound compiled_method FUSE.chmod_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.chmod of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'chown' to wrapped <bound compiled_method FUSE.chown_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.chown of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'truncate' to wrapped <bound compiled_method FUSE.truncate_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.truncate of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'open' to wrapped <bound compiled_method FUSE.open of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.open of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'read' to wrapped <bound compiled_method FUSE.read of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.read of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Leave libFUSE callback for 'write' uninitialized.
DEBUG:fuse:Set libFUSE callback for 'statfs' to wrapped <bound compiled_method FUSE.statfs of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound method AutoOrtho.statfs of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'flush' to wrapped <bound compiled_method FUSE.flush of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.flush of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'release' to wrapped <bound compiled_method FUSE.release of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.release of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'fsync' to wrapped <bound compiled_method FUSE.fsync of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.fsync of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Leave libFUSE callback for 'setxattr' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'getxattr' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'listxattr' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'removexattr' uninitialized.
DEBUG:fuse:Set libFUSE callback for 'opendir' to wrapped <bound compiled_method FUSE.opendir of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.opendir of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'readdir' to wrapped <bound compiled_method FUSE.readdir_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound method AutoOrtho.readdir of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Set libFUSE callback for 'releasedir' to wrapped <bound compiled_method FUSE.releasedir of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.releasedir of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Leave libFUSE callback for 'fsyncdir' uninitialized.
DEBUG:fuse:Set libFUSE callback for 'init' to wrapped <bound compiled_method FUSE.init_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method Operations.init of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Leave libFUSE callback for 'destroy' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'access' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'create' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'lock' uninitialized.
DEBUG:fuse:Set libFUSE callback for 'utimens' to wrapped <bound compiled_method FUSE.utimens_fuse_3 of <mfusepy.FUSE object at 0x7f3115ce44d0>> wrapping <bound compiled_method AutoOrtho.utimens of <autoortho_fuse.AutoOrtho object at 0x7f3134476870>>
DEBUG:fuse:Leave libFUSE callback for 'bmap' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'ioctl' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'poll' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'write_buf' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'read_buf' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'flock' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'fallocate' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'copy_file_range' uninitialized.
DEBUG:fuse:Leave libFUSE callback for 'lseek' uninitialized.
/nix/store/hia0d0lpnkzcjgizrd4y2x681gqpm3mz-fuse-3.17.4-bin/bin/fusermount3: mount failed: Operation not permitted
ERROR:autoortho_fuse:FUSE mount failed with non-negotiable error: 4
INFO:utils.mount_utils:Cleaning up mountpoint: .../Steam/steamapps/common/X-Plane 12/Custom Scenery/z_ao_eur
ERROR:autoortho:Exception in FUSE mount: 4
Traceback (most recent call last):
  File "/tmp/onefile_66002_1761770772_151601/autoortho.py", line 782, in domount
  File "/tmp/onefile_66002_1761770772_151601/autoortho_fuse.py", line 602, in run
  File "/tmp/onefile_66002_1761770772_151601/mfusepy.py", line 1237, in __init__
RuntimeError: 4

In my configuration programs.fuse.userAllowOther = true; is set, and I can confirm it works correctly by checking /etc/fuse.conf


I stumbled upon this file and Issue on GitHub and this, which indicates what the problem is


Attempt with autoPatchelfHook - Unsure why this fails

I have tried to rewrite derivation to patch the file directly, but I’m running into issues that go a little above my knowledge.

Currently, I have this

{ lib
, stdenv
, fetchzip
, autoPatchelfHook
, patchelf
, fuse3
, glib
, zlib
, mesa
, libGL
, libGLU
, libxkbcommon
, wayland
, xorg
, fontconfig
, freetype
, zstd
, dbus
}:

let
  pname = "auto-ortho";
  version = "1.3.3";
  src = fetchzip {
    url = "https://github.com/ProgrammingDinosaur/autoortho4xplane/releases/download/${version}/autoortho_linux_${version}.tar.gz";
    sha256 = "sha256-slH7Po9LYS1JjObKvMrRTmpI0RjnbbkI1Lik0yCUCr0=";
    stripRoot = false;
  };
in
stdenv.mkDerivation {
  inherit pname version src;

  nativeBuildInputs = [
    autoPatchelfHook
  ];

  buildInputs = [
    fuse3
    glib
    zlib
    mesa
    libGL
    libGLU
    libxkbcommon
    wayland
    xorg.libX11
    xorg.libXext
    xorg.libXrender
    xorg.libXtst
    xorg.libXi
    xorg.libXrandr
    fontconfig
    freetype
    zstd
    dbus
  ];

  installPhase = ''
    mkdir -p $out/bin
    cp autoortho_lin_${version}.bin $out/bin/auto-ortho
  '';

  meta = with lib; {
    homepage = "https://github.com/ProgrammingDinosaur/autoortho4xplane";
    description = "AutoOrtho for X-Plane — automatic orthophoto streaming";
    platforms = platforms.linux;
  };
}

Which returns the following:

$ strace -e openat ./result/bin/auto-ortho 
openat(AT_FDCWD, "/nix/store/pfn887ygmab6jmwcwj57phzljspgqnpn-libdbusmenu-glib-16.04.0/lib/glibc-hwcaps/x86-64-v3/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/pfn887ygmab6jmwcwj57phzljspgqnpn-libdbusmenu-glib-16.04.0/lib/glibc-hwcaps/x86-64-v2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/pfn887ygmab6jmwcwj57phzljspgqnpn-libdbusmenu-glib-16.04.0/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/daamdpmaz2vjvna55ccrc30qw3qb8h6d-glibc-2.40-66/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/daamdpmaz2vjvna55ccrc30qw3qb8h6d-glibc-2.40-66/lib/glibc-hwcaps/x86-64-v3/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/daamdpmaz2vjvna55ccrc30qw3qb8h6d-glibc-2.40-66/lib/glibc-hwcaps/x86-64-v2/libc.so.6", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/nix/store/daamdpmaz2vjvna55ccrc30qw3qb8h6d-glibc-2.40-66/lib/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/nix/store/xvshk5x5p4vin4pnnxqivjgfjpd0p0yh-auto-ortho-1.3.3/bin/auto-ortho", O_RDONLY) = 3
Error, couldn't find attached data header.
+++ exited with 2 +++

I’ve checked the glibc files with the directory linked in the logs, but the glibc-hwcaps/ directory does not seem to exist. Anyone have a clue? This was already quite the search for me, but by this point I’m unsure how to continue.

Thanks in advance!

For completeness sake, the error is from Nuitka, and can be found in source here:

I have not been able to find where this specific error is referenced however. I can see references to this function now, gonna have a look.

I also found this issue on the Nuitka repo, which would suggest the issue comes from the binary being ‘stripped’? I’ll have to start a search to find out what that means exactly…

I have tried to fix the issue by adding dontStrip = true; to the last .nix block in the post above, but sadly the same issue. In the build output this does result in the log line

stripping (with command strip and flags -S -p) in  /nix/store/pbzqk0bxf0xx775rb35ddpr2z0ma6r02-auto-ortho-1.3.3/bin

not being shown

Been experimenting with this derivation for a while now, decided to take the route of compiling with Nuitka locally (about 6 minutes compile time…), and it looks like I’ve finally booked result!

The inspiration lies in the README of nix-ld

Where is this useful?

While many proprietary packages in nixpkgs have already been patched with
autoPatchelfHook patching, there are cases where patching is not possible:

  • Use binary executable downloaded with third-party package managers (e.g. vscode, npm or pip) without having to patch them on every update.
  • Run games or proprietary software that attempts to verify its integrity.
  • Run programs that are too large for the nix store (e.g. FPGA IDEs).

While there are other solutions such as buildFHSUserEnv that restore a Linux file
hierarchy as found on common Linux systems (ld-linux-x86-64.so.2), these
sandboxes have their own weaknesses:

  • setuid binaries cannot be executed inside a fhsuserenv
  • inside a buildFHSUserEnv you can not use other sandbox tools like bwrap or ‘nix build’.
  • buildFHSUserEnv requires a subshell which does not work well with direnv

It looks like nix-ld adjusts the LD_LIBRARY_PATH, so that is what I’ve done in this derivation. Using wrapProgram I can set the env var only for this program, which seems to have worked.

auto-ortho/default.nix
{ lib
, python312Packages
, fetchFromGitHub
, fetchPypi
, fuse
, fuse3
, zlib
, glib
, libGL
, libxkbcommon
, xorg
, freetype
, dbus
, fontconfig
, wayland
, gccNGPackages_15
, kdePackages
, icu
, libgcc
}:

let
  refuse = python312Packages.buildPythonPackage rec {
    pname = "refuse";
    version = "0.0.4";
    format = "setuptools";

    src = fetchPypi {
      inherit pname version;
      sha256 = "sha256-t2DgcRs+07AGIc4v/q2PG7g4/8Zt9VmmS0bXTiY25po=";
    };

    disabled = python312Packages.pythonOlder "3.7";

    propagatedBuildInputs = [
      fuse
    ];

    pythonImportsCheck = [ "refuse" ];

    meta = with lib; {
      description = "Simple cross-platform ctypes bindings for libfuse / FUSE for macOS / WinFsp";
      homepage = "https://github.com/pleiszenburg/refuse";
      license = licenses.isc;
    };
  };

  mfusepy = python312Packages.buildPythonPackage rec {
    pname = "mfusepy";
    version = "1.1.1";
    format = "pyproject";

    src = fetchPypi {
      inherit pname version;
      hash = "sha256-Nkdyem53ddR/3v9hNVk18e1e2zHFEk0jMTjyqR6gH58=";
    };

    propagatedBuildInputs = [ python312Packages.setuptools fuse ];

    # No tests included
    doCheck = false;

    # On macOS, users are expected to install macFUSE. This means fusepy should
    # be able to find libfuse in /usr/local/lib.
    patchPhase = ''
      substituteInPlace mfusepy.py \
        --replace "find_library('fuse')" "'${fuse.out}/lib/libfuse.so'" \
    '';

    meta = with lib; {
      description = "Ctypes bindings for the high-level API in libfuse 2 and 3";
      longDescription = ''
        mfusepy is a Python module that provides a simple interface to FUSE and macFUSE.
        It's just one file and is implemented using ctypes to use libfuse.
      '';
      homepage = "https://github.com/mxmlnkn/mfusepy";
      license = licenses.isc;
      platforms = platforms.unix;
    };
  };
in
python312Packages.buildPythonApplication rec {
  pname = "auto-ortho";
  version = "1.4.0";

  src = fetchFromGitHub {
    owner = "ProgrammingDinosaur";
    repo = "autoortho4xplane";
    rev = version;
    sha256 = "sha256-S6aKFcYWpkhh4hxHm0vNys/Ao86UknqoRkCEqtd8rlQ=";
  };

  format = "other";

  nativeBuildInputs = [
    python312Packages.nuitka
  ];

  linkedSharedObjects = [
    python312Packages.shiboken6
    python312Packages.pyside6
    kdePackages.qtbase
    icu
    libGL
    zlib
    libgcc.lib
    wayland
    libxkbcommon
  ];

  buildInputs = [
    fuse
    glib
    libGL
    libxkbcommon
    fontconfig
    xorg.libX11
    freetype
    dbus
    wayland
  ];

  propagatedBuildInputs = with python312Packages; [
    psutil
    pyside6
    flask
    flask-socketio
    eventlet
    packaging
    requests
    geocoder
    pytest
    refuse
    mfusepy

    # from requirements-build
    zstandard
    ordered-set
  ];

  buildPhase = ''
    python3 -m nuitka \
      --linux-icon=autoortho/imgs/ao-icon.ico \
      --enable-plugin=pyside6 \
      --include-data-file=./autoortho/.version*=. \
      --include-data-file=./autoortho/templates/*.html=templates/ \
      --user-package-configuration-file=nuitka-package.config.yml \
      --onefile \
    ./autoortho/__main__.py -o autoortho_lin.bin
  '';

  installPhase = ''
    mkdir -p $out/bin
    mv autoortho_lin.bin $out/bin/${pname} -v
    chmod +x $out/bin/${pname}
  '';

  postFixup = ''
    wrapProgram $out/bin/${pname} \
      --set LD_LIBRARY_PATH $LD_LIBRARY_PATH:${lib.makeLibraryPath linkedSharedObjects}
  '';

  dontStrip = true;

  meta = with lib; {
    homepage = "https://github.com/ProgrammingDinosaur/autoortho4xplane";
    description = "AutoOrtho for X-Plane — automatic orthophoto streaming";
    platforms = platforms.linux;
  };
}

I’m not sure if this is the optimal combination of packages, this was the first set I’ve gotten it to work with. Feel free to optimise further.

There does seem to be a difference in UI, possibly hinting at another library missing.