227 lines
12 KiB
C
227 lines
12 KiB
C
#ifndef FROSTBYTE_SFS_H
|
|
#define FROSTBYTE_SFS_H
|
|
|
|
#include "types.h"
|
|
|
|
/*
|
|
* SFS ("simple filesystem"): a real, persistent, inode-based filesystem
|
|
* living on the ATA block device (ata.h), starting at LBA sfsBaseLba. Unlike
|
|
* the VFS tmpfs tree (vfs.c), which is pure kmalloc state that evaporates on
|
|
* reboot, SFS state (superblock, bitmaps, inode table, file data) is fully
|
|
* on disk: sfsUnmount()+sfsMount() drops every in-memory structure and
|
|
* reconstructs it by reading the disk, the same way a real filesystem driver
|
|
* would after a remount.
|
|
*
|
|
* Block size equals the sector size (512 bytes), so every filesystem block
|
|
* is exactly one ATA sector transfer. sfsBaseLba (100) and the reserved
|
|
* tail (16 sectors) keep this filesystem's LBA range clear of the raw
|
|
* sector ranges ataTest.c/disktest.c already poke directly, so the two
|
|
* layers never fight over the same bytes even though they share one disk.
|
|
*/
|
|
|
|
#define sfsBlockSize 512
|
|
#define sfsBaseLba 100
|
|
#define sfsReserveEnd 16
|
|
#define sfsMaxNodes 512
|
|
#define sfsRootInode 1
|
|
#define sfsNameMax 55
|
|
#define sfsMaxSymlinkHops 8
|
|
#define sfsSymlinkTargetMax 255
|
|
|
|
#define sfsTypeFree 0
|
|
#define sfsTypeFile 1
|
|
#define sfsTypeDir 2
|
|
#define sfsTypeSymlink 3
|
|
#define sfsTypeFifo 4
|
|
|
|
/* Formats the SFS region from scratch: fresh superblock, zeroed bitmaps and
|
|
inode table, empty root directory. Destroys any existing SFS content in
|
|
that LBA range. Returns false if no drive is present or the drive is too
|
|
small to hold the fixed-size metadata. */
|
|
bool sfsFormat(void);
|
|
|
|
/* Mounts the existing on-disk filesystem, auto-formatting on first use (bad
|
|
magic). Loads the inode/block bitmaps into memory; everything else (inode
|
|
table, file data, directory entries) is read from disk on demand. */
|
|
bool sfsMount(void);
|
|
|
|
/* Flushes bitmaps/superblock to disk and frees in-memory state. A later
|
|
sfsMount() reconstructs everything purely from what is on disk. */
|
|
void sfsUnmount(void);
|
|
|
|
bool sfsMounted(void);
|
|
|
|
/* Total bytes usable for file data (excludes reserved metadata blocks). */
|
|
uint64 sfsCapacityBytes(void);
|
|
|
|
/* Data blocks not currently allocated to any inode (bitmap free count).
|
|
Mainly useful for tests to prove truncate/remove actually gives blocks
|
|
back rather than merely detaching them from the inode. */
|
|
uint64 sfsFreeDataBlocks(void);
|
|
|
|
/* path is slash-separated and relative to the SFS root ("" or "/" is the
|
|
root itself). Returns the inode number (>=1) or -1 if not found. Note:
|
|
"." and ".." are not special-cased (deliberate scope limit: paths are
|
|
always resolved from the root down, no relative-to-self hops).
|
|
|
|
A symlink encountered anywhere along the path - including the final
|
|
component - is transparently followed to its target and re-resolved from
|
|
the root (real open()/stat() semantics), up to sfsMaxSymlinkHops hops;
|
|
beyond that (a cycle, or a chain that is simply too long) resolution fails
|
|
with -1 instead of recursing forever. Use sfsLookupNoFollow to inspect a
|
|
symlink itself rather than what it points at. */
|
|
int32 sfsLookup(const char* path);
|
|
|
|
/* Same resolution as sfsLookup, except a symlink at the FINAL path component
|
|
is returned as itself instead of being followed (the lstat()/readlink()
|
|
convention). Symlinks at any earlier component are still followed, since
|
|
there is no other way to keep descending through them. */
|
|
int32 sfsLookupNoFollow(const char* path);
|
|
|
|
int32 sfsCreateFile(const char* path);
|
|
int32 sfsCreateDir(const char* path);
|
|
|
|
/* Creates a persistent named-pipe entry at `path`: a real, on-disk directory
|
|
entry of type sfsTypeFifo that survives sfsUnmount()+sfsMount() (and a
|
|
real reboot, unlike the VFS tmpfs-tree fifos vfsMkfifo already supports
|
|
under e.g. "/tmp") exactly the way a real filesystem's mkfifo(2) special
|
|
file does. The inode carries no data blocks -- sfsReadAt/sfsWriteAt
|
|
already refuse anything that is not sfsTypeFile/Dir/Symlink, so a fifo
|
|
inode simply has size 0 forever, matching real Unix (mkfifo persists the
|
|
NAME and TYPE of the rendezvous point, never any buffered bytes: those
|
|
only ever exist in memory while some process holds the pipe open, and
|
|
are gone the moment every process does, reboot or not). vfs.c is
|
|
responsible for the actually-live half of a fifo (attaching real
|
|
pipe.h byte-queue handles to opens of this path, see vfsOpen) --
|
|
sfs.c itself stays entirely ignorant of pipe.h, exactly as it already is
|
|
of every other higher-layer concern. Returns the new inode number, or -1
|
|
on the same failures as sfsCreateFile/sfsCreateDir (parent missing/not a
|
|
writable dir, name already taken, tables full). */
|
|
int32 sfsCreateFifo(const char* path);
|
|
|
|
/* Monotonically increasing counter, bumped exactly once every time
|
|
sfsFormat() actually wipes the on-disk filesystem (whether called
|
|
directly, e.g. every sfsTest.c test's own clean-slate setup, or
|
|
implicitly by sfsMount() the first time it sees a bad magic) -- never
|
|
bumped by a plain sfsMount()/sfsUnmount() cycle that finds a valid,
|
|
unchanged filesystem, since inode numbers keep meaning the same thing
|
|
across that kind of remount. Exists so a higher layer that caches
|
|
something keyed by inode number across calls (vfs.c's SFS fifo binding
|
|
table, see vfsOpen/vfsMkfifo/vfsUnlink in vfs.c) can tell "this cached
|
|
entry still refers to the same on-disk fifo I bound it to" apart from
|
|
"the whole filesystem underneath it was wiped and reformatted since,
|
|
inode 7 might be a completely different file/dir/fifo/nothing now" --
|
|
without sfs.c ever having to know that cache exists. */
|
|
uint64 sfsGeneration(void);
|
|
|
|
/* Creates a symbolic link at `path` whose target is the literal string
|
|
`target` - never validated against anything, so a symlink may point at a
|
|
path that does not exist yet (or ever), exactly like a real filesystem.
|
|
Returns the new inode number, or -1 on failure (parent missing, name
|
|
already taken, target too long, or the inode/block tables are full). */
|
|
int32 sfsCreateSymlink(const char* path, const char* target);
|
|
|
|
/* Reads the target string of a symlink inode into buf, null-terminated.
|
|
Fails (returns false, buf left untouched) if inodeNum is not a symlink or
|
|
if the stored target does not fit in cap bytes including the terminator -
|
|
never silently truncates. */
|
|
bool sfsReadlink(int32 inodeNum, char* buf, uint64 cap);
|
|
|
|
/* Creates a second directory entry (newPath) for the same inode an existing
|
|
file or symlink resolves to (existingPath is followed through symlinks,
|
|
same as open()/stat() - the new name refers to whatever it ultimately
|
|
points at, not the symlink itself). Directories cannot be hardlinked
|
|
(matches POSIX link(2): would make the directory tree no longer a tree).
|
|
Returns the shared inode number, or -1 on failure (existingPath missing,
|
|
a directory, newPath's parent missing, or newPath already taken). */
|
|
int32 sfsLink(const char* existingPath, const char* newPath);
|
|
|
|
/* Number of directory entries currently pointing at this inode. 0 for an
|
|
invalid/unmounted lookup, otherwise always >=1 for anything sfsLookup can
|
|
still reach. */
|
|
uint32 sfsLinkCountOf(int32 inodeNum);
|
|
|
|
/* Removes a file, or a directory only if it is empty. A hardlinked file's
|
|
data and inode survive until its link count reaches zero -- only the
|
|
named directory entry is guaranteed to disappear immediately. */
|
|
bool sfsRemove(const char* path);
|
|
|
|
uint64 sfsSizeOf(int32 inodeNum);
|
|
uint32 sfsTypeOf(int32 inodeNum);
|
|
|
|
/* Real wall-clock timestamps (rtc.h's rtcNow(), seconds since the Unix
|
|
epoch), stored on disk in the inode itself so they survive
|
|
sfsUnmount()+sfsMount() like everything else about the inode. mtime is
|
|
last content modification (sfsWriteAt, sfsTruncate); ctime is last inode
|
|
metadata change (mtime changes are also metadata changes, so every mtime
|
|
update bumps ctime too, plus link-count changes from sfsLink/sfsRemove
|
|
bump ctime alone, matching real inode semantics). Both are stamped at
|
|
creation, so a freshly created node has mtime == ctime. Return 0 for an
|
|
invalid/unmounted lookup, same convention as sfsSizeOf/sfsTypeOf. */
|
|
uint64 sfsMtimeOf(int32 inodeNum);
|
|
uint64 sfsCtimeOf(int32 inodeNum);
|
|
|
|
/* Real owner rwx permission bits (fs.h's fsModeOwner*), stored on disk in
|
|
the inode like every other field above -- survives sfsUnmount()+
|
|
sfsMount(). Stamped at creation with a type-based default (createNode:
|
|
fsModeDefaultDir for a directory, fsModeDefaultFile for anything else)
|
|
and changed only by sfsChmod. sfsModeOf returns 0 for an invalid/
|
|
unmounted lookup, same convention as sfsSizeOf/sfsTypeOf. sfsChmod
|
|
overwrites the whole value (no symbolic +x-style merge, matching real
|
|
chmod(2)'s own numeric-mode form) and bumps ctime like any other
|
|
metadata-only mutation (sfsLink's linkCount bump does the same) --
|
|
returns false for an invalid/unmounted inodeNum, true otherwise. */
|
|
uint32 sfsModeOf(int32 inodeNum);
|
|
bool sfsChmod(int32 inodeNum, uint32 mode);
|
|
|
|
/* Real owner identity (fs.h's FbStat.uid), stored on disk like every other
|
|
inode field above -- survives sfsUnmount()+sfsMount(). Stamped once, at
|
|
creation (createNode: sched.h's currentThreadUid(), the CALLER's real
|
|
identity at the moment of the create/mkdir/symlink/mkfifo call), and
|
|
afterward only ever changed by sfsChown (below). sfsUidOf returns 0 for
|
|
an invalid/unmounted lookup, same convention as sfsModeOf. */
|
|
uint32 sfsUidOf(int32 inodeNum);
|
|
|
|
/* Real chown(2)'s on-disk half: overwrites the inode's owner uid outright
|
|
and bumps ctime like sfsChmod's own metadata-only mutation does.
|
|
Deliberately has NO privilege check of its own -- vfsChown (vfs.c) is the
|
|
one real choke point that enforces the actual chown(2) privilege rule
|
|
(root-only, real Unix's _POSIX_CHOWN_RESTRICTED default: not even a
|
|
file's own owner may give it away), exactly like sfsChmod leaves
|
|
ownership-vs-caller policy to vfsChmod. Returns false for an invalid/
|
|
unmounted inodeNum, same convention as sfsChmod. */
|
|
bool sfsChown(int32 inodeNum, uint32 newUid);
|
|
|
|
/* Real group identity (fs.h's FbStat.gid), stored on disk like uid above --
|
|
survives sfsUnmount()+sfsMount(). Stamped once, at creation (createNode:
|
|
sched.h's currentThreadGid()), and afterward only ever changed by
|
|
sfsChgrp (below). sfsGidOf returns 0 for an invalid/unmounted lookup,
|
|
same convention as sfsUidOf. */
|
|
uint32 sfsGidOf(int32 inodeNum);
|
|
|
|
/* Real chgrp(2)'s on-disk half: overwrites the inode's group gid outright
|
|
and bumps ctime, mirroring sfsChown's own shape exactly. Deliberately has
|
|
NO privilege check of its own -- vfsChgrp (vfs.c) is the real choke point
|
|
(owner may set only their own current gid, root may set any gid). Returns
|
|
false for an invalid/unmounted inodeNum, same convention as sfsChown. */
|
|
bool sfsChgrp(int32 inodeNum, uint32 newGid);
|
|
|
|
/* Grows or shrinks a file in place, freeing/allocating data blocks as
|
|
needed. */
|
|
bool sfsTruncate(int32 inodeNum, uint64 newSize);
|
|
|
|
/* Reads/writes inode-relative byte ranges. Writing past the current end of
|
|
file zero-fills the gap first (no sparse holes), then writes the payload,
|
|
growing the file and allocating blocks (direct, then single indirect,
|
|
then double indirect, then triple indirect) as needed. Returns bytes
|
|
transferred, or -1 on error (inode not a file, or the file has hit the
|
|
direct+indirect+double+triple addressing limit, roughly 1 GiB). */
|
|
int64 sfsReadAt(int32 inodeNum, uint64 offset, void* buffer, uint64 length);
|
|
int64 sfsWriteAt(int32 inodeNum, uint64 offset, const void* buffer, uint64 length);
|
|
|
|
/* Lists directory entries by index (0, 1, 2, ...). Returns false past the
|
|
last entry. */
|
|
bool sfsReaddirAt(int32 dirInode, uint32 index, char* nameOut, uint32 nameCap, uint32* typeOut);
|
|
|
|
#endif
|