9P-FILE(3)

πŸ“¦ version 0_git20260208, in alpine 3.24, arch rolling, nixos 26.05, nixpkgs unstable

πŸ’… 9P-FILE(3) β€” the Library Functions Manual moment πŸŽ€

okay so this is a page from the Library Functions Manual, bestie, and it's all about the in-memory file hierarchy crew in <9p.h>. Buckle up, we're going in πŸ’–


✨ NAME ✨

Tree, alloctree, freetree, File, createfile, closefile, removefile, walkfile, opendirfile, readdirfile, closedirfile, hasperm β€” literally the whole in-memory file hierarchy. that's the vibe. πŸ¦‹


🌸 SYNOPSIS 🌸

#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <9p.h>

typedef struct File
{
     Ref;
     Dir;
     void*aux;
     ...
} File;

typedef struct Tree
{
     File *root;
     ...
} Tree;

Tree*    alloctree(char *uid, char *gid, ulong mode,
                 void (*destroy)(File*))
void     freetree(Tree *tree)
File*    createfile(File *dir, char *name, char *uid,
                 ulong mode, void *aux)
int      removefile(File *file)
void     closefile(File *file)
File*    walkfile(File *dir, char *path)
Readdir* opendirfile(File *dir)
long     readdirfile(Readdir *rdir, char *buf, long n)
void     closedirfile(Readdir *rdir)
int      hasperm(File *file, char *uid, int p)

like, look at that signature block. she's giving structure. she's giving ... at the end of both structs because there's more and we're not gonna spell it out. iconic πŸ”₯


πŸ’œ DESCRIPTION πŸ’œ

Files and Trees give you an in-memory file hierarchy, and the whole point of them is to be used inside 9P file servers. no cap. 🫢

🌈 the tree itself

  • alloctree β€” creates a brand-new tree of files. the root of the tree (which is also the root element in the structure) gets mode mode, and it's owned by user uid and group gid. the destroy function pointer gets used whenever File structures are freed β€” more on that in a sec, bestie.
  • freetree β€” destroys the whole tree. bye bestie πŸ’€

πŸŽ€ making files

Files (directories included!) other than the root get made with createfile. it attempts to create a file named name inside the directory dir. if it actually gets created, the file will have owner uid and inherit its group from the directory. the mode and the permissions of dir get used to calculate the permission bits for the new file, exactly the way it's described in open(9p). and yes β€” it is permissible for name to be a slash-separated path rather than a single element. go off πŸ¦‹

🧚 removing + walking

  • removefile β€” takes a file out of the file tree. important: the file does not get freed until the last reference to it has been removed. directories may only be removed when they're empty (respect the boundaries, queen πŸ‘‘). returns zero on success, -1 on error. and honestly it's correct to think of removefile as closefile with the extra side effect of removing the file when possible.
  • walkfile β€” evaluates path relative to the directory dir and returns the resulting file, or zero if the named file (or any intermediate element) doesn't exist. no file, no problem, just zero.

πŸ’– aux + reference counting (pay attention here)

the File structure's aux pointer is free real estate β€” the client can use it for per-File storage.

files are reference-counted: if it's not zero, destroy (the one you specified in the call to alloctree) will be called for each file when its last reference is removed, or when the tree is freed. destroy should take care of any cleanup related to aux. 🧹

when you create new file references by copying pointers, call incref (see lock(3)) to update the reference count. when you're noting the removal of a reference to a file, call closefile.

createfile and walkfile return new references. meanwhile removefile, closefile, and walkfile (but not createfile) consume the reference you pass in. yeah. the asymmetry is real. that's the whole game 😭

πŸ“ reading directories

directories can be read, and reading one yields a directory entry structure (see stat(9p)) for every file in that directory. πŸ“‚

because concurrent directory reading is a thing, clients have to grab a Readdir structure by calling opendirfile on the directory. after that, each call to readdirfile yields an integral number of machine-independent stat buffers, and keeps going until end of directory. when you're done, call closedirfile to free the Readdir. clean up after yourself, bestie πŸ’…

⭐ permissions with hasperm

hasperm does permission checking, but keep it real β€” it's simplistic. it assumes only one-user groups, named by uid. it returns non-zero if uid has permission p according to file->mode, where p is a bitwise-or of AREAD, AWRITE and AEXEC. 🌸

9P servers written using File trees do the standard permission checks automatically, so you'd only call hasperm explicitly when you want additional checks. and a 9P server is free to link against a different hasperm implementation if it wants more complex groups. flexible. we love to see it. ✨


πŸ”₯ EXAMPLE πŸ”₯

the following code correctly handles references when elementwise walking a path and creating a file. study her. she's the blueprint. πŸ‘‘

f = tree->root;
incref(f);
for(i=0; i<n && f!=nil; i++)
         f = walkfile(f, elem[i]);
if(f == nil)
         return nil;
nf = createfile(f, "foo", "nls", 0666, nil);
closefile(f);
return nf;

see how walkfile eats the reference and hands back a new one each loop? and then closefile(f) right before returning nf? that's the reference discipline, fr fr 🫢


πŸ¦‹ SOURCE πŸ¦‹

/src/lib9p/file.c


🌈 SEE ALSO 🌈


πŸ’€ BUGS πŸ’€

the reference counting is cumbersome. that's it. that's the bug note. we said what we said. πŸ™ˆ


okay that's the whole page, bestie β€” signing off from the 9P-FILE(3) page of the Library Functions Manual, version 9P-FILE(3), as the footer says. love u, go count some references πŸ’–πŸŽ€βœ¨