Page MenuHomeFreeBSD

Avoid incorrect UFS1 timestamp corrections when system clock fails at boot
ClosedPublic

Authored by mckusick on Sat, May 30, 10:33 PM.
Tags
None
Referenced Files
F159103817: D57371.id179011.diff
Wed, Jun 10, 4:15 AM
Unknown Object (File)
Mon, Jun 8, 8:05 PM
Unknown Object (File)
Mon, Jun 8, 12:00 PM
Unknown Object (File)
Mon, Jun 8, 5:51 AM
Unknown Object (File)
Mon, Jun 8, 5:43 AM
Unknown Object (File)
Mon, Jun 8, 4:39 AM
Unknown Object (File)
Mon, Jun 8, 12:52 AM
Unknown Object (File)
Sun, Jun 7, 8:54 PM

Details

Summary

Git 1111a44301da - main - Defer the January 19, 2038 date limit in UFS1 file systems to February 7, 2106 - did so by changing the UFS1 32-bit signed timestamps to unsigned. With this change, time stamps from before January 1, 1970 went from being negative numbers to large positive numbers implying times in the future. When such a time stamp is encountered when an inode is read into memory or when it is encountered by fsck, its timestamp is replaced with the kernel's current time.

Andre Albsmeier reported that he had a machine reboot after a power failure and the battery that maintained its real-time clock had died. The result was that the system booted with the time set to five years earlier (absent a real-time clock value, the boot ROM used the time that the boot ROM had last been updated). The net result was that fsck reset the time stamps of all files newer than five years old to the five year old time.

This change compares the system's version of the current time to the last modification time in the file system superblock. If the current time is earlier than that time then use the last modification time in the superblock as the value for the current time. There should be no files in the file system with times newer than the last modification time in the superblock.

The superblock time stamp is updated in the in-memory superblock every time any change is made to anything in the file system. The superblock is written to the disk every 30 seconds, so it may be off by up to 30 seconds plus the time it sits in the disk cache waiting to be written if the system has an unclean shutdown (such as a power failure). Thus, the worst case scenario with this change is that files written in the last 30 seconds plus disk cache delay time before the crash may have their times adjusted back by up to 30 seconds plus the disk cache delay time.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

Do we need somthing like

--- lib/libufs/inode.c.ORI      2025-02-12 07:28:29.302981000 +0100
+++ lib/libufs/inode.c  2026-05-31 08:07:25.936547000 +0200
@@ -80,6 +80,8 @@
                ERROR(disk, "cannot get current time of day");
                return (-1);
        }
+       if (fs->fs_time > now.tv_sec)
+               now.tv_sec = fs->fs_time;
        if (inum >= min && inum < max)
                goto gotit;
        bread(disk, fsbtodb(fs, ino_to_fsba(fs, inum)), inoblock,

as well? Or can be assumend that the system clock will always be correct when getinode() is being called?

ffs_load_inode() is used from ffs_vgetf(), and might set the IN_MODIFIED flag. This breaks suspension for FFS1. Note that despite FF1 not supports snapshots, we still need to suspend it for several VFS ops.

This is pre-existing bug.

I'd be tempted to double the tolerance to 1 minute. Some drives with write caching enable won't flush everything to medium so maybe the prior SB is still on the medium. We don't seem to send a BIO_FLUSH down, but this is from code inspection and maybe I missed something. One could speculate that further writes were lost, but while 30s is a long time to the drive, a few seconds might not be so you may find files on these drives past 30s by a little bit and 1 minute seems like a sensible limit to account for it.

Since I wasn't sure, I asked Claude and it said no too. It knew I asked the wrong question too...

me> when we flush the super block with ffs_sbflush, do we send a BIO_FLUSH down afterwards?
● The function is called ffs_sbupdate, not ffs_sbflush. Let me read the implementation.
● No, ffs_sbupdate does not send a BIO_FLUSH after writing the superblock. It writes the superblock (and summary information) using bwrite() or bawrite() depending on the waitfor flag, but there is no subsequent BIO_FLUSH issued to force the data to stable storage on the device.

The write path is:
1. Copy the superblock into a buffer (getblk + bcopy)
2. Write it with bwrite() (synchronous, waits for I/O completion) or bawrite() (async)

bwrite() waits for the I/O to complete at the BIO layer, but that doesn't guarantee the drive's write cache has been flushed to media — that would require an explicit BIO_FLUSH. None of the callers of   ffs_sbupdate appear to issue one either.

(Note: the function is ffs_sbupdate, not ffs_sbflush — there is no ffs_sbflush in the tree.)
In D57371#1314400, @kib wrote:

ffs_load_inode() is used from ffs_vgetf(), and might set the IN_MODIFIED flag. This breaks suspension for FFS1. Note that despite FF1 not supports snapshots, we still need to suspend it for several VFS ops.

This is pre-existing bug.

Given that this is a pre-existing bug, it seems like its fix should be part of a different commit to fix it?

In D57371#1314488, @imp wrote:

I'd be tempted to double the tolerance to 1 minute. Some drives with write caching enable won't flush everything to medium so maybe the prior SB is still on the medium. We don't seem to send a BIO_FLUSH down, but this is from code inspection and maybe I missed something. One could speculate that further writes were lost, but while 30s is a long time to the drive, a few seconds might not be so you may find files on these drives past 30s by a little bit and 1 minute seems like a sensible limit to account for it.

Since I wasn't sure, I asked Claude and it said no too. It knew I asked the wrong question too...

me> when we flush the super block with ffs_sbflush, do we send a BIO_FLUSH down afterwards?
● The function is called ffs_sbupdate, not ffs_sbflush. Let me read the implementation.
● No, ffs_sbupdate does not send a BIO_FLUSH after writing the superblock. It writes the superblock (and summary information) using bwrite() or bawrite() depending on the waitfor flag, but there is no subsequent BIO_FLUSH issued to force the data to stable storage on the device.

The write path is:
1. Copy the superblock into a buffer (getblk + bcopy)
2. Write it with bwrite() (synchronous, waits for I/O completion) or bawrite() (async)

bwrite() waits for the I/O to complete at the BIO layer, but that doesn't guarantee the drive's write cache has been flushed to media — that would require an explicit BIO_FLUSH. None of the callers of   ffs_sbupdate appear to issue one either.

(Note: the function is ffs_sbupdate, not ffs_sbflush — there is no ffs_sbflush in the tree.)

Interesting commentary.

It is not clear if there is any bound on the time that the superblock can stay in the disk cache and not be written if enough requests are coming into the disk. The 30-second comment is just an estimate of how far out of sync the time in the superblock may be relative to the current time. As a practical matter that is probably a good estimate, but I have changed the comment to allow for additional delay time.

Add time check to loading of inodes in libufs per comment by Andre Albsmeier.

This revision is now accepted and ready to land.Mon, Jun 1, 10:58 AM