(root)/
util-linux-2.39/
libmount/
src/
hook_subdir.c
       1  /* SPDX-License-Identifier: LGPL-2.1-or-later */
       2  /*
       3   * This file is part of libmount from util-linux project.
       4   *
       5   * Copyright (C) 2022 Karel Zak <kzak@redhat.com>
       6   *
       7   * libmount is free software; you can redistribute it and/or modify it
       8   * under the terms of the GNU Lesser General Public License as published by
       9   * the Free Software Foundation; either version 2.1 of the License, or
      10   * (at your option) any later version.
      11   *
      12   *
      13   * This is X-mount.subdir= implementation. The code uses global hookset data
      14   * rather than per-callback (hook) data.
      15   *
      16   * Please, see the comment in libmount/src/hooks.c to understand how hooks work.
      17   */
      18  #include <sched.h>
      19  
      20  #include "mountP.h"
      21  #include "fileutils.h"
      22  #include "mount-api-utils.h"
      23  
      24  struct hookset_data {
      25  	char *subdir;
      26  	char *org_target;
      27  	int old_ns_fd;
      28  	int new_ns_fd;
      29  	unsigned int tmp_umounted : 1;
      30  };
      31  
      32  static int tmptgt_cleanup(struct hookset_data *);
      33  
      34  static void free_hookset_data(	struct libmnt_context *cxt,
      35  				const struct libmnt_hookset *hs)
      36  {
      37  	struct hookset_data *hsd = mnt_context_get_hookset_data(cxt, hs);
      38  
      39  	if (!hsd)
      40  		return;
      41  	if (hsd->old_ns_fd >= 0)
      42  		tmptgt_cleanup(hsd);
      43  
      44  	free(hsd->org_target);
      45  	free(hsd->subdir);
      46  	free(hsd);
      47  
      48  	mnt_context_set_hookset_data(cxt, hs, NULL);
      49  }
      50  
      51  /* global data, used by all callbacks */
      52  static struct hookset_data *new_hookset_data(
      53  				struct libmnt_context *cxt,
      54  				const struct libmnt_hookset *hs)
      55  {
      56  	struct hookset_data *hsd = calloc(1, sizeof(struct hookset_data));
      57  
      58  	if (hsd && mnt_context_set_hookset_data(cxt, hs, hsd) != 0) {
      59  		/* probably ENOMEM problem */
      60  		free(hsd);
      61  		hsd = NULL;
      62  	}
      63  	return hsd;
      64  }
      65  
      66  /* de-initiallize this module */
      67  static int hookset_deinit(struct libmnt_context *cxt, const struct libmnt_hookset *hs)
      68  {
      69  	DBG(HOOK, ul_debugobj(hs, "deinit '%s'", hs->name));
      70  
      71  	/* remove all our hooks */
      72  	while (mnt_context_remove_hook(cxt, hs, 0, NULL) == 0);
      73  
      74  	/* free and remove global hookset data */
      75  	free_hookset_data(cxt, hs);
      76  
      77  	return 0;
      78  }
      79  
      80  /*
      81   * Initialize MNT_PATH_TMPTGT; mkdir, create a new namespace and
      82   * mark (bind mount) the directory as private.
      83   */
      84  static int tmptgt_unshare(struct hookset_data *hsd)
      85  {
      86  #ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
      87  	int rc = 0;
      88  
      89  	hsd->old_ns_fd = hsd->new_ns_fd = -1;
      90  
      91  	/* create directory */
      92  	rc = ul_mkdir_p(MNT_PATH_TMPTGT, S_IRWXU);
      93  	if (rc)
      94  		goto fail;
      95  
      96  	/* remember the current namespace */
      97  	hsd->old_ns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
      98  	if (hsd->old_ns_fd < 0)
      99  		goto fail;
     100  
     101  	/* create new namespace */
     102  	if (unshare(CLONE_NEWNS) != 0)
     103  		goto fail;
     104  
     105  	/* try to set top-level directory as private, this is possible if
     106  	 * MNT_RUNTIME_TOPDIR (/run) is a separated filesystem. */
     107  	if (mount("none", MNT_RUNTIME_TOPDIR, NULL, MS_PRIVATE, NULL) != 0) {
     108  
     109  		/* failed; create a mountpoint from MNT_PATH_TMPTGT */
     110  		if (mount(MNT_PATH_TMPTGT, MNT_PATH_TMPTGT, "none", MS_BIND, NULL) != 0)
     111  			goto fail;
     112  		if (mount("none", MNT_PATH_TMPTGT, NULL, MS_PRIVATE, NULL) != 0)
     113  			goto fail;
     114  	}
     115  
     116  	/* remember the new namespace */
     117  	hsd->new_ns_fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
     118  	if (hsd->new_ns_fd < 0)
     119  		goto fail;
     120  
     121  	DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshared"));
     122  	return 0;
     123  fail:
     124  	if (rc == 0)
     125  		rc = errno ? -errno : -EINVAL;
     126  
     127  	tmptgt_cleanup(hsd);
     128  	DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " unshare failed"));
     129  	return rc;
     130  #else
     131  	return -ENOSYS;
     132  #endif
     133  }
     134  
     135  /*
     136   * Clean up MNT_PATH_TMPTGT; umount and switch back to old namespace
     137   */
     138  static int tmptgt_cleanup(struct hookset_data *hsd)
     139  {
     140  #ifdef USE_LIBMOUNT_SUPPORT_NAMESPACES
     141  	if (!hsd->tmp_umounted) {
     142  		umount(MNT_PATH_TMPTGT);
     143  		hsd->tmp_umounted = 1;
     144  	}
     145  
     146  	if (hsd->new_ns_fd >= 0)
     147  		close(hsd->new_ns_fd);
     148  
     149  	if (hsd->old_ns_fd >= 0) {
     150  		setns(hsd->old_ns_fd, CLONE_NEWNS);
     151  		close(hsd->old_ns_fd);
     152  	}
     153  
     154  	hsd->new_ns_fd = hsd->old_ns_fd = -1;
     155  	DBG(UTILS, ul_debug(MNT_PATH_TMPTGT " cleanup done"));
     156  	return 0;
     157  #else
     158  	return -ENOSYS;
     159  #endif
     160  }
     161  
     162  /*
     163   * Attach (move) MNT_PATH_TMPTGT/subdir to the parental namespace.
     164   */
     165  static int do_mount_subdir(
     166  			struct libmnt_context *cxt,
     167  			struct hookset_data *hsd,
     168  			const char *root,
     169  			const char *target)
     170  {
     171  	int rc = 0;
     172  	const char *subdir = hsd->subdir;
     173  
     174  #ifdef USE_LIBMOUNT_MOUNTFD_SUPPORT
     175  	struct libmnt_sysapi *api;
     176  
     177  	api = mnt_context_get_sysapi(cxt);
     178  	if (api) {
     179  		/* FD based way - unfortunately, it's impossible to open
     180  		 * sub-directory on not-yet attached mount. It means
     181  		 * hook_mount.c attaches FS to temporary directory, and we
     182  		 * clone and move the subdir, and umount the old unshared
     183  		 * temporary tree.
     184  		 *
     185  		 * The old mount(2) way does the same, but by BIND.
     186  		 */
     187  		int fd;
     188  
     189  		DBG(HOOK, ul_debug("attach subdir  %s", subdir));
     190  		fd = open_tree(api->fd_tree, subdir,
     191  					OPEN_TREE_CLOEXEC | OPEN_TREE_CLONE);
     192  		set_syscall_status(cxt, "open_tree", fd >= 0);
     193  		if (fd < 0)
     194  			rc = -errno;
     195  
     196  		if (!rc) {
     197  			/* Note that the original parental namespace could be
     198  			 * private, in this case, it will not see our final mount,
     199  			 * so we need to move the the orignal namespace.
     200  			 */
     201  			setns(hsd->old_ns_fd, CLONE_NEWNS);
     202  
     203  			rc = move_mount(fd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH);
     204  			set_syscall_status(cxt, "move_mount", rc == 0);
     205  			if (rc)
     206  				rc = -errno;
     207  
     208  			/* And move back to our private namespace to cleanup */
     209  			setns(hsd->new_ns_fd, CLONE_NEWNS);
     210  		}
     211  		if (!rc) {
     212  			close(api->fd_tree);
     213  			api->fd_tree = fd;
     214  		}
     215  	} else
     216  #endif
     217  	{
     218  		char *src = NULL;
     219  
     220  		if (asprintf(&src, "%s/%s", root, subdir) < 0)
     221  			return -ENOMEM;
     222  
     223  		/* Classic mount(2) based way */
     224  		DBG(HOOK, ul_debug("mount subdir %s to %s", src, target));
     225  		rc = mount(src, target, NULL, MS_BIND, NULL);
     226  
     227  		set_syscall_status(cxt, "mount", rc == 0);
     228  		if (rc)
     229  			rc = -errno;
     230  		free(src);
     231  	}
     232  
     233  	if (!rc) {
     234  		DBG(HOOK, ul_debug("umount old root %s", root));
     235  		rc = umount(root);
     236  		set_syscall_status(cxt, "umount", rc == 0);
     237  		if (rc)
     238  			rc = -errno;
     239  		hsd->tmp_umounted = 1;
     240  
     241  	}
     242  
     243  	return rc;
     244  }
     245  
     246  
     247  static int hook_mount_post(
     248  			struct libmnt_context *cxt,
     249  			const struct libmnt_hookset *hs,
     250  			void *data __attribute__((__unused__)))
     251  {
     252  	struct hookset_data *hsd;
     253  	int rc = 0;
     254  
     255  	hsd = mnt_context_get_hookset_data(cxt, hs);
     256  	if (!hsd || !hsd->subdir)
     257  		return 0;
     258  
     259  	/* reset to the original mountpoint */
     260  	mnt_fs_set_target(cxt->fs, hsd->org_target);
     261  
     262  	/* bind subdir to the real target, umount temporary target */
     263  	rc = do_mount_subdir(cxt, hsd,
     264  			MNT_PATH_TMPTGT,
     265  			mnt_fs_get_target(cxt->fs));
     266  	if (rc)
     267  		return rc;
     268  
     269  	tmptgt_cleanup(hsd);
     270  
     271  	return rc;
     272  }
     273  
     274  static int hook_mount_pre(
     275  			struct libmnt_context *cxt,
     276  			const struct libmnt_hookset *hs,
     277  			void *data __attribute__((__unused__)))
     278  {
     279  	struct hookset_data *hsd;
     280  	int rc = 0;
     281  
     282  	hsd = mnt_context_get_hookset_data(cxt, hs);
     283  	if (!hsd)
     284  		return 0;
     285  
     286  	/* create unhared temporary target */
     287  	hsd->org_target = strdup(mnt_fs_get_target(cxt->fs));
     288  	if (!hsd->org_target)
     289  		rc = -ENOMEM;
     290  	if (!rc)
     291  		rc = tmptgt_unshare(hsd);
     292  	if (!rc)
     293  		mnt_fs_set_target(cxt->fs, MNT_PATH_TMPTGT);
     294  	if (!rc)
     295  		rc = mnt_context_append_hook(cxt, hs,
     296  				MNT_STAGE_MOUNT_POST,
     297  				NULL, hook_mount_post);
     298  
     299  	DBG(HOOK, ul_debugobj(hs, "unshared tmp target %s [rc=%d]",
     300  				MNT_PATH_TMPTGT, rc));
     301  	return rc;
     302  }
     303  
     304  
     305  
     306  static int is_subdir_required(struct libmnt_context *cxt, int *rc, char **subdir)
     307  {
     308  	struct libmnt_optlist *ol;
     309  	struct libmnt_opt *opt;
     310  	const char *dir = NULL;
     311  
     312  	assert(cxt);
     313  	assert(rc);
     314  
     315  	*rc = 0;
     316  
     317  	ol = mnt_context_get_optlist(cxt);
     318  	if (!ol)
     319  		return -ENOMEM;
     320  
     321  	opt = mnt_optlist_get_named(ol, "X-mount.subdir", cxt->map_userspace);
     322  	if (!opt)
     323  		return 0;
     324  
     325  	dir = mnt_opt_get_value(opt);
     326  
     327  	if (dir && *dir == '"')
     328  		dir++;
     329  
     330  	if (!dir || !*dir) {
     331  		DBG(HOOK, ul_debug("failed to parse X-mount.subdir '%s'", dir));
     332  		*rc = -MNT_ERR_MOUNTOPT;
     333  	} else {
     334  		*subdir = strdup(dir);
     335  		if (!*subdir)
     336  			*rc = -ENOMEM;
     337  	}
     338  
     339  	return *rc == 0;
     340  }
     341  
     342  /* this is the initial callback used to check mount options and define next
     343   * actions if necessary */
     344  static int hook_prepare_target(
     345  			struct libmnt_context *cxt,
     346  			const struct libmnt_hookset *hs,
     347  			void *data __attribute__((__unused__)))
     348  {
     349  	const char *tgt;
     350  	char *subdir = NULL;
     351  	int rc = 0;
     352  
     353  	assert(cxt);
     354  
     355  	tgt = mnt_fs_get_target(cxt->fs);
     356  	if (!tgt)
     357  		return 0;
     358  
     359  	if (cxt->action == MNT_ACT_MOUNT
     360  	    && is_subdir_required(cxt, &rc, &subdir)) {
     361  
     362  		/* create a global data */
     363  		struct hookset_data *hsd = new_hookset_data(cxt, hs);
     364  
     365  		if (!hsd) {
     366  			free(subdir);
     367  			return -ENOMEM;
     368  		}
     369  		hsd->subdir = subdir;
     370  
     371  		DBG(HOOK, ul_debugobj(hs, "subdir %s wanted", subdir));
     372  
     373  		rc = mnt_context_append_hook(cxt, hs,
     374  				MNT_STAGE_MOUNT_PRE,
     375  				NULL, hook_mount_pre);
     376  	}
     377  
     378  	return rc;
     379  }
     380  
     381  const struct libmnt_hookset hookset_subdir =
     382  {
     383  	.name = "__subdir",
     384  
     385  	.firststage = MNT_STAGE_PREP_TARGET,
     386  	.firstcall = hook_prepare_target,
     387  
     388  	.deinit = hookset_deinit
     389  };