GENFS_RENAME(9)

πŸ“¦ version 11.0, in nixpkgs unstable

✨ NAME ✨

Okay bestie, this is GENFS_RENAME(9) from the Kernel Developer's Manual β€” the whole generic framework page for implementing VOP_RENAME(9). It covers genfs_rename, genfs_insane_rename, and genfs_sane_rename, and the vibe is very β€œwe are not doing rename locking by hand in this house.” πŸ’…

πŸ“œ SYNOPSIS πŸ“œ

int
genfs_insane_rename(struct vop_rename_args *v,
    int (*sane_rename)(struct vnode *fdvp, struct componentname *fcnp, struct vnode *tdvp, struct componentname *tcnp, kauth_cred_t, bool));

int
genfs_sane_rename(const struct genfs_rename_ops *gro,
    struct vnode *fdvp, struct componentname *fcnp, void *fde,
    struct vnode *tdvp, struct componentname *tcnp, void *tde,
    kauth_cred_t cred, bool posixly_correct);

int
genfs_rename_knote(struct vnode *fdvp, struct vnode *fvp,
    struct vnode *tdvp, struct vnode *tvp);

void
genfs_rename_cache_purge(struct vnode *fdvp, struct vnode *fvp,
    struct vnode *tdvp, struct vnode *tvp);

int
genfs_ufslike_rename_check_possible(unsigned long fdflags,
    unsigned long fflags, unsigned long tdflags, unsigned long tflags,
    bool clobber, unsigned long immutable, unsigned long append);

int
genfs_ufslike_rename_check_permitted(kauth_cred_t cred,
    struct vnode *fdvp, mode_t fdmode, uid_t fduid, struct vnode *fvp,
    uid_t fuid, struct vnode *tdvp, mode_t tdmode, uid_t tduid,
    struct vnode *tvp, uid_t tuid);

int
genfs_ufslike_remove_check_possible(unsigned long dflags,
    unsigned long flags, unsigned long immutable, unsigned long append);

int
genfs_ufslike_remove_check_permitted(kauth_cred_t cred,
    struct vnode *dvp, mode_t dmode, uid_t duid, struct vnode *vp,
    uid_t uid);

πŸ¦‹ DESCRIPTION πŸ¦‹

The genfs_rename functions are the file-system-independent framework for implementing VOP_RENAME(9) with correct locking and error-checking. They're here so you don't have to hand-roll every terrifying little step yourself. 🫢

Real talk: implementing rename is nontrivial. If you're doing it for a new file system, you should seriously consider starting from tmpfs_rename() as implemented in sys/fs/tmpfs/tmpfs_rename.c and adapting it to your file system's physical operations. That's the move. ✨

Because there are so many moving parts to a rename operation, genfs_rename uses the following naming conventions:

  • mp (mount point) β€” mount point of the file system in question.
  • fdvp (from directory vnode pointer) β€” directory from which we are removing an entry.
  • fcnp (from componentname pointer) β€” name of entry to remove from fdvp.
  • fde (from directory entry) β€” fs-specific data about the entry in fdvp.
  • fvp (from vnode pointer) β€” file at the entry named fcnp in fdvp.
  • tdvp (to directory vnode pointer) β€” directory to which we are adding an entry.
  • tcnp (to componentname pointer) β€” name of entry to add to tdvp.
  • tde (to directory entry) β€” fs-specific data about the entry in tdvp.
  • tvp (to vnode pointer) β€” file previously at the entry named tcnp in tdvp, to be replaced, if any, or NULL if there was no entry before.
  • vp (vnode pointer) β€” any file.
  • dvp (directory vnode pointer) β€” any directory with an entry for vp.

A file system mumblefs should implement various file-system-dependent parts of the rename operation in a struct genfs_rename_ops, and use genfs_rename to implement mumblefs_rename() for VOP_RENAME(9) as follows:

      static const struct genfs_rename_ops mumblefs_genfs_rename_ops;

      static int
      mumblefs_sane_rename(
          struct vnode *fdvp, struct componentname *fcnp,
          struct vnode *tdvp, struct componentname *tcnp,
          kauth_cred_t cred, bool posixly_correct)
      {
              struct mumblefs_lookup_results fulr, tulr;

              return genfs_sane_rename(&mumblefs_genfs_rename_ops,
                  fdvp, fcnp, &fulr, tdvp, tcnp, &tulr,
                  cred, posixly_correct);
      }

      int
      mumblefs_rename(void *v)
      {

              return genfs_insane_rename(v, &mumblefs_sane_rename);
      }

The split between mumblefs_rename() and mumblefs_sane_rename() is designed to enable us to easily change the VOP_RENAME(9) interface, which is currently designed for a broken (hence β€˜insane’) locking scheme, to a more sensible locking scheme once all the file systems have their rename operations split up thus. πŸ’…

The struct mumblefs_lookup_results structure is storage for information about directory entries which needs to pass from the lookups of the children (see the gro_lookup member of struct genfs_rename_ops) to the physical on-disk rename or remove operations (see the gro_rename and gro_remove members of struct genfs_rename_ops).

Callers must implement the following operations as members in a struct genfs_rename_ops structure passed to genfs_rename:

πŸ” gro_genealogy

int (*gro_genealogy)(struct mount *mp, kauth_cred_t cred, struct vnode
        *fdvp, struct vnode *tdvp, struct vnode **intermediate_node_ret)

Walk up the directory tree from the directory vnode tdvp until hitting either fdvp or the root. If fdvp is hit, store the child of fdvp through which the path from tdvp passed in *intermediate_node_ret, referenced but unlocked. If fdvp is not hit, store NULL in *intermediate_node_ret. Return zero on success or error on failure. (Failure means file-system-specific failures, not hitting or missing fdvp.)

fdvp and tdvp are guaranteed to be distinct, non-null, referenced, and unlocked. Since no locks are held on entry except for the file-system-wide rename lock, gro_genealogy may take any locks it pleases.

πŸ”’ gro_lock_directory

int (*gro_lock_directory)(struct mount *mp, struct vnode *vp)

Lock the directory vnode vp, but fail if it has been rmdired already. Return zero on success or error on failure.

πŸ”Ž gro_lookup

int (*gro_lookup)(struct mount *mp, struct vnode *dvp, struct
        componentname *cnp, void *de, struct vnode **vpp)

Look up the entry in dvp for cnp, storing the vnode in *vpp and using de, one of the pointers passed to genfs_sane_rename, to store information about the directory entry as needed by the file system's gro_rename operation, and return zero. If there is no such entry, return error.

dvp is guaranteed to be locked, and the vnode returned in *vpp must be unlocked. However, gro_lookup may temporarily lock the vnode without causing deadlock.

🫧 gro_directory_empty_p

bool (*gro_directory_empty_p)(struct mount *mp, kauth_cred_t cred,
        struct vnode *vp, struct vnode *dvp)

Return true if the directory vnode vp is empty. The argument dvp is the parent of vp, as required for this check by some file systems.

dvp and vp are guaranteed to be distinct, non-null, referenced, and locked.

βœ… gro_rename_check_possible

int (*gro_rename_check_possible)(struct mount *mp, struct vnode *fdvp,
        struct vnode *fvp, struct vnode *tdvp, struct vnode *tvp)

Return zero if the file system might allow the rename independent of credentials, or error if not. This should check, for example, any immutability flags in the vnodes in question, and should use genfs_ufslike_rename_check_possible() for file systems similar to UFS/FFS.

fdvp and tdvp may be the same; every other pair of vnodes is guaranteed to be distinct. tvp may be NULL; every other vnode is guaranteed to be non-null. All three or four vnodes are guaranteed to be referenced and locked.

πŸ›‚ gro_rename_check_permitted

int (*gro_rename_check_permitted)(struct mount *mp, kauth_cred_t cred,
        struct vnode *fdvp, struct vnode *fvp, struct vnode *tdvp,
        struct vnode *tvp)

Return zero if the file system allows the rename given the credentials cred, or error if not. This should check, for example, the ownership and permissions bits of the vnodes in question, and should use genfs_ufslike_rename_check_permitted() for file systems similar to UFS/FFS.

fdvp and tdvp may be the same; every other pair of vnodes is guaranteed to be distinct. tvp may be NULL; every other vnode is guaranteed to be non-null. All three or four vnodes are guaranteed to be referenced and locked.

πŸ” gro_rename

int (*gro_rename)(struct mount *mp, kauth_cred_t cred, struct vnode
        *fdvp, struct componentname *fcnp, void *fde, struct vnode *fvp,
        struct vnode *tdvp, struct componentname *tcnp, void *tde,
        struct vnode *tvp)

Perform the physical file system rename operation, report any knotes, and purge the namecache entries. Return zero on success or error on failure. All file-system-independent error cases have been handled already.

File systems using fstrans(9) should use fstrans_start(9) and fstrans_done(9) here. fde and tde are the pointers that were supplied to genfs_sane_rename() and got passed to the gro_lookup operation to find information about directory entries.

This may use genfs_rename_knote() to report any knotes, if the various file-system-dependent routines it uses to edit links don't do that already. This should use genfs_rename_cache_purge() to purge the namecache.

fdvp and tdvp may be the same; every other pair of vnodes is guaranteed to be distinct. tvp may be null; every other vnode is guaranteed to be non-null. All three or four vnodes are guaranteed to be referenced and locked.

🧹 gro_remove_check_possible

int (*gro_remove_check_possible)(struct mount *mp, struct vnode *dvp,
        struct vnode *vp)

Return zero if the file system might allow removing an entry in dvp for vp independent of credentials, or error if not. This should use genfs_ufslike_remove_check_possible() for file systems similar to UFS/FFS.

dvp and vp are guaranteed to be distinct, non-null, referenced, and locked.

This, and gro_remove_check_permitted below, are for renames that reduce to a remove; that is, renaming one entry to another when both entries refer to the same file. For reasons of locking insanity, genfs_rename cannot simply call VOP_REMOVE(9) instead.

🧼 gro_remove_check_permitted

int (*gro_remove_check_permitted)(struct mount *mp, kauth_cred_t cred,
        struct vnode *dvp, struct vnode *vp)

Return zero if the file system allows removing an entry in dvp for vp given the credentials cred, or error if not. This should use genfs_ufslike_remove_check_permitted() for file systems similar to UFS/FFS.

dvp and vp are guaranteed to be distinct, non-null, referenced, and locked.

  • int (*gro_remove)(struct mount *mp, kauth_cred_t cred, struct vnode *dvp, struct componentname *cnp, void *de, struct vnode *vp) β€” so when a rename is basically just a remove πŸ’…, this is the hook that does the actual physical file system remove, reports any knotes, and purges the namecache entries. Return zero on success or an error on failure. Good news: all the file-system-independent error cases have already been handled for you before you ever get here 🫢

File systems that use fstrans(9) should call fstrans_start(9) and fstrans_done(9) right here. The de pointer is one of the pointers that got supplied to genfs_sane_rename() and then passed along to the gro_lookup operation to find info about directory entries ✨

You should signal a NOTE_WRITE knote for dvp, and either a NOTE_DELETE or a NOTE_LINK knote for vp β€” depends on whether this nuked the last link to it or not, iykyk πŸŽ€

dvp and vp are guaranteed to be distinct, non-null, referenced, and locked. No cap, that's the contract πŸ”₯


The following utilities are provided for implementing the struct genfs_rename_ops operations β€” think of them as the little helper besties that do the fiddly bits for you πŸ’–

  • genfs_rename_knote(fdvp, fvp, tdvp, tvp) β€” signals all the knotes relevant for the rename operation. All of them. Every single one πŸ””
  • genfs_rename_cache_purge(fdvp, fvp, tdvp, tvp) β€” purges any namecache entries that the rename operation invalidates. Clean slate energy ✨
  • genfs_ufslike_rename_check_possible(fdflags, fflags, tdflags, tflags, clobber, immutable, append) β€” checks whether the UFS/FFS-like flags of the files involved in a rename actually allow it. Return zero if allowed, or an error if not πŸ™…β€β™€οΈ
    • fdflags β€” flags of source directory
    • fflags β€” flags of source file
    • tdflags β€” flags of target directory
    • tflags β€” flags of target file, if there is one and clobber is true, or ignored otherwise
    • clobber β€” true if there is a target file whose entry will be clobbered, or false if not
    • immutable β€” bit mask for the file system's immutable bit, like the UFS/FFS IMMUTABLE
    • append β€” bit mask for the file system's append-only bit, like the UFS/FFS APPEND
  • genfs_ufslike_rename_check_permitted(cred, fdvp, fdmode, fduid, fvp, fuid, tdvp, tdmode, tduid, tvp, tuid) β€” checks whether the credentials cred are permitted by the file ownership and permissions bits to perform a rename. Return zero if permitted, or an error if not πŸ”
    • cred β€” caller's credentials
    • fdvp β€” source directory
    • fdmode β€” file permissions bits of fdvp
    • fduid β€” uid of the owner of fdvp
    • fvp β€” source file
    • fuid β€” uid of owner of fvp
    • tdvp β€” target directory
    • tdmode β€” file permissions bits of tdvp
    • tduid β€” uid of owner of tdvp
    • tvp β€” target file, if there is one, or NULL if not
    • tuid β€” uid of owner of tvp, if there is a target file, or ignored otherwise
  • genfs_ufslike_remove_check_possible(dflags, flags, immutable, append) β€” checks whether the UFS/FFS-like flags of the files involved in a remove allow it. Return zero if allowed, or an error if not 🚫
    • dflags β€” flags of the directory
    • flags β€” flags of the file in the directory
    • immutable β€” bit mask for the file system's immutable bit, like the UFS/FFS IMMUTABLE
    • append β€” bit mask for the file system's append-only bit, like the UFS/FFS APPEND
  • genfs_ufslike_remove_check_permitted(cred, dvp, dmode, duid, vp, uid) β€” checks whether the credentials cred are permitted by the file ownership and permissions bits to perform a remove. Return zero if permitted, or an error if not πŸ”
    • cred β€” caller's credentials
    • dvp β€” directory
    • dmode β€” file permissions bits of dvp
    • duid β€” uid of owner of dvp
    • vp β€” file in dvp
    • uid β€” uid of owner of vp

πŸ¦‹ NOTES πŸ¦‹

Okay bestie, listen up, because this is the part where things get unhinged 😭 There are so many cases of rename that you literally cannot assume a priori that any pairs of fdvp, fvp, tdvp, or fvp are distinct:

fdvp = fvp     rename("a/.", "b")
fdvp = tdvp    rename("a/b", "a/c")
fdvp = tvp     rename("a/b", "a")
fvp = tdvp     rename("a", "a/b")
fvp = tvp      rename("a", "a")
tdvp = tvp     rename("a", "b/.")

Handling all these cases correctly, and getting the locking correct and deadlock-free, is very tricky β€” which is literally the entire reason genfs_rename exists πŸ’€ Like, that's its whole main character thing.

The interface to genfs_rename is very complicated because it must fit the insane VOP_RENAME(9) and VOP_LOOKUP(9) protocols until we can fix them, and because it must accommodate a variety of crufty file systems. Yes, both. At the same time. We're all doing our best here πŸ™ˆ

πŸ‘‘ SEE ALSO πŸ‘‘

genfs(9), vfs(9), vnodeops(9) β€” go read these, they're the supporting cast and they ate 🫢


🌸 HISTORY 🌸

genfs_rename was designed and implemented by Taylor R. Campbell <riastradh@NetBSD.org> after many discussions with David Holland <dholland@NetBSD.org>, and first appeared in NetBSD 6.0 ⭐


That's the whole page bestie, you made it to the end πŸŽ‰ This lil legend is from Debian, dated May 1, 2013, and it answers to the name GENFS_RENAME(9) β€” go forth and rename responsibly πŸ’œβœ¨

πŸ“¦ where this page comes from