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)oropen_tree(2)make for you when you passOPEN_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
*_pathis absolute, then the corresponding*_dirfdis ignored. bye, you're not needed. π - If the pathname given in
*_pathis relative and the corresponding*_dirfdis the special valueAT_FDCWD, then*_pathis interpreted relative to the current working directory of the calling process (exactly likeopen(2)does). - If the pathname given in
*_pathis relative, then it's interpreted relative to the directory that the corresponding file descriptor*_dirfdrefers to β not relative to the current working directory of the calling process, which is whatopen(2)would do for a relative pathname. in this case the corresponding*_dirfdmust be a directory that was opened for reading (O_RDONLY) or opened using theO_PATHflag. - If
*_pathis an empty string, andflagscontains the appropriateMOVE_MOUNT_*_EMPTY_PATHflag, then the corresponding file descriptor*_dirfdis operated on directly. and in this case, the corresponding*_dirfdmay 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β iffrom_pathis an empty string, operate on the file referred to byfrom_dirfd(which may have come fromopen(2),fsmount(2), oropen_tree(2)). in this casefrom_dirfdmay refer to any type of file, not just a directory. and iffrom_dirfdisAT_FDCWD, thenmove_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 onesfsmount(2)andopen_tree(2)produce) onto a mount point. this is the main character flag fr fr. πMOVE_MOUNT_T_EMPTY_PATHβ same energy asMOVE_MOUNT_F_EMPTY_PATH, except it operates onto_dirfdandto_pathinstead.MOVE_MOUNT_F_SYMLINKSβ iffrom_pathreferences a symbolic link, then dereference it. the default behavior formove_mount()is to not follow symbolic links. so this flag is you opting in.MOVE_MOUNT_T_SYMLINKSβ same asMOVE_MOUNT_F_SYMLINKS, except it operates onto_dirfdandto_path.MOVE_MOUNT_F_NO_AUTOMOUNTβ don't automount the terminal ("basename") component offrom_pathif 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 asMOVE_MOUNT_F_NO_AUTOMOUNT, except it operates onto_dirfdandto_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 byto_dirfdandto_pathand adds it into the mount propagation "peer group" of the attached non-private-propagation mount object indicated byfrom_dirfdandfrom_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 byto_dirfdandto_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 byto_dirfdandto_pathis an existing mount object, then instead of attaching or moving the mount object indicated byfrom_dirfdandfrom_pathon 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)orpivot_root(2). if you want to mount beneath the filesystem root, you have to usepivot_root(2). periodt. - The target path indicated by
to_dirfdandto_pathmust not be a detached mount object β like the ones produced byopen_tree(2)withOPEN_TREE_CLONEor byfsmount(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 inmount_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_BENEATHwould cause the mount object to be propagated to the top mount from the parent mount, defeating the purpose of usingMOVE_MOUNT_BENEATHin 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 atpath_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 isNULL, 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 inmount_namespaces(7)). it's too shared to be moved, no cap.EINVALβ the source mount hasMS_UNBINDABLEchild 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 inmount_namespaces(7)). the unbindable ones would have leaked out and propagated, and we are NOT doing that π₯EINVALβMOVE_MOUNT_BENEATHwas 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 thanPATH_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 correspondingMOVE_MOUNT_*_EMPTY_PATHflag 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 requiredCAP_SYS_ADMINcapability. 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