Skip to content

On Linux the application data and log folders resolve to /usr/share, which a normal user cannot write #135

Description

@karlspace

Problem

The settings stores and BGLogger keep their files under Environment.SpecialFolder.CommonApplicationData. On Linux and macOS, .NET maps that folder to the fixed path /usr/share (dotnet/runtime Environment.GetFolderPathCore.Unix.cs:55, release/10.0; the line sits outside #if TARGET_OSX, so macOS gets it too). /usr/share is owned by root (drwxr-xr-x root root) and is meant for "architecture-independent resources of packages" (UAPI.9). Persistent system data belongs in /var/lib.

Affected code (main 02325dd; these files are unchanged since v3.0.8):

  • src/BAUERGROUP.Shared.Core/Application/ApplicationFolders.cs:62, :73, :131-141: ExecutionAutomaticApplicationDataFolder returns /usr/share/<App> unless BAUERGROUP_ROAMINGAPPLICATIONDATA=TRUE.
  • Application/ApplicationConfigurationStoreJSON.cs:110-114, ApplicationConfigurationStoreXML.cs:82-86, ApplicationConfigurationStoreBinary.cs:96-102: the getter for the default file name creates that folder, so the parameterless constructor already throws.
  • Logging/BGLoggerConfiguration.cs:33-37: the default LogDirectory is computed on its own from CommonApplicationData. It does not honour BAUERGROUP_ROAMINGAPPLICATIONDATA.
  • Logging/BGLoggerConfiguration.cs:547, :550: FileName and ArchiveFileName join the parts with \, so on Linux even a writable LogDirectory gets no log inside it. That is a separate cause with its own fix, tracked in BGLogger file target uses a hard-coded backslash, so on Linux/macOS the log is written as a file named "Logging\<App>.log" #134.
  • The same folder is also used by src/BAUERGROUP.Shared.Data/EmbeddedDatabase/ConcurrentPersistentDictionary.cs:80.

Observed on 2026-09-15 with a probe (Shared.Core 3.0.8, net10.0, self-contained linux-x64) on Ubuntu 26.04 under WSL2, running as a normal user (uid 1000):

Case Result
ApplicationFolders.ExecutionAutomaticApplicationDataFolder /usr/share/DirsProbe
new ApplicationConfigurationStoreJSON<T>() System.UnauthorizedAccessException: Access to the path '/usr/share/DirsProbe' is denied.
BGLogger.Info(...) with the default LogDirectory (/usr/share/DirsProbe/Logging) no log file is written and no error is raised
The same with BAUERGROUP_ROAMINGAPPLICATIONDATA=TRUE settings are saved to ~/.config/DirsProbe/DirsProbe.Configuration.json, but LogDirectory is still /usr/share/DirsProbe/Logging
LogDirectory = "/tmp/dirsprobe/Logging" (writable) no Logging directory; /tmp/dirsprobe contains one file literally named Logging\DirsProbe.log (#134)
Environment.GetFolderPath(SpecialFolder.LocalApplicationData) '', because ~/.local/share did not exist yet. With the default option the runtime returns an empty string for a path that is not readable (GetFolderPathCore.Unix.cs:28-40).

Running as root would hide the problem, but the application would then write its settings and logs into /usr/share.

Proposal

Use one resolver for the settings stores, the log directory and the Sentry cache (#133):

  1. ApplicationFolders.ExecutionAutomaticApplicationDataFolder is the only rule:
    • Windows: unchanged (%ProgramData%\<App>, or %APPDATA%\<App> with the variable).
    • BAUERGROUP_ROAMINGAPPLICATIONDATA=TRUE, any OS: unchanged (SpecialFolder.ApplicationData, which is $XDG_CONFIG_HOME or ~/.config on Linux). Nothing moves for anyone who already uses the variable.
    • Otherwise, when not on Windows:
      • Use /var/lib/<App> if that directory exists. The installer or package creates it with the ownership the station needs, e.g. install -d -o <user> /var/lib/<App>, or systemd StateDirectory=<App>.
      • Otherwise use the per-user folder Environment.GetFolderPath(SpecialFolder.LocalApplicationData, SpecialFolderOption.DoNotVerify) plus <App>. That is $XDG_DATA_HOME or ~/.local/share on Linux (runtime :124-131), and ~/Library/Application Support on macOS (:101-103). DoNotVerify avoids the empty string shown above. The overload is available in netstandard2.0.
  2. Log directory: the default LogDirectory becomes Path.Combine(ExecutionAutomaticApplicationDataFolder, "Logging") instead of its own CommonApplicationData lookup.
  3. Separator: not part of this issue; see BGLogger file target uses a hard-coded backslash, so on Linux/macOS the log is written as a file named "Logging\<App>.log" #134. Both are needed before a Linux station has a log file.
  4. Directory creation: resolving a path should not create the folder. Create it on save, as ApplicationConfigurationStoreJSON.Save already does at :85-89. A location that cannot be written then fails when writing, not in the constructor.
  5. Tests:
    • the resolver, with the OS, environment variables, home folder and the existence check injected
    • the file target writing into a real subdirectory on Linux CI

Trade-offs:

  • Existence check for /var/lib/<App>, no write test. It is deterministic and cheap. A folder created with the wrong owner fails visibly when saving, instead of silently falling back to a per-user folder. A silent fallback would split one station's settings by login user.

  • Per-user fallback. Two OS users on one station get separate settings, as on Windows with the variable today.

  • Behaviour change on Windows. Point 2 changes two things on Windows:

    • With BAUERGROUP_ROAMINGAPPLICATIONDATA=TRUE, the log follows the variable to %APPDATA%\<App>\Logging.
    • The folder is named after the executable (ApplicationFolders), not after BGLoggerConfiguration.ApplicationName. An application that sets a custom ApplicationName would find its log in a different folder.

    Both should be in the release notes. If that is unwanted, apply point 2 on non-Windows only.

  • Logs stay in the data folder. They do not go to /var/log or $XDG_STATE_HOME. This keeps one folder per application with the same layout as on Windows, and Enable the Sentry offline cache by default, keeping 50 reports #133's cache fits in it.

  • macOS follows the same code path but has not been tested.

Related

Context

bgIndustrialAutomation Client (bauer-group/OT-AutomationClient) is preparing Linux stations.

  • Its settings store gets its path from ApplicationFolders.ExecutionAutomaticApplicationDataFolder (src/BAUERGROUP.bgIndustrialAutomation.Client.Core/Configuration/ApplicationSettingsStore.cs:80-83, origin/main 3f6fb39).
  • It derives its Sentry cache folder from the same path (src/BAUERGROUP.bgIndustrialAutomation.Client.App/ErrorReporting.cs:362-364).

On Linux, saving settings throws UnauthorizedAccessException, so a station always restarts on defaults, and no log file is written.

The only workaround without code changes is BAUERGROUP_ROAMINGAPPLICATIONDATA=TRUE. It moves the settings and the cache to ~/.config/<App>/, but not the log. The application's documentation lists Linux as untested and says the settings, log and cache would need a location other than /usr/share (docs/ERROR-REPORTING.md:1166-1168).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingreleased

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions