VNODEOPS(9)

πŸ“¦ version 11.0, in nixpkgs unstable

🌸 NAME 🌸

Okay bestie, welcome to the VNODEOPS(9) page from the Kernel Developer's Manual πŸ’… This is the whole vnode operations situation: vnodeops, VOP_LOOKUP, VOP_CREATE, VOP_MKNOD, VOP_OPEN, VOP_CLOSE, VOP_ACCESS, VOP_GETATTR, VOP_SETATTR, VOP_READ, VOP_WRITE, VOP_FALLOCATE, VOP_FDISCARD, VOP_IOCTL, VOP_FCNTL, VOP_POLL, VOP_KQFILTER, VOP_REVOKE, VOP_MMAP, VOP_FSYNC, VOP_SEEK, VOP_REMOVE, VOP_LINK, VOP_RENAME, VOP_MKDIR, VOP_RMDIR, VOP_SYMLINK, VOP_READDIR, VOP_READLINK, VOP_ABORTOP, VOP_INACTIVE, VOP_RECLAIM, VOP_LOCK, VOP_UNLOCK, VOP_ISLOCKED, VOP_BMAP, VOP_PRINT, VOP_PATHCONF, VOP_ADVLOCK, VOP_WHITEOUT, VOP_GETPAGES, VOP_PUTPAGES, VOP_STRATEGY, VOP_BWRITE, VOP_GETEXTATTR, VOP_SETEXTATTR, VOP_LISTEXTATTR, VOP_DELETEEXTATTR β€” vnode operations ✨


✨ SYNOPSIS ✨

Like, here’s the include squad first 🫢

       #include <sys/param.h>
       #include <sys/buf.h>
       #include <sys/dirent.h>
       #include <sys/vnode.h>
       #include <sys/mount.h>
       #include <sys/namei.h>
       #include <sys/unistd.h>
       #include <sys/fcntl.h>
       #include <sys/lockf.h>
       #include <sys/extattr.h>

       int
       VOP_LOOKUP(struct vnode *dvp, struct vnode **vpp,
           struct componentname *cnp);

       int
       VOP_CREATE(struct vnode *dvp, struct vnode **vpp,
           struct componentname *cnp, struct vattr *vap);

       int
       VOP_MKNOD(struct vnode *dvp, struct vnode **vpp,
           struct componentname *cnp, struct vattr *vap);

       int
       VOP_OPEN(struct vnode *vp, int mode, kauth_cred_t cred);

       int
       VOP_CLOSE(struct vnode *vp, int fflag, kauth_cred_t cred);

       int
       VOP_ACCESS(struct vnode *vp, int mode, kauth_cred_t cred);

       int
       VOP_GETATTR(struct vnode *vp, struct vattr *vap, kauth_cred_t cred);

       int
       VOP_SETATTR(struct vnode *vp, struct vattr *vap, kauth_cred_t cred);

       int
       VOP_READ(struct vnode *vp, struct uio *uio, int ioflag,
           kauth_cred_t cred);

       int
       VOP_WRITE(struct vnode *vp, struct uio *uio, int ioflag,
           kauth_cred_t cred);

       int
       VOP_FALLOCATE(struct vnode *vp, off_t pos, off_t len);

       int
       VOP_FDISCARD(struct vnode *vp, off_t pos, off_t len);

       int
       VOP_IOCTL(struct vnode *vp, u_long command, void *data, int fflag,
           kauth_cred_t cred);

       int
       VOP_FCNTL(struct vnode *vp, u_int command, void *data, int fflag,
           kauth_cred_t cred);

       int
       VOP_POLL(struct vnode *vp, int events);

       int
       VOP_KQFILTER(struct vnode *vp, struct knote *kn);

       int
       VOP_REVOKE(struct vnode *vp, int flags);

       int
       VOP_MMAP(struct vnode *vp, vm_prot_t prot, kauth_cred_t cred);

       int
       VOP_FSYNC(struct vnode *vp, kauth_cred_t cred, int flags, off_t offlo,
           off_t offhi);

       int
       VOP_SEEK(struct vnode *vp, off_t oldoff, off_t newoff,
           kauth_cred_t cred);

       int
       VOP_REMOVE(struct vnode *dvp, struct vnode *vp,
           struct componentname *cnp);

       int
       VOP_LINK(struct vnode *dvp, struct vnode *vp,
           struct componentname *cnp);

       int
       VOP_RENAME(struct vnode *fdvp, struct vnode *fvp,
           struct componentname *fcnp, struct vnode *tdvp, struct vnode *tvp,
           struct componentname *tcnp);

       int
       VOP_MKDIR(struct vnode *dvp, struct vnode **vpp,
           struct componentname *cnp, struct vattr *vap);

       int
       VOP_RMDIR(struct vnode *dvp, struct vnode *vp,
           struct componentname *cnp);

       int
       VOP_SYMLINK(struct vnode *dvp, struct vnode **vpp,
           struct componentname *cnp, struct vattr *vap, char *target);

       int
       VOP_READDIR(struct vnode *vp, struct uio *uio, kauth_cred_t cred,
           int *eofflag, off_t **cookies, int *ncookies);

       int
       VOP_READLINK(struct vnode *vp, struct uio *uio, kauth_cred_t cred);

       int
       VOP_ABORTOP(struct vnode *dvp, struct componentname *cnp);

       int
       VOP_INACTIVE(struct vnode *vp);

       int
       VOP_RECLAIM(struct vnode *vp);

       int
       VOP_LOCK(struct vnode *vp, int flags);

       int
       VOP_UNLOCK(struct vnode *vp);

       int
       VOP_ISLOCKED(struct vnode *vp);

       int
       VOP_BMAP(struct vnode *vp, daddr_t bn, struct vnode **vpp, daddr_t *bnp,
           int *runp);

       int
       VOP_PRINT(struct vnode *vp);

       int
       VOP_PATHCONF(struct vnode *vp, int name, register_t *retval);

       int
       VOP_ADVLOCK(struct vnode *vp, void *id, int op, struct flock *fl,
           int flags);

       int
       VOP_WHITEOUT(struct vnode *dvp, struct componentname *cnp, int flags);

       int
       VOP_GETPAGES(struct vnode *vp, voff_t offset, struct vm_page **m,
           int *count, int centeridx, vm_prot_t access_type, int advice,
           int flags);

       int
       VOP_PUTPAGES(struct vnode *vp, voff_t offlo, voff_t offhi, int flags);

       int
       VOP_STRATEGY(struct vnode *vp, struct buf *bp);

       int
       VOP_BWRITE(struct vnode *vp, struct buf *bp);

       int
       VOP_GETEXTATTR(struct vnode *vp, int attrnamespace, const char *name,
           struct uio *uio, size_t *size, kauth_cred_t cred);

       int
       VOP_SETEXTATTR(struct vnode *vp, int attrnamespace, const char *name,
           struct uio *uio, kauth_cred_t cred);

       int
       VOP_LISTEXTATTR(struct vnode *vp, int attrnamespace, struct uio *uio,
           size_t *size, kauth_cred_t cred);

       int
       VOP_DELETEEXTATTR(struct vnode *vp, int attrnamespace, const char *name,
           kauth_cred_t cred);

Also lowkey important: not all header files are required for each function. πŸŽ€


πŸ’… DESCRIPTION πŸ’…

Okay so picture the vnode operations vector as the main-character checklist for what can be done to the file associated with a vnode. The system keeps one vnode operations vector for each file system type configured into the kernel. That vector contains a pointer to a function for each operation the file system supports, and honestly it’s giving very much β€œone function per vibe.” Many of the functions described in the vnode operations vector are closely related to their corresponding system calls β€” in most cases, they get called as a result of the system call associated with the operation being invoked. No cap. πŸ’«

Functions in the vnode operations vector are invoked using specialized macros. Here’s the summary cheat sheet, and yes it’s a whole mood:

🌈 the operation cheat sheet 🌈

  • VOP_LOOKUP β€” look up a file name in the name cache πŸ”Ž
  • VOP_CREATE β€” create a new file ✨
  • VOP_MKNOD β€” make a new device πŸ› οΈ
  • VOP_OPEN β€” open a file πŸšͺ
  • VOP_CLOSE β€” close a file 🫢
  • VOP_ACCESS β€” determine file accessibility πŸ”
  • VOP_GETATTR β€” get file attributes πŸ“‹
  • VOP_SETATTR β€” set file attributes ✍️
  • VOP_READ β€” read from a file πŸ“–
  • VOP_WRITE β€” write to a file πŸ–ŠοΈ
  • VOP_FALLOCATE β€” allocate backing for a file 🧱
  • VOP_FDISCARD β€” discard backing for a file πŸ—‘οΈ
  • VOP_IOCTL β€” perform device-specific I/O πŸŽ›οΈ
  • VOP_FCNTL β€” perform file control πŸ•ΉοΈ
  • VOP_POLL β€” test if a poll event has occurred πŸ‘€
  • VOP_KQFILTER β€” register a knote πŸͺ
  • VOP_REVOKE β€” eliminate vnode activity 🚫
  • VOP_MMAP β€” map file into user address space πŸ—ΊοΈ
  • VOP_FSYNC β€” flush pending data to disk πŸ’Ύ
  • VOP_SEEK β€” test if file is seekable 🎯
  • VOP_REMOVE β€” remove a file ❌
  • VOP_LINK β€” link a file πŸ”—
  • VOP_RENAME β€” rename a file 🏷️
  • VOP_MKDIR β€” make a new directory πŸ“
  • VOP_RMDIR β€” remove a directory 🧹
  • VOP_SYMLINK β€” create a symbolic link πŸͺ„
  • VOP_READDIR β€” read directory entry πŸ“‚
  • VOP_READLINK β€” read contents of a symlink 🧚
  • VOP_ABORTOP β€” abort pending operation πŸ›‘
  • VOP_INACTIVE β€” release the inactive vnode 😴
  • VOP_RECLAIM β€” reclaim vnode for another file ♻️
  • VOP_LOCK β€” sleep until vnode lock is free πŸ’€
  • VOP_UNLOCK β€” wake up process sleeping on lock ⏰
  • VOP_ISLOCKED β€” test if vnode is locked πŸ”’
  • VOP_BMAP β€” logical block number conversion πŸ”’
  • VOP_PRINT β€” print debugging information πŸ–¨οΈ
  • VOP_PATHCONF β€” return POSIX pathconf data πŸ“
  • VOP_ADVLOCK β€” advisory record locking πŸ—‚οΈ
  • VOP_WHITEOUT β€” whiteout vnode πŸ‘»
  • VOP_GETPAGES β€” read VM pages from file πŸ“₯
  • VOP_PUTPAGES β€” write VM pages to file πŸ“€
  • VOP_STRATEGY β€” read/write a file system buffer πŸ”
  • VOP_BWRITE β€” write a file system buffer πŸ“
  • VOP_GETEXTATTR β€” get extended attribute 🏷️
  • VOP_SETEXTATTR β€” set extended attribute ✏️
  • VOP_LISTEXTATTR β€” list extended attributes πŸ—’οΈ
  • VOP_DELETEEXTATTR β€” remove extended attribute 🧽

Tea time β˜•: the implementation details of the vnode operations vector are not quite what is described here. I know. Iconic behavior.

If the file system type does not support a specific operation, it must nevertheless assign an appropriate stub in the vnode operations vector to do the minimum required of it. In most cases, such functions either do nothing or return an error value to the effect that it is not supported. Basically: no ghosting the vector, bestie β€” you still gotta put something there. πŸ’€

Many of the functions in the vnode operations vector take a componentname structure. It’s used to encapsulate many parameters into a single function argument. It has the following structure:

       struct componentname {
               /*
                * Arguments to lookup.
                */
               uint32_t cn_nameiop;    /* namei operation */
               uint32_t cn_flags;      /* flags to namei */
               kauth_cred_t cn_cred;   /* credentials */
               /*
                * Shared between lookup and commit routines.
                */
               const char *cn_nameptr; /* pointer to looked up name */
               size_t  cn_namelen;     /* length of looked up component */
               size_t  cn_consume;     /* chars to consume in lookup() */
       };

The top half of the structure is used exclusively for the pathname lookups using VOP_LOOKUP() and is initialized by the caller. The semantics of the lookup are affected by the lookup operation specified in cn_nameiop and the flags specified in cn_flags. Valid operations are:

  • LOOKUP β€” perform name lookup only πŸ‘€
  • CREATE β€” set up for file creation ✨
  • DELETE β€” set up for file deletion πŸ—‘οΈ
  • RENAME β€” set up for file renaming 🏷️
  • OPMASK β€” mask for operation 🎭

Valid values for cn->cn_flags are:

  • LOCKLEAF β€” lock inode on return πŸ”’
  • LOCKPARENT β€” want parent vnode returned locked 🫱
  • NOCACHE β€” name must not be left in name cache (see namecache(9)) 🚫
  • FOLLOW β€” follow symbolic links ➑️
  • NOFOLLOW β€” do not follow symbolic links (pseudo) β›”
  • MODMASK β€” mask of operational modifiers πŸŽ›οΈ
🚨 Warning bestie: no vnode operations may be called from interrupt context. Most operations also require the vnode to be locked on entry. To prevent deadlocks, when acquiring locks on multiple vnodes, the lock of parent directory must be acquired before the lock on the child directory. Lock order is literally the moment. πŸ”₯

Vnode operations for a file system type generally should not be called directly from the kernel, but accessed indirectly through the high-level convenience functions discussed in vnsubr(9) 🫢

✨ FUNCTIONS ✨

Okay bestie, this is the main event πŸ’… β€” every single entry point a vnode op can have. Buckle in.


πŸŽ€ VOP_LOOKUP(dvp, vpp, cnp)

Look up one single pathname component inside a directory. dvp is the locked vnode of the directory you're searching, and cnp is the pathname component you're hunting for. Found it? Then the address of the resulting unlocked vnode goes back to you in vpp. The operation tucked inside cnp->cn_nameiop tells VOP_LOOKUP() why the lookup is happening, and it uses that to cache file-system-type-specific info in the vnode for later operations. iykyk 🧠

There are three flavors of lookup: ".", ".." (that's ISDOTDOT), and regular. If the component you want is ".", then dvp gets an extra reference slapped on and it comes right back to you in *vpp. For any other pathname component, VOP_LOOKUP() checks that the directory is accessible and then searches the name cache for the component. See namecache(9). Not in the name cache? Then it goes and actually searches the directory. The resulting unlocked vnode lands in vpp. And dvp is always returned locked. πŸ”’

On failure, *vpp is NULL and *dvp stays locked. On success, *vpp is unlocked and zero comes back. Pro tip: if *vpp and dvp are the same vnode, the caller typically has to release twice (decrement the reference count) and unlock once. Not me making the rules, that's just how it is 😭


πŸ¦‹ VOP_CREATE(dvp, vpp, cnp, vap)

Make a brand-new file in a given directory, slay. dvp is the locked vnode of the directory where the new file goes, cnp is the pathname component of the new file, and vap spells out the attributes the new file should be created with. Created successfully? Address of the resulting unlocked vnode β†’ vpp, and zero β†’ you. ✨

This gets called after VOP_LOOKUP() when a file is being created. Normally VOP_LOOKUP() will have set the SAVENAME flag in cnp->cn_flags to keep the memory pointed to by cnp->cn_pnbuf valid. If an error shows up while creating the file, that memory gets released. If the file is created successfully, it'll be released too β€” unless the SAVESTART flags in specified in cnp->cn_flags. πŸ™ˆ


πŸŽ€ VOP_MKNOD(dvp, vpp, cnp, vap)

Same energy, but for a new device-special file in a given directory. dvp is the locked vnode of the directory you're creating it in, cnp is the pathname component of the new device-special file, and vap gives the attributes it should be created with. Successful creation β†’ the address of the resulting unlocked vnode in vpp, and zero returned. πŸ’–

Called after VOP_LOOKUP() when a device-special file is being created. Normally VOP_LOOKUP() will have set the SAVENAME flag in cnp->cn_flags so the memory pointed to by cnp->cn_pnbuf stays valid. Error while creating the device-special file? That memory is released. Created successfully? Also released β€” unless the SAVESTART flags in specified in cnp->cn_flags.


🌸 VOP_OPEN(vp, mode, cred)

Open a file, obviously. vp is the vnode of the file to open, mode is the access mode the calling process needs, and the calling credentials are cred. The access mode is a set of flags, including FREAD, FWRITE, O_NONBLOCK, O_APPEND, etc. VOP_OPEN() must be called before a file can be accessed by a thread. The vnode reference count gets incremented. πŸ‘‘

VOP_OPEN() expects the vnode vp to be locked on entry and will leave it locked on return. Success β†’ zero comes back; otherwise you get an appropriate error code. No cap.


🌈 VOP_CLOSE(vp, fflag, cred)

Close a file. vp is the vnode of the file to close, fflag is the access mode of the calling process, and the credentials are cred. Possible flags: FREAD, FWRITE, and FNONBLOCK. VOP_CLOSE() frees the resources that VOP_OPEN() allocated. ♻️

The vnode vp will be locked on entry and should remain locked on return.


⭐ VOP_ACCESS(vp, mode, cred)

Figure out whether the file is accessible (permissions check ✨) against the specified credentials. vp is the vnode of the file to check, mode is the kind of access required, and cred holds the user credentials to check against. mode is a mask that can contain VREAD, VWRITE, or VEXEC. Accessible that way? Zero. Otherwise, an appropriate error code. πŸ”₯

The vnode vp will be locked on entry and should remain locked on return.


πŸŽ€ VOP_GETATTR(vp, vap, cred)

Grab the specific vnode attributes of a file. vp is the vnode of the file you want attributes for, and cred gives the calling credentials. VOP_GETATTR() uses the file-system-type-specific data object vp->v_data to reference the underlying file attributes, and the attributes come back in vap. Anything not available is set to the value VNOVAL. 🫢

For more info on vnode attributes, see vattr(9). Historically it was considered acceptable to call VOP_GETATTR() without first locking the vnode β€” that usage is deprecated. ⚠️

The vnode vp will be locked on entry and should remain locked on return.


🧚 VOP_SETATTR(vp, vap, cred)

Set specific vnode attributes on a file. vp is the locked vnode of the file to set attributes for, and cred is the calling credentials. VOP_SETATTR() uses the file-system-type-specific data object vp->v_data to reference the underlying file attributes, and the new attributes are defined in vap. Anything VOP_SETATTR() is not changing should be set to the value VNOVAL. Success β†’ zero; otherwise, an appropriate error. πŸ’œ

For more info on vnode attributes, see vattr(9).


πŸ“– VOP_READ(vp, uio, ioflag, cred)

Read the contents of a file. vp is the vnode of the file to read from, uio is where the data goes, ioflag is a set of flags, and cred are the credentials of the calling process.

ioflag is how you give directives and hints to the file system. On a read, the high 16 bits provide a read-ahead hint (in units of file system blocks) that the file system should attempt. The low 16 bits are a bit mask that can contain these flags:

  • IO_UNIT β€” do I/O as atomic unit
  • IO_APPEND β€” append write to end
  • IO_SYNC β€” sync I/O file integrity completion
  • IO_NODELOCKED β€” underlying node already locked
  • IO_NDELAY β€” FNDELAY flag set in file table
  • IO_DSYNC β€” sync I/O data integrity completion
  • IO_ALTSEMANTICS β€” use alternate I/O semantics
  • IO_NORMAL β€” operate on regular data
  • IO_EXT β€” operate on extended attributes
  • IO_DIRECT β€” do not buffer data in the kernel

Zero on success, otherwise an error. The vnode should be locked on entry and remains locked on exit. πŸ”’


✍️ VOP_WRITE(vp, uio, ioflag, cred)

Write to a file. vp is the vnode of the file to write to, uio is the location of the data to write, ioflag is a set of flags, and cred are the credentials of the calling process.

ioflag gives directives and hints to the file system β€” same deal as before: the low 16 bits are a bit mask that can contain the same flags as VOP_READ(). πŸŒ€

Zero on success, otherwise an error. The vnode should be locked on entry and remains locked on exit.


πŸ”₯ VOP_FALLOCATE(vp, pos, len)

Allocate backing store. vp is the vnode for the file, and pos and len (specified in bytes) name an extent within the file. The blocks underlying this range β€” rounding up at the top and down at the bottom if needed β€” get checked; if no physical storage is allocated, a physical block is allocated and zeroed. This operation removes β€œholes” from files. πŸ•³οΈβž‘οΈπŸ§±


πŸ’€ VOP_FDISCARD(vp, pos, len)

Discard backing store. vp is the vnode for the file, and pos and len (in bytes) name an extent within the file. The blocks underlying this range β€” rounding down at the top and up at the bottom if needed β€” get checked. If any physical storage is used, it is deallocated. This operation creates β€œholes” in files. Discarded blocks of regular files read back afterwards as zeroes. On devices, the underlying discard-block operation, if any (e.g. ATA TRIM), is issued. The device handles this as it sees fit.

⚠️ For real, be careful here: it is not guaranteed that discarded blocks on devices will be zeroed. Reading a discarded block might produce zeros, or ones, or the previously existing data, or some other data, or trash. Trash!! πŸ˜­πŸ’€

πŸ“ VOP_IOCTL(vp, command, data, fflag, cred)

Perform device-specific I/O. vp is the vnode of the file, normally representing a device. command specifies the device-specific operation to perform, and cnp provides extra data for the specified operation. fflags is a set of flags. cred is the caller's credentials. Success β†’ zero; otherwise, an appropriate error code. πŸŽ›οΈ

Most file systems do not supply a function for VOP_IOCTL(). This function implements the ioctl(2) system call.

VOP_FCNTL(vp, command, data, fflag, cred)

Do the file-control thing πŸ’…. vp is the locked vnode of the file, command says which operation we're doing, and cnp hands over the extra data for that operation. fflags is a set of flags, and cred is the caller's credentials. Successful? You get zero back πŸŽ€. Otherwise you get an appropriate error code. That's the whole deal, no cap.


VOP_POLL(vp, events)

Vibe check on a poll event πŸ‘€ β€” has one actually happened? vp is the vnode of the file we're polling. It returns whatever events of interest have maybe gone down for the file, as specified by events, and events is a set of flags as specified by poll(2).

Heads up bestie: vp stays unlocked the entire time this is running. The whole operation. Don't be weird about it.

VOP_KQFILTER(vp, kn)

Register a knote kn with the vnode vn. Slay ✨. Zero on success, otherwise an appropriate error code comes back at you.

Same energy as above: vp remains unlocked throughout the whole operation, fr fr.

VOP_REVOKE(vp, flags)

Okay so this one is the main character of destruction 😭 β€” it eliminates all activity associated with the vnode vp. flags is a set of flags, and if REVOKEALL is set in flags, then every single vnode aliased to vp gets eliminated too. Zero if it went fine, otherwise an appropriate error.

And yes: vp remains unlocked throughout the whole operation. The way that this keeps happening πŸ’€.

VOP_MMAP(vp, prot, cred)

This is the "hey, heads up" call β€” it informs the file system that vp is currently in the process of being memory mapped 🫢. prot specifies the vm access protection the vnode is going to be mapped with, and cred is the caller's credentials. If the file system is cool with the memory mapping, you get zero; otherwise you get an appropriate error code.

Most file systems don't even supply a function for VOP_MMAP() and instead use genfs_mmap() to default for success β€” iconic shortcut behavior ngl. Only file systems which do not integrate with the page cache at all typically want to disallow memory mapping.


VOP_FSYNC(vp, cred, flags, offlo, offhi)

Flush those pending data buffers for a file out to disk πŸ”₯. vp is the locked vnode of the file to flush, cred is the caller's credentials, and flags is a set of flags. If FSYNC_WAIT is specified in flags, then the function should wait for I/O to complete before returning β€” no leaving early, bestie. offlo and offhi specify the range of the file to flush. Zero on success, otherwise an appropriate error code.

This function implements the sync(2) and fsync(2) system calls. She's literally the backbone πŸ’œ.


VOP_SEEK(vp, oldoff, newoff, cred)

Is the file seekable to this offset, yes or no πŸ¦‹? vp is the locked vnode of the file to test. For most file systems this function simply tests if newoff is valid. And if the specified newoff is less than zero, the function returns error code EINVAL β€” that's it, that's the whole test.


VOP_REMOVE(dvp, vp, cnp)

Remove a file πŸŽ€. dvp is the locked vnode of the directory we're removing the file from, and vp is the locked vnode of the file getting removed. cnp is the pathname component about the file to remove. Zero if it worked, otherwise an appropriate error code.

Both dvp and vp are locked on entry and are to be unlocked before returning. Don't forget, or it's giving deadlock πŸ™ˆ.

VOP_LINK(dvp, vp, cnp)

Make a link to a file ✨. dvp is the locked node of the directory where the new link gets created, and vp is the vnode of the file being linked. cnp is the pathname component of the new link. Zero on success, otherwise an error code.

Locking lore, listen up:

  • The directory vnode dvp should be locked on entry and will be released and unlocked on return.
  • The vnode vp should not be locked on entry and will remain unlocked on return.

VOP_RENAME(fdvp, fvp, fcnp, tdvp, tvp, tcnp)

Rename a file πŸ¦‹. Buckle up, there are six arguments:

  • fdvp β€” the vnode of the old parent directory containing the file to be renamed
  • fvp β€” the vnode of the file to be renamed
  • fcnp β€” the pathname component about the file to be renamed
  • tdvp β€” the vnode of the new directory of the target file
  • tvp β€” the vnode of the target file (if it exists)
  • tcnp β€” the pathname component about the file's new name

Zero if it worked, otherwise an error code.

Locking rules, and they are a lot 😭:

  • The caller must hold the target file system's rename lock.
  • The source directory and file vnodes should be unlocked and their reference counts should be incremented before entry.
  • The target directory and file vnodes should both be locked on entry.
  • VOP_RENAME() updates the reference counts prior to returning.
⚠️ real talk ⚠️ Because of the complexity and nastiness of the interface, please do not write new code that calls VOP_RENAME() directly until such time as ongoing cleanup work reaches a point where the interface has been rendered halfway sane. That's a direct quote from the vibe, bestie. Do not do it. πŸ’€

VOP_MKDIR(dvp, vpp, cnp, vap)

Make a new directory inside a given directory 🌸. dvp is the locked vnode of the directory to create the new directory in, and cnp is the pathname component of the new directory. vap specifies the attributes that the new directory should be created with. If the file is successfully created, the address of the resulting unlocked vnode is returned in vpp and zero is returned.

Background tea πŸ“: this function is called after VOP_LOOKUP() when a directory is being created. Normally, VOP_LOOKUP() will have set the SAVENAME flag in cnp->cn_flags to keep the memory pointed to by cnp->cn_pnbuf valid. If an error is detected when creating the directory, this memory is released. If the directory is created successfully it will be released unless the SAVESTART flags in specified in cnp->cn_flags.


VOP_RMDIR(dvp, vp, cnp)

Remove a directory from a given directory 🧚. dvp is the locked vnode of the directory to remove the directory from, and vp is the locked vnode of the directory to remove. cnp is the pathname component of the directory. Zero on success, otherwise an error code.

Both dvp and vp should be locked on entry and will be released and unlocked on return. Same rules as VOP_REMOVE, minus the file energy.

VOP_SYMLINK(dvp, vpp, cnp, vap, target)

Create a symbolic link inside a given directory πŸ’œ. dvp is the locked vnode of the directory to create the symbolic link in, and cnp is the pathname component of the symbolic link. vap specifies the attributes that the symbolic link should be created with, and target specifies the pathname of the target of the symbolic link. If the symbolic link is successfully created, the address of the resulting unlocked vnode is returned in vpp and zero is returned.

Same backstory as VOP_MKDIR πŸŽ€: this is called after VOP_LOOKUP() when a symbolic link is being created. Normally, VOP_LOOKUP() will have set the SAVENAME flag in cnp->cn_flags to keep the memory pointed to by cnp->cn_pnbuf valid. If an error is detected when creating the symbolic link, this memory is released. If the symbolic link is created successfully it will be released unless the SAVESTART flags in specified in cnp->cn_flags.


VOP_READDIR(vp, uio, cred, eofflag, cookies, ncookies)

Read directory entries πŸ‘‘. The cast:

  • vp β€” the vnode of the directory to read the contents of
  • uio β€” the destination location to read the contents into
  • cred β€” the caller's credentials
  • eofflag β€” pointer to a flag which is set by VOP_READDIR() to indicate an end-of-file condition. If eofflag is NULL, the end-of-file condition is not returned.
  • cookies and ncookies β€” the addresses for the list and number of directory seek cookies generated for NFS. Both cookies and ncookies should be NULL if they aren't required to be returned by VOP_READDIR().

The directory contents are read into struct dirent structures, and uio->uio_offset is set to the offset of the next unread directory entry. That offset may be used in a following invocation to continue a sequential read of the directory contents. Zero if it worked, otherwise an appropriate error code.

The directory should be locked on entry and will remain locked on return. Locked in, locked out πŸ’….

Cookie rules, because this part is a whole thing πŸͺ: in case ncookies and cookies are supplied, one cookie should be returned per directory entry. The value of the cookie for each directory entry should be the offset within the directory where the on-disk version of the following directory entry starts. That is, for each directory entry i, the corresponding cookie should refer to the offset of directory entry i + 1.

⚠️ memory warning ⚠️ Note that the cookies array must be allocated by the callee using the M_TEMP malloc type as callers of VOP_READDIR() must be able to free the allocation. Respect the allocator and nobody gets hurt 😭.

VOP_READLINK(vp, uio, cred)

Read what's inside a symbolic link 🌈. vp is the locked vnode of the symlink, uio is the destination location to read the contents into, and cred is the credentials of the caller. Zero if it worked, otherwise an error code.

The vnode should be locked on entry and will remain locked on return. Consistency is her thing πŸ’–.

VOP_ABORTOP(dvp, cnp)

Abort a pending operation on vnode dvp and free resources allocated in cnp πŸ™ˆ. Short and sweet.

This operation is rarely implemented in file systems, and genfs_abortop() is typically used instead β€” so if you see it in the wild, that's the substitute teacher doing the job ⭐.

πŸ’… VOP_INACTIVE(vp)

Okay so bestie, this one frees the inactive vnode. VOP_INACTIVE() gets called when the kernel is officially done using the vnode β€” like, no cap, it's over. This can happen because the reference count hit zero, or because the file system is being forcibly unmounted while there are still open files (messy breakup energy 😭). It can be used to reclaim space for open but deleted files.

The argument vp is the locked vnode to be released. If it works, zero comes back; otherwise you get an appropriate error code. And listen β€” vp must be locked on entry, and it stays locked on return. No exceptions, periodt.


πŸ¦‹ VOP_RECLAIM(vp)

Reclaiming the vnode for another file system, bestie ✨. VOP_RECLAIM() is called when a vnode is being reused for a different file system, so any file-system-specific resources tied to that vnode should get freed β€” release the baggage, it's giving moving-on era.

The argument vp is the vnode to be reclaimed. Zero on success, otherwise an appropriate error code. And heads up: vp should be locked on entry, and it will be returned unlocked β€” so it enters locked and leaves unlocked, the glow-up is real πŸ’….


πŸ”’ VOP_LOCK(vp, flags)

Sleep until the vnode lock is free β€” yes, literally sleep, we love a nap break 😴.

  • vp is the vnode of the file to be locked.
  • flags is LK_EXCLUSIVE to take the lock exclusively, or LK_SHARED for a shared lock.
  • If flags contains LK_NOWAIT and the lock is busy, the operation returns immediately with an error code (no waiting around, she's got places to be).
  • If flags contains LK_RETRY, that's a hint the caller wants the lock on dead vnodes too.

Zero on success, otherwise an appropriate error code. VOP_LOCK() is used to serialize access to the file system β€” like, to prevent two writes to the same file from happening at the same time. Iconic coordination behavior, honestly.

⚠️ Kernel code should use vn_lock(9) to lock a vnode rather than calling VOP_LOCK() directly. Fr fr, use the proper entry point.

🌈 VOP_UNLOCK(vp)

Wake up the process sleeping on the lock β€” rise and shine, bestie β°πŸ’–.

The argument vp is the vnode of the file to be unlocked. Zero on success, otherwise an appropriate error code. VOP_UNLOCK() is used to serialize access to the file system, same deal as before: it's there to prevent two writes to the same file from happening at the same time.


🧚 VOP_ISLOCKED(vp)

Test if the vnode vp is locked. The possible return values are:

  • LK_EXCLUSIVE β€” lock held exclusively by the calling thread
  • LK_SHARED β€” shared lock held by anyone
  • 0 β€” unlocked
πŸ’€ Crucial caveat, do not skip: this function must never be used to make locking decisions at run time. It is provided only for diagnostic purposes. It's a vibe check, not a decision-maker β€” iykyk.

πŸ“ VOP_BMAP(vp, bn, vpp, bnp, runp)

Convert the logical block number bn of a file specified by vnode vp to its physical block number on the disk β€” a little logical-to-physical makeover ✨.

  • The physical block is returned in bnp. If the logical block isn't allocated, -1 is used.
  • If vpp is not NULL, the vnode of the device vnode for the file system is returned in the address specified by vpp.
  • If runp is not NULL, the number of contiguous blocks starting from the next block after the queried block is returned in runp.

πŸŽ€ VOP_PRINT(vp)

Print debugging information πŸ›βœ¨. The argument vp is the vnode to print. Zero on success, otherwise an appropriate error code. She's just here to spill the tea about what's going on.


πŸ‘‘ VOP_PATHCONF(vp, name, retval)

Implement POSIX pathconf(2) and fpathconf(2) support β€” the POSIX girlies stay fed πŸ’….

  • vp is the locked vnode to get information about.
  • name specifies the type of information to return.
  • The info is returned in the address specified by retval.

Valid values for name are:

  • _PC_LINK_MAX β€” return the maximum number of links to a file
  • _PC_NAME_MAX β€” return the maximum number of bytes in a file name
  • _PC_PATH_MAX β€” return the maximum number of bytes in a pathname
  • _PC_PIPE_BUF β€” return the maximum number of bytes which will be written atomically to a pipe
  • _PC_CHOWN_RESTRICTED β€” return 1 if appropriate privileges are required for the chown(2) system call, otherwise zero
  • _PC_NO_TRUNC β€” return 0 if file names longer than {NAME_MAX} are silently truncated

If name is recognized, *retval is set to the specified value and zero is returned; otherwise an appropriate error is returned.


🫢 VOP_ADVLOCK(vp, id, op, fl, flags)

Manipulate advisory record locks on a vnode β€” lock gossip, but make it ~advisory~ πŸ”.

  • vp is the vnode on which locks are manipulated.
  • id is the id token which is changing the lock.
  • op is the fcntl(2) operation to perform.

Valid values for op are:

  • F_SETLK β€” set lock
  • F_GETLK β€” get the first conflicted lock
  • F_UNLCK β€” clear lock

The argument fl is a description of the lock. In the case of SEEK_CUR, the caller should add the current file offset to fl->l_start beforehand β€” and VOP_ADVLOCK() treats SEEK_CUR as SEEK_SET.

The argument flags is the set of flags. Valid values are:

  • F_WAIT β€” wait until lock is granted
  • F_FLOCK β€” use flock(2) semantics for lock
  • F_POSIX β€” use POSIX semantics for lock

Zero on success, otherwise an appropriate error is returned.


🌸 VOP_WHITEOUT(dvp, cnp, flags)

Whiteout a pathname component in the directory with vnode dvp β€” ghosting a pathname component, honestly πŸ’€. The argument cnp specifies the pathname component to whiteout.

dvp should be locked on entry and will remain locked on return. Locked going in, locked coming out, no drama.

😭 VOP_GETPAGES(vp, offset, m, count, centeridx, access_type, advice, flags)

Read VM pages from a file β€” page girlies rise πŸ¦‹.

  • vp is the locked vnode to read the VM pages from.
  • offset is the offset in the file to start accessing.
  • m is an array of VM pages.
  • count points to a variable that specifies the number of pages to read.

Zero on success, otherwise an appropriate error code. If PGO_LOCKED is specified in flags, VOP_GETPAGES() might return less pages than requested β€” and in that case, the variable pointed to by count gets updated. Not a bug, just her being flexible πŸ’….

This function is primarily used by the page-fault handling mechanism.


πŸ”₯ VOP_PUTPAGES(vp, offlo, offhi, flags)

Write modified (dirty) VM pages to a file β€” flushing the dirty laundry, but make it VM pager chic 🧺✨.

  • vp is the vnode to write the VM pages to. The vnode's vm object lock (v_uobj.vmobjlock) must be held by the caller and will be released upon return.
  • offlo and offhi specify the range of VM pages to write. In case offhi is given as 0, all pages at and after the start offset offlo belonging to the vnode vp will be written.
  • flags controls the behavior of the routine and takes the vm pager's flags (PGO_-prefixed).

Zero on success, otherwise an appropriate error code.

This function is primarily used by the pageout handling mechanism, and is commonly implemented indirectly by genfs_putpages() with the help of VOP_STRATEGY() and VOP_BMAP() β€” teamwork makes the dream work 🫢.


⭐ VOP_STRATEGY(vp, bp)

Read/write a file system buffer β€” the main character of the buffer world, ngl πŸ‘‘.

  • vp is the vnode to read/write to.
  • bp is the buffer to be read or written.

VOP_STRATEGY() will either read or write data to the file depending on the value of bp->b_flags. Zero on success, otherwise an appropriate error code.


πŸŽ€ VOP_BWRITE(vp, bp)

Write a file system buffer β€” simpler than VOP_STRATEGY(), and she knows it πŸ’….

  • vp is the vnode to write to.
  • bp specifies the buffer to be written.

Zero on success, otherwise an appropriate error code.


πŸ’œ VOP_GETEXTATTR(vp, attrnamespace, name, uio, size, cred)

Get an extended attribute β€” fetching the extra metadata, the hidden lore of a file πŸ•΅οΈβ€β™€οΈβœ¨.

  • vp is the locked vnode of the file or directory from which to retrieve the attribute.
  • attrnamespace specifies the extended attribute namespace.
  • name is a nul-terminated character string naming the attribute to retrieve.
  • uio, if not NULL, specifies where the extended attribute value is to be written.
  • size, if not NULL, will contain the number of bytes required to read all of the attribute data upon return. In most cases, uio will be NULL when size is not, and vice versa β€” they're giving "never in the same place at the same time" 😭.
  • cred specifies the user credentials to use when authorizing the request.

🌈 VOP_SETEXTATTR(vp, attrnamespace, name, uio, cred)

Set an extended attribute β€” dropping that metadata in, no cap πŸ’….

  • vp is the locked vnode of the file or directory to which to store the attribute.
  • namespace specifies the extended attribute namespace.
  • name is a nul-terminated character string naming the attribute to store.
  • uio specifies the source of the extended attribute data.
  • cred specifies the user credentials to use when authorizing the request.

🧚 VOP_LISTEXTATTR(vp, attrnamespace, uio, size, cred)

Retrieve the list of extended attributes β€” the full roster, bestie πŸ“‹βœ¨.

  • vp is the locked vnode of the file or directory whose attributes are to be listed.
  • attrnamespace specifies the extended attribute namespace.
  • uio, if not NULL, specifies where the extended attribute list is to be written.
  • size, if not NULL, will contain the number of bytes required to read all of the attribute names upon return. In most cases, uio will be NULL when size is not, and vice versa β€” same chaotic duo energy as before 🫠.
  • cred specifies the user credentials to use when authorizing the request.
  • VOP_DELETEEXTATTR(vp, attrnamespace, name, cred) β€” bestie, this one removes the attribute called name from the file that vp is pointing at. The attrnamespace argument tells it which extended attribute namespace you're working in (iykyk). And here's the tea: if the file system can't do a full removal, it should return EOPNOTSUPP β€” that way the caller still has a way out, zeroing out the value with VOP_SETEXTATTR() instead. Teamwork, honestly. πŸ’…
vp should be locked on entry and it stays locked on return. No sneaky unlocking, periodt. πŸ”’

πŸ“ FILES πŸŽ€

  • src/sys/kern/vnode_if.src β€” this iconic file has the whole list of vnode functions, their definitions, and the exact locking protocol. Like, the receipts. Read it. 🫢

πŸ’€ ERRORS πŸ’€

Okay so when things go sideways, here's what the kernel might throw at you:

  • [EACCES] β€” nope, access for that operation is denied πŸ™…
  • [EDQUOT] β€” quota exceeded, you've been a little too greedy with the disk space πŸ“
  • [EINVAL] β€” someone tried to read from an illegal offset in the directory, or the input was just unrecognized 😭
  • [EIO] β€” a read error popped off while reading the directory or reading the contents of a symbolic link, tragic fr
  • [EJUSTRETURN] β€” a CREATE or RENAME operation would be successful, so like, it's fine actually ✨
  • [ENOATTR] β€” the attribute you asked for isn't defined for this vnode, sorry bestie πŸ™ˆ
  • [ENOENT] β€” that component was simply not found in the directory, rip
  • [ENOSPC] β€” file system is full, no cap, there's no room 🌸
  • [ENOTDIR] β€” this vnode does not represent a directory, wrong vibe entirely
  • [ENOTEMPTY] β€” attempt to remove a directory that isn't empty yet, rude
  • [EPERM] β€” someone tried to change an immutable file, absolutely not πŸ‘‘
  • [EROFS] β€” the file system is read-only, you literally cannot, bestie

🌈 SEE ALSO 🌈

extattr(9), intro(9), namei(9), vattr(9), vfs(9), vfsops(9), vnode(9)

Go read them, they're all iconic in their own way πŸ’–


πŸ¦‹ HISTORY πŸ¦‹

The vnode operations vector, its functions and the corresponding macros showed up all the way back in 4.3BSD. Like, ancient. Main character energy since day one, honestly. ⭐


Okay that's the whole page, bestie β€” go forth and slay your vnode ops πŸ’…βœ¨ This one came from Debian, dated June 15, 2023. Mwah, study hard, drink water, and don't forget to lock your vnodes. πŸŽ€πŸ«Ά

πŸ“¦ where this page comes from