Fixing permissions when program copies out of Nix store

I’ve noticed recently that Bitwig Studio, starting with version 5.2, has the automatic Onset detection broken in NixOS. The chain of failure seems to be as follows:

  • Bitwig Studio resides in the Nix Store, all non-executable files have “444” permissions
  • For the Onset detection feature, Bitwig uses an external plugin host which gets the operation details using a config file
  • There are several template config files in the Nix Store

Now, when a new Onset detection is to be done:

  • Bitwig copies the template config file to $HOME/.BitwigStudio/cache/audio/<some-temporary-folter>/<some-temporary-filename>, crucially, this preserves the read-only permissions, the file appears here with permissions “444”:
-r--r--r-- 1 jan users 145 Jan  1  1970 onsets-2A7BB5059582D44CCC0A5EAA1D30E9F7-config
  • Bitwig now tries to modify this file to insert the currently chosen UI parameters. Since the file is read-only, this fails, causing the whole operation to fail

From the logs (ignore version “5.3-beta” there, it’s the same issue with 5.2)

[2024-11-28 10:45:10.722 float-sample-analysis error] Error analyzing onsets for file /home/jan/.BitwigStudio/temp-projects/fc2d7061-3a79-4250-b079-bee97d6cffa3/bounce/Drum and Bass Kit 1-bounce-1.wav with transform file /nix/store/hk4sk7wffbfzy4f0d75mrqaas53h2n69-bitwig-studio-5.3-beta-1/libexec/resources/VampTransforms/BeatDetection/Default.vamp:
  xI: Could not analyze onsets: Error running Bitwig Vamp Plug-in Host: /home/jan/.BitwigStudio/cache/audio/7949815B52CE8DF24FFA2FAFC747AA2C/beats-A7A355B9E2DCF301708B8381C4F7DEE3-config (Permission denied)
  at xC.NX1(SourceFile:45)
  at xl.NX1(SourceFile:1568)
  at xl.NX1(SourceFile:1445)
  at YuN.NX1(SourceFile:74)
  at xl.NX1(SourceFile:1443)
  at xh.run(SourceFile:72)
  at wCl.run(SourceFile:307)
  at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source)
  at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source)
  at java.base/java.lang.Thread.run(Unknown Source)

I am in contact with the manufacturer, ideally the program would just ensure the file to be writable after copying, but I’m not sure if that is going anywhere.

So I’m wondering if there is anything that I could do from my side to fix this. Is there maybe some ACL magic that could be done so that all files under a certain folder will always be writeable, regardless of what their permissions were before they were copied there?

If the program explicitly sets the permissions to those of the source file, I don’t think there’s anything you could do.

It could be that the umask is at play here though or that you could override the behaviour seen using ACLs (copying files and inherit permissions of target folder - Ask Ubuntu).

You’d have to try playing around with that.

What I’ve also been looking for for another purpose is a way to bind-mount a directory (e.g. the Nix store) inside of a bubblewrap container such that it looks like its permissions are +w to applications. (Attempting to write would obviously still fail.) If you find anything like that, let me know.

1 Like

If the program explicitly sets the permissions to those of the source file, I don’t think there’s anything you could do.

That is exactly what it looks like. Here is a bit more systrace output:

[pid 641141] statx(AT_FDCWD, "/home/jan/.BitwigStudio/cache/audio/D620CF5F832A87206E3593A0C743F88C/beats-A7A355B9E2DCF301708B8381C4F7DEE3-config", AT_STATX_SYNC_AS_STAT|AT_SYMLINK_NOFOLLOW, STATX_ALL,  <unfinished ...>
[pid 641141] <... statx resumed>0x7f14310bbe00) = -1 ENOENT (No such file or directory)

[pid 641141] openat(AT_FDCWD, "/nix/store/hk4sk7wffbfzy4f0d75mrqaas53h2n69-bitwig-studio-5.3-beta-1/libexec/resources/VampTransforms/BeatDetection/Default.vamp", O_RDONLY <unfinished ...>
[pid 641141] <... openat resumed>)      = 73
[pid 641141] openat(AT_FDCWD, "/home/jan/.BitwigStudio/cache/audio/D620CF5F832A87206E3593A0C743F88C/beats-A7A355B9E2DCF301708B8381C4F7DEE3-config", O_WRONLY|O_CREAT|O_EXCL, 0100444 <unfinished ...>
[pid 641141] <... openat resumed>)      = 84

[pid 641141] copy_file_range(73, NULL, 84, NULL, 2147479552, 0 <unfinished ...>
[pid 641141] <... copy_file_range resumed>) = 93
[pid 641141] copy_file_range(73, NULL, 84, NULL, 2147479552, 0) = 0

[pid 641141] fchown(84, 0, 0 <unfinished ...>
[pid 641141] <... fchown resumed>)      = -1 EPERM (Operation not permitted)
[pid 641141] flistxattr(73,  <unfinished ...>
[pid 641141] <... flistxattr resumed>"", 1024) = 0
[pid 641141] utimensat(84, NULL, [{tv_sec=1732284948, tv_nsec=0} /* 2024-11-22T15:15:48+0100 */, {tv_sec=1, tv_nsec=0} /* 1970-01-01T01:00:01+0100 */], 0 <unfinished ...>
[pid 641141] <... utimensat resumed>)   = 0

[pid 641141] close(84 <unfinished ...>
[pid 641141] <... close resumed>)       = 0
[pid 641141] close(73 <unfinished ...>
[pid 641141] <... close resumed>)       = 0

And then later:

[pid 641141] openat(AT_FDCWD, "/home/jan/.BitwigStudio/cache/audio/D620CF5F832A87206E3593A0C743F88C/beats-A7A355B9E2DCF301708B8381C4F7DEE3-config", O_WRONLY|O_CREAT|O_APPEND, 0666 <unfinished ...>
[pid 641141] <... openat resumed>)      = -1 EACCES (Permission denied)

Looks like it first checks if the tempfile is already there, if it isn’t it opens the template file and the destination file with permissions “444” (I am not entirely sure what the 0100 before that means exactly) and then uses copy_file_range to do the copying. After everything is closed, it then tries to open that same file with access permissions 0666 in order to modify, which then fails.

I did some digging into Java, and it seems that the most likely candidate to use copy_file_range in the backend is java.nio.file.Files.copy which has a COPY_ATTRIBUTES flag which is likely set here since it also preserves the 1970s access time of the Nix store.

Seems like at this point there are a couple of options:

  • Use something like eBPF to intercept the openat syscall, filter anything that goes to Bitwig cache directory and (if possible) modify the parameters to include write permissions
  • Look into the Java code and see if we can modify the parameter to the “copy”-call to not include COPY_ATTRIBUTES. However, the Java code is very likely obfuscated, judging from the stack trace in the original post, and I’m not terribly well versed in reverse engineering Java

Both solutions do not sound terribly nice (especially w.r.t. the official Bitwig package in Nixpkgs), but I’ll keep investigating. I’m leaning towards trying first to modify the Java code itself, so if anyone has some tool pointers here, that would be great.

I just remembered that I was once told about: aspect-oriented programming where you can cause arbitrary effects whenever some event happens; allowing you to hook into e.g. function calls. This was told to me in the context of Java, so there you might be some relatively standard tooling available that would enable you to override the COPY_ATTRIBUTES usage in any Java program or just run (effectively) chmod afterwards; undoing the file mode copy.

Another idea that sprung to mind would be to somehow make it seem like the program does not own the files it just copied which might be possible with namespace/container magic. It wouldn’t be able to set the mode if it doesn’t own the file.

I will look into aspects, and especially if it’s possible to retro-fit them into existing binaries. Do you happen to have any links here?

Meanwhile, you mentioning Bubblewrap got me an idea. First of all, I created a temporary directory, for now /tmp/VampTransforms, but I’ll properly use mktemp later. I then use bindfs to mount the part of the store-path there that I require to have write-perms, making use of the --perms flag to modify the permissions:

bindfs --no-allow-other --perms=u+w /nix/store/hk4sk7wffbfzy4f0d75mrqaas53h2n69-bitwig-studio-5.3-beta-1/libexec/resources/VampTransforms /tmp/VampTransforms

This gives me the store-folder in the new location with faked write permissions. Then I use Bubblewrap to launch Bitwig like so:

bwrap --bind / / --bind /tmp/VampTransforms /nix/store/hk4sk7wffbfzy4f0d75mrqaas53h2n69-bitwig-studio-5.3-beta-1/libexec/resources/VampTransforms bitwig-studio

Lo and behold, Onset and Beat detection are working again and from the little I played with it, I don’t see anything else broken.

Also, I just got a response from the support and they basically give me the whole “Use Ubuntu or you are on your own”-Spiel. I’ll try one last mail with the recent information, but ultimately I don’t think they will care.

2 Likes

No, sorry; I only know something roughly like this is a thing but have no further experience with it.

Ah right, bindfs is a thing. Though AFAIK it has caused some issues within FHSenvs before: people using impermance in home-manager keep complaining about Steam games not functioning.
Performance is also bad because it’s FUSE but that doesn’t matter in this case. It does matter in my use-case which is why I immediately discarded the idea and didn’t think of it here.

If this is just a few small config file templates, you could also just copy them to a user-owned tmpdir, chmod +w and use a regular bubblewrap bind-mount to place the tmpdir in bitwig’s store path.

You could even integrate that hack into the upstream package itself; that’d be the best place to do such a thing actually. It currently patchelfs but it needs to do so manually which looks quite complicated, so buildFHSEnv could be simpler here.

Yes, that’s what I ended up doing. It was just two files with 35kb in total, so no need to open the whole FUSE can of worms. The final script that takes an existing Bitwig derivation and wraps it is here:

Many thanks for the discussion. I will test this a bit within my Audio flake and contribute back to Nixpkgs once I am sure it doesn’t break stuff.

1 Like