Snapshots

A snapshot is a committed stable view of a Power-Safe filesystem. Each mounted filesystem has one stable snapshot and one working view (in which copy-on-write modifications to the stable snapshot are being made).

Whenever a new snapshot is made, filesystem activity is suspended (to give a stable system), the bitmaps are updated, all dirty blocks are forced to disk, and the alternate filesystem superblock is written (with a higher sequence number). Then filesystem activity is resumed, and another working view is constructed on the old superblock. When a filesystem is remounted after an unclean power failure, it restores the last stable snapshot.

Snapshots are made:

You can disable snapshots on a filesystem at a global or local level. When disabled, a new superblock isn't written, and an attempt to make a snapshot fails with an errno of EAGAIN (or silently, for the sync() or timer cases). If snapshots are still disabled when the filesystem is unmounted (implicitly or at a power failure), any pending modifications are discarded (lost).

Snapshots are also permanently disabled automatically after an unrecoverable error that would result in an inconsistent filesystem. An example is running out of memory for caching bitmap modifications, or a disk I/O error while writing a snapshot. In this case, the filesystem is forced to be read-only, and the current and all future snapshot processing is omitted; the aim being to ensure that the last stable snapshot remains undisturbed and available for reloading at the next mount/startup (i.e., the filesystem always has a guaranteed stable state, even if slightly stale). This is only for certain serious error situations, and generally shouldn't happen.

Manually disabling snapshots can be used to encapsulate a higher-level sequence of operations that must either all succeed or none occur (e.g., should power be lost during this sequence). Possible applications include software updates or filesystem defragmentation.

To disable snapshots at the global level, clear the FS_FLAGS_COMMITTING flag on the filesystem, using the DCMD_FSYS_FILE_FLAGS command to devctl():

struct fs_fileflags  flags;
 
memset( &flags, 0, sizeof(struct fs_fileflags));
flags.mask[FS_FLAGS_GENERIC] = FS_FLAGS_COMMITTING;
flags.bits[FS_FLAGS_GENERIC] = disable ? 0 : FS_FLAGS_COMMITTING;
devctl( fd, DCMD_FSYS_FILE_FLAGS, &flags,
        sizeof(struct fs_fileflags), NULL);
Note: This is a single flag for the entire filesystem, and can be set or cleared by any superuser client; thus applications must coordinate the use of this flag among themselves.

Alternatively, you can use the chattr utility (as a convenient front-end to the above devctl() command):

# chattr -snapshot /fs/qnx6
/fs/qnx6: -snapshot
...
# chattr +snapshot /fs/qnx6
/fs/qnx6: +snapshot

To disable snapshots at a local level, adjust the QNX6FS_SNAPSHOT_HOLD count on a per-file-descriptor basis, again using the DCMD_FSYS_FILE_FLAGS command to devctl(). Each open file has its own hold count, and the sum of all local hold counts is a global hold count that disables snapshots if nonzero. Thus if any client sets a hold count, snapshots are disabled until all clients clear their hold counts.

The hold count is a 32-bit value, and can be incremented more than once (and must be balanced by the appropriate number of decrements). If a file descriptor is closed, or the process terminates, then any local holds it contributed are automatically undone. The advantage of this scheme is that it requires no special coordination between clients; each can encapsulate its own sequence of atomic operations using its independent hold count:

struct fs_fileflags  flags;
 
memset( &flags, 0, sizeof(struct fs_fileflags));
flags.mask[FS_FLAGS_FSYS] = QNX6FS_SNAPSHOT_HOLD;
flags.bits[FS_FLAGS_FSYS] = QNX6FS_SNAPSHOT_HOLD;
devctl( fd, DCMD_FSYS_FILE_FLAGS, &flags,
        sizeof(struct fs_fileflags), NULL);
...
memset( &flags, 0, sizeof(struct fs_fileflags));
flags.mask[FS_FLAGS_FSYS] = QNX6FS_SNAPSHOT_HOLD;
flags.bits[FS_FLAGS_FSYS] = 0;
devctl( fd, DCMD_FSYS_FILE_FLAGS, &flags,
        sizeof(struct fs_fileflags), NULL);

In this case, chattr isn't particularly useful to manipulate the state, as the hold count is immediately reset once the utility terminates (as its file descriptor is closed). However, it is convenient to report on the current status of the filesystem, as it will display both the global and local flags as separate states:

# chattr /fs/qnx6
/fs/qnx6: +snapshot +contiguous +used +hold

If +snapshot isn't displayed, then snapshots have been disabled via the global flag. If +hold is displayed, then snapshots have been disabled due to a global nonzero hold count (by an unspecified number of clients). If +dirty is permanently displayed (even after a sync()), then either snapshots have been disabled due to a potentially fatal error, or the disk hardware doesn't support full data synchronization (track cache flush).

Note: Enabling snapshots doesn't in itself cause a snapshot to be made; you should do this with an explicit fsync() if required. It's often a good idea to fsync() both before disabling and after enabling snapshots (the chattr utility does this).