move_mount(2)

🌸 move_mount(2) β€” System Calls Manual 🌸

okay bestie, this is move_mount from the System Calls Manual, and it is a lot, so we're doing this in parts. part 1, lesgo πŸ’–


πŸŽ€ NAME πŸŽ€

move_mount β€” move or attach a mount object to a filesystem. that's it, that's the name section, iconic.

πŸ“š LIBRARY πŸ“š

Standard C library (libc, -lc) ✨

✨ SYNOPSIS ✨

#include <fcntl.h>          /* Definition of AT_* constants */
#include <sys/mount.h>

int move_mount(int from_dirfd, const char *from_path,
               int to_dirfd, const char *to_path,
               unsigned int flags);

πŸ’– DESCRIPTION πŸ’–

okay so move_mount() is one of the whole suite of file-descriptor-based mount facilities in Linux. it's giving very modern, very fancy.

what it does: move_mount() moves the mount object that you point at with from_dirfd and from_path over to the path you point at with to_dirfd and to_path. and the mount object you're moving can be either:

  • an existing mount point chillin' in the current mount namespace, or
  • a detached mount object, like the ones fsmount(2) or open_tree(2) make for you when you pass OPEN_TREE_CLONE.

here's the cute part: to reach the source mount object or the destination mount point, you don't need any permissions on the object itself. BUT β€” and this is the catch, bestie β€” if either pathname is actually supplied, then you need execute (search) permission on all the directories listed in from_path or to_path. every single one. no skipping. πŸ™ˆ

also the calling process must have the CAP_SYS_ADMIN capability to move or attach a mount object. no cap, no mount. that's just the rules. πŸ’…


🌈 how the *_dirfd + *_path combos work 🌈

just like the *at() system calls, move_mount() teams up from_dirfd and to_dirfd with from_path and to_path to figure out the source and destination objects (respectively). the tea is:

  • If the pathname given in *_path is absolute, then the corresponding *_dirfd is ignored. bye, you're not needed. πŸ’€
  • If the pathname given in *_path is relative and the corresponding *_dirfd is the special value AT_FDCWD, then *_path is interpreted relative to the current working directory of the calling process (exactly like open(2) does).
  • If the pathname given in *_path is relative, then it's interpreted relative to the directory that the corresponding file descriptor *_dirfd refers to β€” not relative to the current working directory of the calling process, which is what open(2) would do for a relative pathname. in this case the corresponding *_dirfd must be a directory that was opened for reading (O_RDONLY) or opened using the O_PATH flag.
  • If *_path is an empty string, and flags contains the appropriate MOVE_MOUNT_*_EMPTY_PATH flag, then the corresponding file descriptor *_dirfd is operated on directly. and in this case, the corresponding *_dirfd may refer to any type of file β€” not just a directory. wild, right? πŸ¦‹

if you're sitting there like "why are *_dirfd arguments even useful", go read openat(2), it explains the whole thing. 🫢


πŸ’… the flags menu πŸ’…

flags controls aspects of the path lookup for both the source and the destination objects, plus some other properties of the whole mount operation. you build a value for flags by bitwise-ORing zero or more of these constants together:

  • MOVE_MOUNT_F_EMPTY_PATH β€” if from_path is an empty string, operate on the file referred to by from_dirfd (which may have come from open(2), fsmount(2), or open_tree(2)). in this case from_dirfd may refer to any type of file, not just a directory. and if from_dirfd is AT_FDCWD, then move_mount() will operate on the current working directory of the calling process. this is the most common mechanism used to attach detached mount objects (the ones fsmount(2) and open_tree(2) produce) onto a mount point. this is the main character flag fr fr. πŸ‘‘
  • MOVE_MOUNT_T_EMPTY_PATH β€” same energy as MOVE_MOUNT_F_EMPTY_PATH, except it operates on to_dirfd and to_path instead.
  • MOVE_MOUNT_F_SYMLINKS β€” if from_path references a symbolic link, then dereference it. the default behavior for move_mount() is to not follow symbolic links. so this flag is you opting in.
  • MOVE_MOUNT_T_SYMLINKS β€” same as MOVE_MOUNT_F_SYMLINKS, except it operates on to_dirfd and to_path.
  • MOVE_MOUNT_F_NO_AUTOMOUNT β€” don't automount the terminal ("basename") component of from_path if it's a directory that happens to be an automount point. this lets a mount object that has an automount point at its root actually get moved, and it prevents unintended triggering of an automount point. note: this flag has no effect if the automount point has already been mounted over.
  • MOVE_MOUNT_T_NO_AUTOMOUNT β€” same as MOVE_MOUNT_F_NO_AUTOMOUNT, except it operates on to_dirfd and to_path. this one lets an automount point be manually mounted over.
  • MOVE_MOUNT_SET_GROUP (since Linux 5.15) β€” this takes the attached private-propagation mount object indicated by to_dirfd and to_path and adds it into the mount propagation "peer group" of the attached non-private-propagation mount object indicated by from_dirfd and from_path.

unlike every other move_mount() operation, this one does not move or attach any mount objects. instead it only updates the metadata of attached mount objects. so if you thought this was a move, it is not a move. πŸ™ˆ

⚠️ bestie, take careful note of the argument order here β€” the mount object being modified by this operation is the one specified by to_dirfd and to_path. yes, the destination. not the source. do not get this backwards, i'm begging. 😭

what's it even for? it makes it possible to first create a mount tree made up of only private mounts, and then configure the propagation layout you actually want afterwards. (the "SHARED SUBTREES" section of mount_namespaces(7) has more on mount propagation and peer groups, if you wanna go deeper. iykyk.)

  • MOVE_MOUNT_BENEATH (since Linux 6.5) β€” okay icon behavior incoming ✨. if the path indicated by to_dirfd and to_path is an existing mount object, then instead of attaching or moving the mount object indicated by from_dirfd and from_path on top of the mount stack, you attach or move it beneath the current top mount on the mount stack. under it. underneath. 🫢

why would you do that? because after using MOVE_MOUNT_BENEATH, you can umount(2) the top mount and reveal the mount object that you attached beneath it earlier. this lets you do a seamless (and atomic) replacement of intricate mount trees, which you can then use to "upgrade" a mount tree with a newer version. it's giving glow-up. πŸ’…

but this operation has several restrictions, and they are not optional, so listen up:

  • Mount objects cannot be attached beneath the filesystem root, including cases where the filesystem root was configured by chroot(2) or pivot_root(2). if you want to mount beneath the filesystem root, you have to use pivot_root(2). periodt.
  • The target path indicated by to_dirfd and to_path must not be a detached mount object β€” like the ones produced by open_tree(2) with OPEN_TREE_CLONE or by fsmount(2).
  • The current top mount of the target path's mount stack and its parent mount must be in the calling process's mount namespace.
  • The caller must have sufficient privileges to unmount the top mount of the target path's mount stack β€” this is the proof that they have privileges to reveal the underlying mount.
  • Mount propagation events triggered by this move_mount() operation (as described in mount_namespaces(7)) are calculated based on the parent mount of the current top mount of the target path's mount stack.
  • The target path's mount cannot be an ancestor in the mount tree of the source mount object.
  • The source mount object must not have any overmounts β€” otherwise it would be possible to create "shadow mounts" (that's two mounts mounted on the same parent mount at the same mount point, which is not the vibe).
  • It is not possible to move a mount beneath a top mount if the parent mount of the current top mount propagates to the top mount itself. otherwise MOVE_MOUNT_BENEATH would cause the mount object to be propagated to the top mount from the parent mount, defeating the purpose of using MOVE_MOUNT_BENEATH in the first place. 😭
  • It is not possible to move a mount beneath a top mount if the parent mount of the current top mount propagates to the mount object being mounted beneath. otherwise you'd get a similar propagation issue to the previous point, which also defeats the purpose of using MOVE_MOUNT_BENEATH. same energy, same problem. πŸ’€

one last lil detail before we move on: if from_dirfd is a mount object file descriptor and move_mount() is operating on it directly, then from_dirfd will remain associated with the mount object after move_mount() succeeds. that means you can reuse from_dirfd with move_mount(2) and/or *at() system calls as many times as you need. it's reusable, bestie. sustainable queen. 🌈

🫢 RETURN VALUE 🫢

on success, move_mount() returns 0. on error, it returns -1, and errno is set to indicate the error. the classic. 🌸


πŸ’€ ERRORS πŸ’€

bestie. when move_mount() is not in the mood, it tells you exactly why and sets errno to one of these. read them like a group chat β€” every single one is a different flavour of "no":

  • EACCES β€” search permission said βœ‹ for one of the directories in the path prefix of one of from_path or to_path. (also go peek at path_resolution(7) if you want the lore 🫢)
  • EBADF β€” one of from_dirfd or to_dirfd is not a valid file descriptor. it's giving fake ID 😭
  • EFAULT β€” one of from_path or to_path is NULL, or it's a pointer pointing somewhere outside the calling process's accessible address space. that address does not exist in this economy πŸ’€
  • EINVAL β€” invalid flag specified in flags. nope, not a real one, we don't do that here.
  • EINVAL β€” the path indicated by from_dirfd and from_path is not a mount object. you pointed at something that isn't a mount, so like... what do you want from me πŸ™ˆ
  • EINVAL β€” the mount object type of the source mount object and the target inode are not compatible (i.e., the source is a file but the target is a directory, or vice-versa). mismatched, incompatible, not a match, it's not going to work out bestie 😭
  • EINVAL β€” the source mount object or the target path are not in the calling process's mount namespace (or an anonymous mount namespace of the calling process). different namespace, different world ✨
  • EINVAL β€” the source mount object's parent mount has shared mount propagation, and so it cannot be moved (as described in mount_namespaces(7)). it's too shared to be moved, no cap.
  • EINVAL β€” the source mount has MS_UNBINDABLE child mounts but the target path resides on a mount tree with shared mount propagation, which would otherwise cause the unbindable mounts to be propagated (as described in mount_namespaces(7)). the unbindable ones would have leaked out and propagated, and we are NOT doing that πŸ”₯
  • EINVAL β€” MOVE_MOUNT_BENEATH was attempted, but one of the listed restrictions was violated. read the rules again, bestie, the restrictions are right there πŸ‘‘
  • ELOOP β€” too many symbolic links encountered while resolving one of from_path or to_path. the symlinks are linking each other in a circle forever ngl πŸ”„
  • ENAMETOOLONG β€” one of from_path or to_path is longer than PATH_MAX. too much name. overflowing. periodt.
  • ENOENT β€” a component of one of from_path or to_path does not exist. it's not there. it never was. sorry. πŸ’€
  • ENOENT β€” one of from_path or to_path is an empty string, but the corresponding MOVE_MOUNT_*_EMPTY_PATH flag is not specified in flags. empty string energy without the matching flag? not allowed, you gotta declare it.
  • ENOTDIR β€” a component of the path prefix of one of from_path or to_path is not a directory, or one of from_path or to_path is relative and the corresponding from_dirfd or to_dirfd is a file descriptor referring to a file other than a directory. you said directory, the filesystem said "actually no, that's a file" πŸ™ˆ
  • ENOMEM β€” the kernel could not allocate sufficient memory to complete the operation. out of RAM, out of vibes 😭
  • EPERM β€” the calling process does not have the required CAP_SYS_ADMIN capability. no capability, no mount moves. that's the whole thing βœ‹

🌈 STANDARDS 🌈

Linux.

that's it. that's the whole entry. iconic, honestly. πŸ’…


🧚 HISTORY 🧚

Linux 5.2, glibc 2.36. so it's been around, it's grown up, it's established, it's serving 🌸


✨ EXAMPLES ✨

okay this is the fun part, real code, real demos, let me show you how she works πŸ’–

moving an attached mount πŸ¦‹

you can use move_mount() to move attached mounts, like this:

move_mount(AT_FDCWD, "/a", AT_FDCWD, "/b", 0);

this would move the mount object mounted on /a to /b. and the way that the above procedure is functionally equivalent to the following mount operation using mount(2)?? obsessed:

mount("/a", "/b", NULL, MS_MOVE, NULL);

with file descriptors from open_tree(2) or open(2) πŸ”₯

move_mount() can also be used in conjunction with file descriptors returned from open_tree(2) or open(2):

int fd = open_tree(AT_FDCWD, "/mnt", 0); /* open("/mnt", O_PATH); */
move_mount(fd, "", AT_FDCWD, "/mnt2", MOVE_MOUNT_F_EMPTY_PATH);
move_mount(fd, "", AT_FDCWD, "/mnt3", MOVE_MOUNT_F_EMPTY_PATH);
move_mount(fd, "", AT_FDCWD, "/mnt4", MOVE_MOUNT_F_EMPTY_PATH);

this would move the mount object mounted at /mnt to /mnt2, then /mnt3, and then /mnt4. one fd, three new homes, main character energy πŸ‘‘

attaching a detached mount object πŸŽ€

if the source mount object indicated by from_dirfd and from_path is a detached mount object, move_mount() can be used to attach it to a mount point:

int fsfd, mntfd;

fsfd = fsopen("ext4", FSOPEN_CLOEXEC);
fsconfig(fsfd, FSCONFIG_SET_STRING, "source", "/dev/sda1", 0);
fsconfig(fsfd, FSCONFIG_SET_FLAG, "user_xattr", NULL, 0);
fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0);
mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, MOUNT_ATTR_NODEV);
move_mount(mntfd, "", AT_FDCWD, "/home", MOVE_MOUNT_F_EMPTY_PATH);

this would create a new filesystem configuration context for ext4, configure it, create a detached mount object, and then attach it to /home. and the above procedure is functionally equivalent to the following mount operation using mount(2):

mount("/dev/sda1", "/home", "ext4", MS_NODEV, "user_xattr");

literally the same thing, different outfit πŸ’…

detached bind-mounts πŸ“

the same operation also works with detached bind-mounts created with open_tree(2) with OPEN_TREE_CLONE:

int mntfd = open_tree(AT_FDCWD, "/home/cyphar", OPEN_TREE_CLONE);
move_mount(mntfd, "", AT_FDCWD, "/root", MOVE_MOUNT_F_EMPTY_PATH);

this would create a new bind-mount of /home/cyphar as a detached mount object, and then attach it to /root. and yes, the above procedure is functionally equivalent to the following mount operation using mount(2):

mount("/home/cyphar", "/root", NULL, MS_BIND, NULL);

slay. same result, cleaner route ✨


πŸ‘‘ SEE ALSO πŸ‘‘

go read these next, they're the friend group:

fsconfig(2), fsmount(2), fsopen(2), fspick(2), mount(2), mount_setattr(2), open_tree(2), mount_namespaces(7)


and that's the whole page bestie!! 🫢 this one comes from Linux man-pages 6.20, dated 2026-02-11, and it's move_mount(2) β€” go move some mounts, you've got this πŸ’–πŸŒΈπŸ’…

πŸ“¦ where this page comes from
  • arch rolling: man-pages 6.20-1 Β· BSD-2-Clause AND BSD-3-Clause AND BSD-4.3TAHOE AND BSD-4-Clause-UC AND GPL-1.0-or-later AND GPL-2.0-only AND GPL-2.0-or-later AND GPL-3.0-or-later AND LGPL-3.0-linking-exception AND LGPL-3.0-or-later AND LicenseRef-Public-Domain AND LicenseRef-UltraPermissive AND Linux-man-pages-1-para AND Linux-man-pages-copyleft AND Linux-man-pages-copyleft-2-para AND Linux-man-pages-copyleft-var AND MIT AND Spencer-94
  • nixpkgs unstable: man-pages 6.20
  • πŸ“œ the page's license: Linux-man-pages-copyleft
  • πŸ’– girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms