File permissions not captured from .tcattr

Hello,

I recently encountered an issue and from investigating it i realised that for some reason the file permissions that i list in my .tcattr for files that go into /etc/ folder doesn’t apply on the final system.

My changes directory file structure:

changes_device_metrics_iso/
└── usr
    ├── etc
    │   ├── fluent-bit
    │   │   ├── custom-parsers.conf
    │   │   ├── fluent-bit.conf
    │   │   └── fluent-bit.d
    │   │       └── custom-invendor-containers.conf
    │   └── .tcattr
    └── lib
        ├── systemd
        │   └── system
        │       └── fluent-bit.service
        └── .tcattr

usr/etc/.tcattr file:

# file: fluent-bit/fluent-bit.conf
# owner: 0
# group: 0
user::rwx
group::r-x
other::r-x

# file: fluent-bit/fluent-bit.d/custom-invendor-containers.conf
# owner: 0
# group: 0
user::rw-
group::r--
other::r--

# file: fluent-bit/custom-parsers.conf
# owner: 0
# group: 0
user::rw-
group::r--
other::r--


tcbuild.yaml (relevant section):

customization:
  device-tree:
    include-dirs:
      - linux/arch/arm64/boot/dts/ti/
      - linux/include/
    custom: linux/arch/arm64/boot/dts/ti/k3-am625-verdin-wifi-mallow.dts
    overlays:
      clear: true
      add:
        - device-tree-overlays/verdin-am62_pin_names_overlay.dts
        - device-tree-overlays/invendor_verdin-am62_overlay.dts
  filesystem:
    - changes_password_iso/
    - changes_device_metrics_iso/

build command:

source .conf/tcb-env-setup.sh  -s $SCRIPTDIR/storage -t 3.15
torizoncore-builder build --file $TCBUILD_FILE

On the freshly built system:

root@verdin-am62-15645559:/var/rootdirs/home/torizon# uname -a
Linux verdin-am62-15645559 6.6.138-7.6.1 #1-Torizon SMP PREEMPT Fri May  8 07:27:41 UTC 2026 aarch64 GNU/Linux
root@verdin-am62-15645559:/var/rootdirs/home/torizon# tdx-info 

Software summary
------------------------------------------------------------
Bootloader:               U-Boot
Kernel version:           6.6.138-7.6.1 #1-Torizon SMP PREEMPT Fri May  8 07:27:41 UTC 2026
Kernel command line:      root=LABEL=otaroot rootfstype=ext4 quiet logo.nologo vt.global_cursor_default=0 plymouth.ignore-serial-consoles splash fbcon=map:3 ostree=/ostree/boot.1/torizon/924a112d873d82de6dd7eb68769c91104084f6470f9112bfda5bbc372066b893/0 firmware_class.path=/etc/btfw/
Distro name:              NAME="Torizon OS"
Distro version:           VERSION_ID=7.6.1-build.38
Distro variant:           VARIANT="Docker"
Hostname:                 verdin-am62-15645559
------------------------------------------------------------

Hardware info
------------------------------------------------------------
HW model:                 Toradex Verdin AM62 WB on Mallow Board
Toradex version:          0075 V1.2A
Serial number:            15645559
Processor arch:           aarch64
------------------------------------------------------------
root@verdin-am62-15645559:/var/rootdirs/home/torizon# stat /etc/fluent-bit/fluent-bit.conf 
  File: /etc/fluent-bit/fluent-bit.conf
  Size: 2827      	Blocks: 8          IO Block: 4096   regular file
Device: 179,1	Inode: 141039      Links: 1
Access: (0770/-rwxrwx---)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-03-09 12:35:21.892000000 +0000
Modify: 2026-06-25 12:43:55.158819060 +0000
Change: 2018-03-09 12:35:21.892000000 +0000
 Birth: 2018-03-09 12:35:21.892000000 +0000
root@verdin-am62-15645559:/var/rootdirs/home/torizon# stat /etc/fluent-bit/fluent-bit.d/custom-invendor-containers.conf 
  File: /etc/fluent-bit/fluent-bit.d/custom-invendor-containers.conf
  Size: 4511      	Blocks: 16         IO Block: 4096   regular file
Device: 179,1	Inode: 141038      Links: 1
Access: (0660/-rw-rw----)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-03-09 12:35:21.888000000 +0000
Modify: 2026-06-25 12:43:55.159083223 +0000
Change: 2018-03-09 12:35:21.888000000 +0000
 Birth: 2018-03-09 12:35:21.888000000 +0000
root@verdin-am62-15645559:/var/rootdirs/home/torizon# stat /etc/fluent-bit/custom-parsers.conf 
  File: /etc/fluent-bit/custom-parsers.conf
  Size: 91        	Blocks: 8          IO Block: 4096   regular file
Device: 179,1	Inode: 141040      Links: 1
Access: (0660/-rw-rw----)  Uid: (    0/    root)   Gid: (    0/    root)
Access: 2018-03-09 12:35:21.892000000 +0000
Modify: 2026-06-25 12:43:55.158627776 +0000
Change: 2018-03-09 12:35:21.892000000 +0000
 Birth: 2018-03-09 12:35:21.892000000 +0000

It seems like the file permissions set in my .tcattr are not surviving the ostree merge (file is set to default permissions used by ostree):

sudo ostree --repo=storage/ostree-archive ls cabinet-fw-update /usr/etc/fluent-bit/fluent-bit.conf
-00770 0 0   2827 /usr/etc/fluent-bit/fluent-bit.conf

But I can’t figure out why… My .tcattr has the same structure as one generated by isolate command (and same permissions). My changes_password_iso/ directory also has some modifications to the /etc/ folder, could they be clashing somehow?

Currently this is not really an issue for the fluent-bit files, but this becomes a problem when i try to add auto-provisioning-overrides.sh to /etc/sota for example, as default permissions set by the ostree don’t pass the auto-provisioning service permission check…

Hi, I’m the Toradex AI Assistant.

I might make mistakes or occasionally get you wrong. Our support team will step in if needed.


The issue you’re encountering is related to the .tcattr file placement. According to the TorizonCore Builder documentation, the .tcattr file should be placed at the root of your changes directory, not inside subdirectories(1).

The Problem

Your current structure has:

changes_device_metrics_iso/
└── usr
    ├── etc
    │   └── .tcattr    # ❌ Wrong location
    └── lib
        └── .tcattr    # ❌ Wrong location

The .tcattr file paths are relative to the root of the changes directory, not to subdirectories within it(1).

The Solution

You need to move your .tcattr file to the root of changes_device_metrics_iso/ and adjust the file paths accordingly:

changes_device_metrics_iso/
├── .tcattr           # ✅ Correct location
└── usr
    ├── etc
    │   └── fluent-bit/
    └── lib
        └── systemd/

And update your .tcattr content to reference the full paths from the changes directory root:

# file: usr/etc/fluent-bit/fluent-bit.conf
# owner: 0
# group: 0
user::rwx
group::r-x
other::r-x

# file: usr/etc/fluent-bit/fluent-bit.d/custom-invendor-containers.conf
# owner: 0
# group: 0
user::rw-
group::r--
other::r--

# file: usr/etc/fluent-bit/custom-parsers.conf
# owner: 0
# group: 0
user::rw-
group::r--
other::r--

Notice that the paths now start with usr/etc/ instead of just the filename(1).

Important Notes

  1. All .tcattr files are not committed to the OSTree rootfs, so they don’t exist in your final Torizon OS image(1).

  2. Files not listed in .tcattr get default attributes: regular files (root:root, 0660), executable files (root:root, 0770), and directories (root:root, 0755)(1).

  3. Since you have multiple changes directories (changes_password_iso/ and changes_device_metrics_iso/), each should have its own .tcattr file at its root if you need to set custom attributes for files within that directory(1).

This should resolve your permission issues, including the one with auto-provisioning-overrides.sh in /etc/sota.

Best regards,
Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

@ToradexAI

Okay, creating a .tcattr in root of changes_device_metrics_iso/ directory and removing the .tcattr files in usr/etc and usr/lib seemed to have fixed the issue:

sudo ostree --repo=storage/ostree-archive ls cabinet-fw-update /usr/etc/fluent-bit/fluent-bit.conf
-00755 0 0   2827 /usr/etc/fluent-bit/fluent-bit.conf

But there are still some things that don’t make sense to me.

  1. If you use torizoncorebuilder isolate command, then it creates .tcattr files not in the root changes directory but in usr/*/ . Does that mean the output of isolate command is no longer correct?
  2. I have changes_password_iso/ directory which was generated by the isolate command with the following structure:
changes_password_iso/
└── usr
    └── etc
        ├── locale.conf
        ├── .passwd_changed
        ├── shadow
        ├── .tcattr
        └── vconsole.conf

but despite its presumably incorrect location of the .tcattr file, the file permissions are retained correctly:

stas@stas-ubuntu:~/work/TorizonCoreBuilder$ sudo ostree --repo=storage/ostree-archive ls cabinet-fw-update /usr/etc/shadow
-00400 0 0    134 /usr/etc/shadow
stas@stas-ubuntu:~/work/TorizonCoreBuilder$ sudo ostree --repo=storage/ostree-archive ls cabinet-fw-update /usr/etc/locale.conf
-00644 0 0     75 /usr/etc/locale.conf
stas@stas-ubuntu:~/work/TorizonCoreBuilder$ sudo ostree --repo=storage/ostree-archive ls cabinet-fw-update /usr/etc/.passwd_changed
-00644 0 0      0 /usr/etc/.passwd_changed

Is there something special about it?

Great questions! Let me clarify what’s happening here.

1. The isolate Command Behavior

You’re correct to notice this inconsistency. According to the documentation, the isolate command does create a .tcattr file in /etc/.tcattr (which becomes usr/etc/.tcattr in your changes directory)(1). The documentation states:

“Files and directories permissions and ownership are saved in the /etc/.tcattr metadata file together with all other isolated files.”(1)

However, the documentation also clearly states that when using --changes-directory with the union command, the .tcattr file should be at the root of the changes directory(2).

This appears to be a discrepancy in how isolate generates the structure versus how union expects to consume it.

2. Why Your changes_password_iso/ Works Despite the “Wrong” Location

This is the key insight: the isolate command only captures changes from /etc/, and it stores them with the .tcattr file at usr/etc/.tcattr(1).

When TorizonCore Builder processes changes from isolate, it has special handling for the /etc/ directory structure because that’s the standard output format of the isolate command. The tool knows to look for .tcattr at usr/etc/.tcattr when processing isolated /etc/ changes.

However, when you’re creating custom changes directories manually (like your changes_device_metrics_iso/), you need to follow the general rule: .tcattr at the root of the changes directory(2).

Summary

  • For directories created by isolate: The .tcattr file at usr/etc/.tcattr works because the tool has special handling for this structure(1)
  • For manually created changes directories: Place .tcattr at the root of your changes directory(2)
  • Your fix was correct: Moving .tcattr to the root of changes_device_metrics_iso/ follows the documented approach for custom changes directories

The behavior you observed is consistent with the tool’s design, even though it might seem inconsistent at first glance. The isolate command output is a special case that the tool handles differently from general custom changes directories.

Best regards,
Toradex AI Assistant.

**


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

@ToradexAI

Then how does the torizoncore builder union command differentiates if the changes directory was created by isolate command or manually? In the end, these are just directories listed in the tcbuild.yaml file…

You're absolutely right to question this—I need to correct my previous explanation.

The Actual Behavior

Looking at the documentation more carefully, TorizonCore Builder does NOT differentiate between directories created by isolate and manually created directories. The union command processes all changes directories listed in the configuration file the same way[(1)](https://developer.toradex.com/torizon/os-customization/torizoncore-builder-tool-commands-manual).

The documentation states that when using the union command with --changes-directory, the .tcattr metadata file is used to set file attributes[(1)](https://developer.toradex.com/torizon/os-customization/torizoncore-builder-tool-commands-manual). However, it doesn't specify a special handling path for isolate-generated directories versus manual ones.

Re-examining Your Situation

Given that both directories are processed identically, let me reconsider why changes_password_iso/ worked with .tcattr at usr/etc/.tcattr:

Possibility 1: The .tcattr file format allows relative paths from where it's located. So usr/etc/.tcattr might reference files relative to usr/etc/ (like shadow, locale.conf), while a root-level .tcattr would need full paths (like usr/etc/shadow).

Possibility 2: Your changes_password_iso/.tcattr file might have been using relative paths that worked from its location, while your changes_device_metrics_iso/ had .tcattr files with paths that didn't match the actual file locations.

The Documented Approach

According to the documentation, the isolate command creates /etc/.tcattr (which becomes usr/etc/.tcattr in the changes directory)[(2)](https://developer.toradex.com/torizon/os-customization/use-cases/capturing-changes-in-the-configuration-of-a-board-on-torizoncore). The union command documentation mentions that credentials (file permissions and ownership) are kept using the .tcattr file automatically[(2)](https://developer.toradex.com/torizon/os-customization/use-cases/capturing-changes-in-the-configuration-of-a-board-on-torizoncore).

The safest and most consistent approach is:

  • Place .tcattr at the root of your changes directory
  • Use full paths from the changes directory root in the .tcattr file (e.g., usr/etc/fluent-bit/fluent-bit.conf)

This is what resolved your issue with changes_device_metrics_iso/, and this approach is explicitly supported by the documentation[(1)](https://developer.toradex.com/torizon/os-customization/torizoncore-builder-tool-commands-manual).

I apologize for the confusion in my previous response about "special handling"—that was incorrect. The tool processes all changes directories uniformly.

Best regards,
Toradex AI Assistant.


Did you like my answer? :+1: :-1:

If I answered your question, please tick the “Solution” checkbox.

For follow-up questions to me, please use @ToradexAI in your post.

Greetings @stasgil,

I believe the issue was not related to the location of your .tcattr file, but rather the number of them. On initial inspection I believe the tool only works correctly when there’s 1 .tcattr file present per changes directory. Regardless of it’s location.

In your original setup you had two .tcattr files. Which is why it worked as expected when you condensed it to a single file later on. This would match what the isolate command does as I believe it only ever creates 1 .tcattr file when producing a changes directory.

Best Regards,
Jeremias

I see, thank you for clarification

Glad we were able to help clarify.

Best Regards,
Jeremias