As well we can imagine a transactional filesystem will provide transaction APIs,
file:beginx()
if file:commitstartx() then ... end
file:commitx()
file:rollbackx()
The idea being that any file:write() will be part of a transaction started by file:beginx() and automatically rollbacked if it is closed before using file:commitx() (or the program is aborted, or if the system crashes and is rebooted to remount the filesytem)
All these file:write()s will be made to a journaled version allowing safe recovery.
The additional file:commitstartx() API would be necessary to allow concurrent transactions in any order with two phase commits, it tests if the transaction can be commited without creating a deadlock condition, allowing the program to either rollback and possibly rollback other transactions blocked by other owned file-locks.
In such situation, the journal may use subcluster allocation in a serial stream owned by the current context (which still uses it on file:read() to get consistant reads). In that case there will be minimum I/O overhead. when writing to the journal (and once you commit the transaction, the space allocated in the journal stream can be reused by the same context/user for other transactions, as long as the file remains open for that context.
Also note that your solution of writing one static byte at the final position is bad: it overwrites that byte whose initial value is then lost, so it is not a "truncation" of the file. Finally you chosen byte ("1") would force the journaling filesystem to allocate space for it even if that space was first "virtual". If you choose a byte value it should be a NULL byte (so that no space is allocated in the virtual file storage), even if it also means that any prior non-NULL byte will be also lost (virtually) as if it was overwritten (if that byte was in a non-virtual sector/cluster was not full of NULLs, you'll get an allocation for that sector/cluster when it is committed to disk on a transactional filesytem, or that NUL byte will still be physically written over the previous byte if the filesystem is not versioned, even if that file system is not transactional).
For all these reasons, file APIs should be extended for supporting:
- journaling
- transaction (including two-phase commits)
- versioning
- virtual preallocation and truncation at any seek position (even a very large one)
- possibly creating embedded subtransactions (only if there's support for two-phase-commits).
And the basic file:open(), fileread(), file:write(), file:seek(), and file:close() operations from POSIX are not sufficient for modern apps. All these can be emulated easily on filesystems that are neither transactional, nor journaling, nor virtualized.