This blog entry is a new one. No re-publication of an old blog entry. It’s not one of the newest features in Solaris, but in comparison to other features, it’s still the new kid on the block and one of the less known ones. It’s about retention of files in ZFS. I think it was introduced to the ZFS in Oracle Solaris in 2022. It’s in the ZFS Storage Appliance since then. I will give you an overview of the feature in this blog entry.

Data retention?

So, what is retention? Wikipedia defines it as “Data retention defines the policies of persistent data and records management for meeting legal and business data archival requirements.”

In a company you often need store data for certain amounts of time. Often because of regulatory requirements. In this time you don’t want your data modified in this time? You want to be sure that the DICOM files of your MRI are still there in a number of years and haven’t been edited or deleted. You want the documents about repairs and spare parts of the aircraft you are using to get into your vacation stored in a way, that don’t allow deletion and editing. You want to be sure, that the documents you are using for your or your companies declaration to the tax authority is stored in a way that ensures you can’t delete it or modify it. All this areas are heavily regulated, there are many organizatorital rules in place, you can support with technology.

In recent time another usecase got equally important. Fore example: You want to ensure, that your backup isn’t modifed in the retention time of your backup by a malicious process encrypting all your data. It can be even a safety net against user error: For example, accidentally deleting files or filesystems because you clicked too high or too low and hit the filesystem with the backups.

File Retention in ZFS

A filesystem with retention capabilities can help with implementing and enforcing a data retention policy. Like not allowing the deletion of a file for a certain time on a operating system level, so even a shell user can’t delete or modify it. However, as usual, the following is valid: Such a file system is a technology helping you to implement a policy; it’s not the policy itself. Technologies must be embedded in policies.

In regards of ZFS file retention Solaris enforces the configured policies with no way around it, except you configured it this way to allow this for certain users (you will learn about this later).

You have to see this feature in tandem with other features like auditing allowing to log every successful or unsuccessful attempt to change configurations.

This tutorial

In this tutorial I will show a subset of the capabilities of the file retention feature of the ZFS filesystem. As the ZFS Storage Appliance uses Solaris under the hood, you can assume it’s the feature enabling the retention feature of the ZFS Storage appliance.

Preparations

In my example I will a ZFS pool based on files in a ZFS filesystem. Just to make things easier for me

root@testbed:~# mkfile 1g testpool
root@testbed:~# lofiadm -a /root/testpool
/dev/lofi/1
root@testbed:~# zpool create testpool /dev/lofi/1

Okay, now I will create a pool with a retention policy. I will explain mandatory later.

root@testbed:~# zfs create -o retention.policy=mandatory testpool/mandatory
cannot create 'testpool/mandatory': mandatory retention may not be enabled on a pool with no redundancy

Okay, first thing to keep in mind. No mandatory retention without a pool with redundancy, so either mirror or RAIDZ1-3

root@testbed:~# mkfile 1g testpool_mirror
root@testbed:~# lofiadm -a /root/testpool_mirror
/dev/lofi/2
root@testbed:~# zpool attach testpool /dev/lofi/1 /dev/lofi/2
root@testbed:~# zpool status testpool
    pool: testpool
      id: 4597756362022219442
   state: ONLINE
    scan: resilvered 88K in 1s with 0 errors on Fri Apr 18 20:41:01 2025
  config:

        NAME             STATE      READ WRITE CKSUM
        testpool         ONLINE        0     0     0
          mirror-0       ONLINE        0     0     0
            /dev/lofi/1  ONLINE        0     0     0
            /dev/lofi/2  ONLINE        0     0     0

  errors: No known data errors

Retention types

Solaris knows two types of retention.

root@testbed:~# zfs create -o retention.policy=mandatory testpool/mandatory
root@testbed:~# zfs create -o retention.policy=privileged testpool/privileged

The difference is rather simple. When you use privileged retention, a sufficiently privileged user can delete files even when the retention time hasn’t passed and so override the retention. You can even destroy the whole filesystem or pool. The necessary privileged is FILE_RETENTION_OVERRIDE. The root user has this privilege per default; however, you can delegate this privilege.

I will explain later what I’m doing; just take away from this example that I’m able to destroy the dataset with the privileged retention policy.

root@testbed:~# zfs create -o retention.policy=mandatory testpool/deletiondemomandatory 
root@testbed:~# zfs create -o retention.policy=privileged testpool/deletiondemoprivileged 
root@testbed:~# zfs set retention.period.default=600s testpool/deletiondemomandatory 
root@testbed:~# zfs set retention.period.default=600s testpool/deletiondemoprivileged 
root@testbed:~# mkfile 1k /testpool/deletiondemomandatory/testfile1
root@testbed:~# touch -R /testpool/deletiondemomandatory/testfile1
root@testbed:~# mkfile 1k /testpool/deletiondemoprivileged/testfile1
root@testbed:~# touch -R /testpool/deletiondemoprivileged/testfile1
root@testbed:~# zfs destroy testpool/deletiondemoprivileged

I’m not able to destroy the dataset when the pool uses the mandatory policy and still has a single retained file.

root@testbed:~# zfs destroy testpool/deletiondemomandatory
cannot destroy 'testpool/deletiondemomandatory': has retained files

With the necessary privileges, you can even delete the file, as long it’s a dataset with the privileged policy:

root@testbed:~# zfs create -o retention.policy=privileged testpool/deletiondemoprivileged 
root@testbed:~# zfs set retention.period.default=600s testpool/deletiondemoprivileged 
root@testbed:~# mkfile 1k /testpool/deletiondemoprivileged/testfile1
root@testbed:~# touch -R /testpool/deletiondemoprivileged/testfile1
root@testbed:~# rm /testpool/deletiondemoprivileged/testfile1
rm: /testpool/deletiondemoprivileged/testfile1: override protection 400 (yes/no)? yes

When there is no retained file in a filesystem with the mandatory policy however, you can delete the file system or the pool.

root@testbed:~# zfs destroy testpool/deletiondemomandatory
root@testbed:~# 

It’s not possible to change the retention policy once it’s set. Obviously, because you then could set it to privileged and would be able to destroy it or to delete files. You don’t want this.

root@testbed:~# zfs set retention.policy=mandatory testpool/privileged
Cannot set property for 'testpool/privileged': 'retention.policy' is readonly

There are several properties around retention you can set. To make my next examples a little bit easier, I will use one of them at this time in my example. The property is called retention.period.default.

root@testbed:/testpool# zfs set retention.period.default=180s testpool/mandatory 

I set it to 180s. So, whenever I retain a file without specifying an exact time, this will be the default retention time. As it would be a little bit impractical for this tutorial to use, for example, 30 years, I will use 3 minutes.

My first retained file

For my examples, I will use the filesystem with mandatory retention. As you see, even root is bound by the mandatory retention.

root@testbed:/testpool/mandatory# mkfile 1k testfile1
root@testbed:/testpool/mandatory# touch -R testfile1 

I created a file and then touched the file with the -R option. This tells ZFS to retain the file until the retention time has passed. As I didn’t specify a retention date and time, the default retention time is used. In my example: The already mentioned 3 minutes.

Okay … let’s try to delete the file.

root@testbed:/testpool/mandatory# date
Fri Apr 18 20:54:48 CEST 2025
root@testbed:/testpool/mandatory# rm testfile1
rm: testfile1: override protection 400 (yes/no)? yes
rm: testfile1 not removed: Insufficient privileges
root@testbed:/testpool/mandatory# date
Fri Apr 18 20:55:06 CEST 2025
root@testbed:/testpool/mandatory# date
Fri Apr 18 20:56:13 CEST 2025
root@testbed:/testpool/mandatory# rm testfile1
rm: testfile1: override protection 400 (yes/no)? yes
rm: testfile1 not removed: Insufficient privileges

root@testbed:/testpool/mandatory# rm testfile1
rm: testfile1: override protection 400 (yes/no)? yes
rm: testfile1 not removed: Insufficient privileges

As you see, my attempts to delete the file are blocked with rm: testfile1 not removed: Insufficient privileges until the 3 minutes have passed.

root@testbed:/testpool/mandatory# date
Fri Apr 18 20:57:52 CEST 2025
root@testbed:/testpool/mandatory# rm testfile1
rm: testfile1: override protection 400 (yes/no)? yes
root@testbed:/testpool/mandatory# date
Fri Apr 18 20:58:34 CEST 2025

Okay, you can check the time when the retention of a file ends with ls. The interesting line is the rtime line. In my example, the retention ends on Apr 18 21:07:23 2025.

root@testbed:/testpool/mandatory# touch -R testfile2
root@testbed:/testpool/mandatory# ls -l -%all testfile2
-r--------   1 root     root        1024 Apr 18  2025 testfile2
         timestamp: atime         Apr 18 21:03:27 2025 
         timestamp: ctime         Apr 18 21:04:23 2025 
         timestamp: mtime         Apr 18 21:03:27 2025 
         timestamp: crtime        Apr 18 21:03:27 2025 
         timestamp: rtime         Apr 18 21:07:23 2025 

Let’s try to delete it again.

root@testbed:/testpool/mandatory# date
Fri Apr 18 21:06:50 CEST 2025
root@testbed:/testpool/mandatory# rm testfile2 
rm: testfile2: override protection 400 (yes/no)? y
rm: testfile2 not removed: Insufficient privileges
root@testbed:/testpool/mandatory# ls -l -%rtime testfile2
-r--------   1 root     root        1024 Apr 18 21:07 testfile2
root@testbed:/testpool/mandatory# date
Fri Apr 18 21:07:54 CEST 2025
root@testbed:/testpool/mandatory# rm testfile2 
rm: testfile2: override protection 400 (yes/no)? yes

But retention has other consequences. You can’t change the file. Before deleting the file in the example before I tried to change the file:

root@testbed:~# echo test >> /testpool/mandatory/testfile2 
-bash: /testpool/mandatory/testfile2: Insufficient privileges

You can’t move it.

root@testbed:~# mv /testpool/mandatory/testfile2 /testpool/mandatory/dir1/dir2
mv: cannot rename /testpool/mandatory/testfile2 to /testpool/mandatory/dir1/dir2/testfile2: Insufficient privileges

Let’s assume you have retained a file once. You can prolong the time by using touch -R multiple times.

root@testbed:~# ls -l -%all /testpool/mandatory/testfile2 
-r--------   1 root     root        1024 Apr 19  2025 /testpool/mandatory/testfile2
         timestamp: atime         Apr 19 17:26:29 2025 
         timestamp: ctime         Apr 19 17:34:47 2025 
         timestamp: mtime         Apr 19 17:26:29 2025 
         timestamp: crtime        Apr 19 17:26:29 2025 
         timestamp: rtime         Apr 19 17:37:47 2025 
root@testbed:~# touch -R /testpool/mandatory/testfile2 
root@testbed:~# ls -l -%all /testpool/mandatory/testfile2 
-r--------   1 root     root        1024 Apr 19  2025 /testpool/mandatory/testfile2
         timestamp: atime         Apr 19 17:26:29 2025 
         timestamp: ctime         Apr 19 17:35:49 2025 
         timestamp: mtime         Apr 19 17:26:29 2025 
         timestamp: crtime        Apr 19 17:26:29 2025 
         timestamp: rtime         Apr 19 17:38:49 2025 

So far, we only used the default retention time. But you can specify one as well. For example, I want to set a retention time of 18:01 on April 19th, 2025.

root@testbed:~# ls -l -%all /testpool/mandatory/testfile2 
-r--------   1 root     root        1024 Apr. 19 17:38 /testpool/mandatory/testfile2
         timestamp: atime         Apr. 19 17:26:29 2025 
         timestamp: ctime         Apr. 19 17:35:49 2025 
         timestamp: mtime         Apr. 19 17:26:29 2025 
         timestamp: crtime        Apr. 19 17:26:29 2025 
         timestamp: rtime         Apr. 19 17:38:49 2025 
root@testbed:~# touch -R 0419180125 /testpool/mandatory/testfile2 
root@testbed:~# ls -l -%all /testpool/mandatory/testfile2 
-r--------   1 root     root        1024 Apr. 19  2025 /testpool/mandatory/testfile2
         timestamp: atime         Apr. 19 17:26:29 2025 
         timestamp: ctime         Apr. 19 17:40:03 2025 
         timestamp: mtime         Apr. 19 17:26:29 2025 
         timestamp: crtime        Apr. 19 17:26:29 2025 
         timestamp: rtime         Apr. 19 18:01:00 2025 
root@testbed:~# rm /testpool/mandatory/testfile2 
rm: /testpool/mandatory/testfile2: override protection 400 (yes/no)? yes
rm: /testpool/mandatory/testfile2 not removed: Insufficient privileges

Thousand ways to leave^H^H^H^H^Htrigger your retention

Okay, there aren’t a thousand ways to trigger file retention, but there are a few. Another way is to use an atime in the future and then remove the write permissions of the file.

root@testbed:~# ls -l -%all /testpool/mandatory/testfile4
-rw-------   1 root     root        1024 Apr 19 17:46 /testpool/mandatory/testfile4
         timestamp: atime         Apr 19 17:46:09 2025 
         timestamp: ctime         Apr 19 17:46:09 2025 
         timestamp: mtime         Apr 19 17:46:09 2025 
         timestamp: crtime        Apr 19 17:46:09 2025 
root@testbed:~# touch -a 0419180125 /testpool/mandatory/testfile4
root@testbed:~# chmod ugo-w /testpool/mandatory/testfile4 
root@testbed:~# ls -l -%all /testpool/mandatory/testfile4
-r--------   1 root     root        1024 Apr 19  2025 /testpool/mandatory/testfile4
         timestamp: atime         Apr 19 18:01:00 2025 
         timestamp: ctime         Apr 19 17:47:31 2025 
         timestamp: mtime         Apr 19 17:46:09 2025 
         timestamp: crtime        Apr 19 17:46:09 2025 
         timestamp: rtime         Apr 19 18:01:00 2025 

Recently I wrote a blog entry about attributes: You can trigger retention by setting an atime in the future and then set the readonly attribute.

root@testbed:~# mkfile 1k /testpool/mandatory/testfile5
root@testbed:~# touch -a 0419180125 /testpool/mandatory/testfile5
root@testbed:~# ls -l -%all /testpool/mandatory/testfile5
-rw-------   1 root     root        1024 Apr 19 17:49 /testpool/mandatory/testfile5
         timestamp: atime         Apr 19 18:01:00 2025 
         timestamp: ctime         Apr 19 17:49:36 2025 
         timestamp: mtime         Apr 19 17:49:36 2025 
         timestamp: crtime        Apr 19 17:49:36 2025 
root@testbed:~# chmod S+vreadonly /testpool/mandatory/testfile5
root@testbed:~# ls -l -%all /testpool/mandatory/testfile5
-r--------   1 root     root        1024 Apr 19  2025 /testpool/mandatory/testfile5
         timestamp: atime         Apr 19 18:01:00 2025 
         timestamp: ctime         Apr 19 17:50:33 2025 
         timestamp: mtime         Apr 19 17:49:36 2025 
         timestamp: crtime        Apr 19 17:49:36 2025 
         timestamp: rtime         Apr 19 18:01:00 2025 

Delete on expiry

Data retention is not only about preventing deletion or modification of data until the retention period has been expired. Sometimes it’s about getting rid of the data after the retention period. The retention feature in ZFS helps you to implement such a policy.

Again, I will configured a filesystem with mandatory retention and a default retention period of 180s.

root@testbed:~# zfs create -o retention.policy=mandatory testpool/deleteonexpiry
root@testbed:~# zfs set retention.period.default=180s testpool/deleteonexpiry

To activate deletion on retention expiry, I have to activate this policy:

root@testbed:~# zfs set retention.policy.onexpiry=delete testpool/deleteonexpiry

Now after the expiry of the retention the file is deleted by the system.

root@testbed:~# mkfile 1k /testpool/deleteonexpiry/testfile1
root@testbed:~# touch -R /testpool/deleteonexpiry/testfile1
root@testbed:~# date ; ls -l -%all /testpool/deleteonexpiry/testfile1
Sat Apr 19 20:16:22 CEST 2025
-r--------   1 root     root        1024 Apr 19  2025 /testpool/deleteonexpiry/testfile1
         timestamp: atime         Apr 19 20:15:29 2025 
         timestamp: ctime         Apr 19 20:15:41 2025 
         timestamp: mtime         Apr 19 20:15:29 2025 
         timestamp: crtime        Apr 19 20:15:29 2025 
         timestamp: rtime         Apr 19 20:18:41 2025 
root@testbed:~# date ; ls -l -%all /testpool/deleteonexpiry/testfile1
Sat Apr 19 20:17:24 CEST 2025
-r--------   1 root     root        1024 Apr 19  2025 /testpool/deleteonexpiry/testfile1
         timestamp: atime         Apr 19 20:15:29 2025 
         timestamp: ctime         Apr 19 20:15:41 2025 
         timestamp: mtime         Apr 19 20:15:29 2025 
         timestamp: crtime        Apr 19 20:15:29 2025 
         timestamp: rtime         Apr 19 20:18:41 2025 
root@testbed:~# date ; ls -l -%all /testpool/deleteonexpiry/testfile1
Sat Apr 19 20:19:25 CEST 2025
/testpool/deleteonexpiry/testfile1: No such file or directory

As useful as this feature is, there may be a small problem. Think about backup tools that manage their backup files in a database. They have their own retention policies and clean up the database and the backup files on their own.

However, when you configure deletion on expiry, the file is deleted as soon as the retention period has passed. The issue is: When the retention period has not passed, the file is not deletable; if the retention period has passed, there is no file.

So, there is no time slot for the tool to delete the file on its own. It’s now dependent on the backup tool if it accepts a situation where the backup file has already been deleted and simply ignores that there was no file to delete.

For such tools, another property was introduced. You can configure a grace period. The file isn’t deleted right after the expiry of the retention, but only after the additional grace period. If your cleanup is running, for example, once a day, you can configure a grace period of one day. Only expired files left over by the clean-up tool would then be deleted by the delete-on-expiry policy.

At first, I’m configuring a deletegrace period of 60s seconds. So after expiry the system won’t delete the file for additional 60 seconds.

root@testbed:~# zfs set retention.period.deletegrace=60s 

Let`s retain a new file

root@testbed:~# mkfile 1k /testpool/deleteonexpiry/testfile1
root@testbed:~# touch -R /testpool/deleteonexpiry/testfile1

Lets check whats happen now :

root@testbed:~# mkfile 1k /testpool/deleteonexpiry/testfile1
root@testbed:~# date ; ls -l -%all /testpool/deleteonexpiry/testfile1
Sat Apr 19 20:27:27 CEST 2025
-r--------   1 root     root        1024 Apr 19  2025 /testpool/deleteonexpiry/testfile1
         timestamp: atime         Apr 19 20:24:29 2025 
         timestamp: ctime         Apr 19 20:27:13 2025 
         timestamp: mtime         Apr 19 20:24:29 2025 
         timestamp: crtime        Apr 19 20:24:29 2025 
         timestamp: rtime         Apr 19 20:30:13 2025 
root@testbed:~# date ; ls -l -%all /testpool/deleteonexpiry/testfile1
Sat Apr 19 20:30:24 CEST 2025
-r--------   1 root     root        1024 Apr 19 20:30 /testpool/deleteonexpiry/testfile1
         timestamp: atime         Apr 19 20:24:29 2025 
         timestamp: ctime         Apr 19 20:27:13 2025 
         timestamp: mtime         Apr 19 20:24:29 2025 
         timestamp: crtime        Apr 19 20:24:29 2025 
         timestamp: rtime         Apr 19 20:30:13 2025 

Without the grace period, the file would have been deleted already at this point in time.

root@testbed:~# date ; ls -l -%all /testpool/deleteonexpiry/testfile1
Sat Apr 19 20:31:30 CEST 2025
/testpool/deleteonexpiry/testfile1: No such file or directory

Hold on expiry

There are situations where you can’t delete any data. There are situations where you are told “Keep everything, we may need it! Don’t dare to delete something. Don’t recycle tapes. Nothing until we tell you otherwise”.

I’m pretty sure you don’t want to explain to someone that ZFS auto-deleted a file on your fileservers or backup server, because the retention expired after you got such a notification. Furthermore, you don’t want to explain to the same someone, that a retained file could be deleted by users, because the retention period expired.

Out of this reason, you can set another on-expiry-policy. It’s called hold:

root@testbed:~# zfs create -o retention.policy=mandatory testpool/holdonexpiry
root@testbed:~# zfs set retention.period.default=180s testpool/holdonexpiry  
root@testbed:~# zfs set retention.policy.onexpiry=hold testpool/holdonexpiry  

Now let’s retain a file.

root@testbed:~# mkfile 1k /testpool/holdonexpiry/testfile1
root@testbed:~# touch -R /testpool/holdonexpiry/testfile1

I should be able to delete the file on Apr 19 21:03:48 2025:

root@testbed:~# date ; ls -l -%all /testpool/holdonexpiry/testfile1
Sat Apr 19 21:01:35 CEST 2025
-r--------   1 root     root        1024 Apr 19  2025 /testpool/holdonexpiry/testfile1
         timestamp: atime         Apr 19 21:00:31 2025 
         timestamp: ctime         Apr 19 21:00:48 2025 
         timestamp: mtime         Apr 19 21:00:31 2025 
         timestamp: crtime        Apr 19 21:00:31 2025 
         timestamp: rtime         Apr 19 21:03:48 2025 

However, because of the hold policy, ZFS doesn’t allow the deletion of the file after the expiry of the retention period. And as the policy isn’t delete, the file isn’t deleted after expiry as well.

root@testbed:~# date ; ls -l -%all /testpool/holdonexpiry/testfile1
Sat Apr 19 21:04:26 CEST 2025
-r--------   1 root     root        1024 Apr 19 21:03 /testpool/holdonexpiry/testfile1
         timestamp: atime         Apr 19 21:00:31 2025 
         timestamp: ctime         Apr 19 21:00:48 2025 
         timestamp: mtime         Apr 19 21:00:31 2025 
         timestamp: crtime        Apr 19 21:00:31 2025 
         timestamp: rtime         Apr 19 21:03:48 2025 
root@testbed:~# rm /testpool/holdonexpiry/testfile1
rm: /testpool/holdonexpiry/testfile1: override protection 400 (yes/no)? yes
rm: /testpool/holdonexpiry/testfile1 not removed: Insufficient privileges

The destruction of the ZFS filesystem isn’t possible, too .

root@testbed:~# zfs destroy testpool/holdonexpiry
cannot destroy 'testpool/holdonexpiry': has retention.policy.onexpiry set to 'hold'

When you get the notice that the hold has ended, you can set the on-expiry-policy to off, and you are now able to delete the file.

root@testbed:~# zfs set retention.policy.onexpiry=off testpool/holdonexpiry
root@testbed:~# rm /testpool/holdonexpiry/testfile1
rm: /testpool/holdonexpiry/testfile1: override protection 400 (yes/no)? yes
root@testbed:~#

Or you could throw away the whole dataset as well.

root@testbed:~# zfs destroy testpool/holdonexpiry

Auto retention

Sometimes you don’t want to tell the system to retain the file. For example, because you have no way to integrate any of the ways to trigger the retention in your application.

root@testbed:~# zfs create -o retention.policy=mandatory testpool/autoretention
root@testbed:~# zfs set retention.period.default=180s testpool/autoretention

With retention.period.grace property you can set the period of time after the last modification to the file the retention should auto-trigger.

root@testbed:~# zfs set retention.period.grace=60s testpool/autoretention

Let’s try to delete the file after 10 seconds.

root@testbed:~# mkfile 1k /testpool/autoretention/testfile1 
root@testbed:~# sleep 10; rm /testpool/autoretention/testfile1 

This will obviously work. The grace period hasn’t passed at that moment. So, the file is not retained. Now, let’s wait for 60 seconds.

root@testbed:~# mkfile 1k /testpool/autoretention/testfile1 
root@testbed:~# sleep 60; rm /testpool/autoretention/testfile1 
rm: /testpool/autoretention/testfile1: override protection 400 (yes/no)? yes
rm: /testpool/autoretention/testfile1 not removed: Insufficient privileges

The grace period is not counted from the creation of the file, but from the last change. Let’s write something into the file roughly every 10 seconds.

root@testbed:~# mkfile 1k /testpool/autoretention/testfile2 
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2

At least 60 seconds should have passed since creation now. But I’m still able to write into this file.

root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2
root@testbed:~# sleep 10; echo narf >> /testpool/autoretention/testfile2

I’m now waiting for 70 seconds. If nobody else is writing to this file, the grace period should pass in this time, and the file should be retained.

root@testbed:~# sleep 70; echo narf >> /testpool/autoretention/testfile2
-bash: /testpool/autoretention/testfile2: Insufficient privileges

And indeed, it is.

Zero length files

Retention behaves a little bit differently on files that are retained when they have a length of zero. For my test, I’m creating yet another ZFS filesystem with mandatory retention.

root@testbed:~# zfs create -o retention.policy=mandatory testpool/zerolength   
root@testbed:~# zfs set retention.period.default=180s testpool/zerolength   

Now I’m creating a zero-length file with touch.

root@testbed:~# touch /testpool/zerolength/testfile1
root@testbed:~# ls -/V /testpool/zerolength/testfile1
-rw-r--r--   1 root     root           0 Apr 20 10:02 /testpool/zerolength/testfile1
                {archive,av_modified}

When I’m triggering retention for this file, we will see a new attribute assigned to the file.

root@testbed:~# touch -R /testpool/zerolength/testfile1 
root@testbed:~# ls -/V /testpool/zerolength/testfile1
-rw-r--r--   1 root     root           0 Apr 20 10:02 /testpool/zerolength/testfile1
                {archive,appendonly,av_modified}

The attribute appendonly has been added to the file. And this gives you an indication about the different behavior. You can’t delete this file.

root@testbed:~# rm /testpool/zerolength/testfile1
rm: /testpool/zerolength/testfile1 not removed: Insufficient privileges

You can’t overwrite it.

root@testbed:~# echo test > /testpool/zerolength/testfile1
-bash: /testpool/zerolength/testfile1: Insufficient privileges

But you can append to it

root@testbed:~# echo test >> /testpool/zerolength/testfile1
root@testbed:~# echo test >> /testpool/zerolength/testfile1

Okay, let’s assume you know there will be no further additions to the file. Now you can remove all write permissions from the file, and the file gets a normal retained file.

root@testbed:~# chmod ugo-w /testpool/zerolength/testfile1

Again, the attributes give you an indication about what happens now. The attribute appendonly disappears, and you will see readonly instead.

root@testbed:~# ls -/V /testpool/zerolength/testfile1
-r--r--r--   1 root     root           5 Apr 20 10:14 /testpool/zerolength/testfile1
                {archive,readonly,av_modified}

And indeed, you can’t append to this file any longer.

root@testbed:~# echo test >> /testpool/zerolength/testfile1
-bash: /testpool/zerolength/testfile1: Insufficient privileges

Of course, after the retention has expired, you are able to delete the file again:

root@testbed:~# date ; ls -l -%all /testpool/zerolength/testfile1
Sun Apr 20 10:12:53 CEST 2025
-r--r--r--   1 root     root          10 Apr 20 10:05 /testpool/zerolength/testfile1
         timestamp: atime         Apr 20 10:04:23 2025 
         timestamp: ctime         Apr 20 10:04:58 2025 
         timestamp: mtime         Apr 20 10:03:55 2025 
         timestamp: crtime        Apr 20 10:02:39 2025 
         timestamp: rtime         Apr 20 10:05:59 2025 
root@testbed:~# rm /testpool/zerolength/testfile1
rm: /testpool/zerolength/testfile1: override protection 444 (yes/no)? yes
root@testbed:~#

Do you want to learn more?

The file retention feature has its own section in the documentation at oracle. You will find it here: docs.oracle.com: Retaining Files on Your ZFS File System

Written by

Joerg Moellenkamp

Grey-haired, sometimes grey-bearded Windows dismissing Unix guy.