[CRIU] [PATCH 2/2] autofs: support of "hidden" catatonic mounts

Andrew Vagin avagin at virtuozzo.com
Fri May 13 14:16:07 PDT 2016


On Thu, May 05, 2016 at 07:48:14PM +0300, Stanislav Kinsburskiy wrote:
> There can be a situation, when autofs mount is catatonic, but doesn't know
> about it yet.
> It wasn't possible to distinguish between such mounts and others, which are
> active, but not accessible for some reason. And all of such mounts were
> threated as undumpable.
> Such "hidden" catatonic mounts can appear if:
> 
> 1) autofs master process has exited without switching
>    the mount to catatonic mode (or was killed).
> 
> 2) mount point was unmounted, but not propagated to
>    nested mount namespace with private mounts.
> 
> These mounts can be handled by accessing the mount point. If it's catatonic,
> it will update it's internals on first failed request to the autofs master.
> Then we can read options again and dump the mountpoint as catatonic.
> 
> Signed-off-by: Stanislav Kinsburskiy <skinsbursky at virtuozzo.com>
> ---
>  criu/autofs.c |  171 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 170 insertions(+), 1 deletion(-)
> 
> diff --git a/criu/autofs.c b/criu/autofs.c
> index 09c057c..c540d1b 100644
> --- a/criu/autofs.c
> +++ b/criu/autofs.c
> @@ -2,6 +2,7 @@
>  #include <sys/stat.h>
>  #include <stdarg.h>
>  #include <sys/mount.h>
> +#include <sys/wait.h>
>  
>  #include "proc_parse.h"
>  #include "autofs.h"
> @@ -23,6 +24,8 @@
>  
>  #define AUTOFS_CATATONIC_FD	-1
>  
> +static int autofs_mnt_open(const char *mnt_path, dev_t devid);
> +
>  struct autofs_pipe_s {
>  	struct list_head list;
>  	unsigned long inode;
> @@ -279,6 +282,144 @@ static int parse_options(char *options, AutofsEntry *entry, long *pipe_ino)
>  	return 0;
>  }
>  
> +static int autofs_revisit_options(struct mount_info *pm)
> +{
> +	FILE *f;
> +	char *str;
> +	int ret = -ENOMEM;
> +
> +	str = malloc(1024);
> +	if (!str) {
> +		pr_err("failed to allocate\n");
> +		return -ENOMEM;
> +	}
> +
> +	f = fopen_proc(getpid(), "mountinfo");
> +	if (!f) {
> +		pr_perror("Can't open %d mountinfo", getpid());
> +		goto free_str;
> +	}
> +
> +	while (fgets(str, 1024, f)) {
> +		int mnt_id = -1;
> +		char *token;
> +
> +		/* Removing '/n' */
> +		str[strlen(str)-1] = '\0';
> +
> +		while ((token = strsep(&str, " ")) != NULL) {
> +			if (mnt_id == -1) {
> +				mnt_id = atoi(token);
> +				if (mnt_id != pm->mnt_id)
> +					break;
> +			} else if (strstr(token, "pipe_ino=")) {
> +				ret = 0;
> +				free(pm->options);
> +
> +				pm->options = xstrdup(token);
> +				if (!pm->options)
> +					pr_err("failed to duplicate string\n");
> +				else
> +					ret = 0;
> +				goto close_proc;
> +			}
> +		}
> +	}
> +
> +	pr_err("failed to find autofs mount with mnt_id %d\n", pm->mnt_id);
> +	ret = -ENOENT;
> +
> +close_proc:
> +	fclose(f);
> +free_str:
> +	free(str);
> +	return ret;
> +}
> +
> +/*
> + * To access the mount point we have to set proper mount namespace.
> + * But, unfortunatelly, we have to set proper pid namespace as well,
> + * because otherwise autofs driver won't find the autofs master.
> + */
> +static int access_autofs_mount(struct mount_info *pm)
> +{
> +	const char *mnt_path = pm->mountpoint + 1;
> +	dev_t dev_id = pm->s_dev;
> +	int new_pid_ns = -1, old_pid_ns = -1;
> +	int old_mnt_ns;
> +	int autofs_mnt;
> +	int err = -1;
> +	int pid, status;
> +
> +	/*
> +	 * To be able to set proper pid namespace, we must open fd before
> +	 * switching to the mount namespace.
> +	 * The same applies to pid namespace fd to restore back.

switch_ns uses open_proc(), which uses get_service_fd(PROC_FD_OFF),
so I don't understand why switch_ns() doesn't work for pidns here

And here is an error from coverity:
*** CID 161321:  API usage errors  (USE_AFTER_FREE)
/criu/autofs.c: 419 in access_autofs_mount()
413             }
414     close_old_pid_ns:
415             if (old_pid_ns >= 0)
416                     close(old_pid_ns);
417     close_new_pid_ns:
418             if (new_pid_ns >= 0)
>>>     CID 161321:  API usage errors  (USE_AFTER_FREE)
>>>     Calling "close(int)" closes handle "new_pid_ns" which has
>>>     already been closed.
419                     close(new_pid_ns);
420             return err;
421     }
422
423     static int autofs_create_entry(struct mount_info *pm, AutofsEntry *entry)
424     {

> +	 */
> +	new_pid_ns = open_proc(pm->nsid->ns_pid, "ns/pid");
> +	if (new_pid_ns < 0)
> +		return -1;
> +
> +	old_pid_ns = open("/proc/self/ns/pid", O_RDONLY);
> +	if (old_pid_ns < 0) {
> +		pr_perror("Can't open /proc/self/ns/pid");
> +		goto close_new_pid_ns;
> +	}
> +
> +	if (switch_ns(pm->nsid->ns_pid, &mnt_ns_desc, &old_mnt_ns)) {
> +		pr_err("failed to switch to mount namespace\n");
> +		goto close_old_pid_ns;
> +	}
> +
> +	if (restore_ns(new_pid_ns, &pid_ns_desc)) {
> +		pr_err("failed to restore pid namespace\n");
> +		goto restore_mnt_ns;
> +	}
> +	new_pid_ns = -1;
> +
> +	autofs_mnt = autofs_mnt_open(mnt_path, dev_id);
> +	if (autofs_mnt < 0)
> +		goto restore_pid_ns;
> +
> +	pid = fork();
> +	switch (pid) {
> +		case -1:
> +			pr_err("failed to fork\n");
> +			goto close_autofs_mnt;
> +		case 0:
> +			/* We don't care about results.
> +			 * All we need is to "touch" */
> +			openat(autofs_mnt, mnt_path, O_RDONLY|O_NONBLOCK|O_DIRECTORY);
> +			_exit(0);
> +
> +	}
> +	/* Here we also don't care about results */
> +	waitpid(pid, &status, 0);
> +
> +	err = autofs_revisit_options(pm);
> +
> +close_autofs_mnt:
> +	close(autofs_mnt);
> +restore_pid_ns:
> +	if (restore_ns(old_pid_ns, &pid_ns_desc)) {
> +		pr_err("failed to restore pid namespace\n");
> +		return -1;
> +	}
> +	old_pid_ns = -1;
> +restore_mnt_ns:
> +	if (restore_ns(old_mnt_ns, &mnt_ns_desc)) {
> +		pr_err("failed to restore mount namespace\n");
> +		return -1;
> +	}
> +close_old_pid_ns:
> +	if (old_pid_ns >= 0)
> +		close(old_pid_ns);
> +close_new_pid_ns:
> +	if (new_pid_ns >= 0)
> +		close(new_pid_ns);
> +	return err;
> +}
> +
>  static int autofs_create_entry(struct mount_info *pm, AutofsEntry *entry)
>  {
>  	long pipe_ino;
> @@ -295,8 +436,36 @@ static int autofs_create_entry(struct mount_info *pm, AutofsEntry *entry)
>  		int found, read_fd, virt_pgrp;
>  
>  		read_fd = autofs_find_read_fd(entry->pgrp, pipe_ino);
> -		if (read_fd < 0)
> +		if (read_fd < 0) {
> +			if (read_fd != -ENOENT)
> +				return -1;
> +
> +			/* Ok, our read end doesn't exist.
> +			 * There can be a case, when mount looks normal, but
> +			 * it's a "hidden" or "abandoned" catatonic mount in
> +			 * reality.
> +			 * This can happen if:
> +			 * 1) autofs master process has exited without switching
> +			 * the mount to catatonic mode (or was killed).
> +			 * 2) mount point was unmounted, but not propagated to
> +			 * nested mount namespace with private mounts.
> +			 * We can try handle these cases by accessing the mount
> +			 * point. If it's catatonic, it will update it's
> +			 * options, then we can read them again and dump it.
> +			 */
> +			if (access_autofs_mount(pm)) {
> +				pr_err("failed to access autofs %s\n",
> +						pm->mountpoint + 1);
> +				return -1;
> +			}
> +			if (parse_options(pm->options, entry, &pipe_ino))
> +				return -1;
> +			if (entry->fd == AUTOFS_CATATONIC_FD)
> +				return 0;
> +			pr_err("Autofs %d is alive, but unreachable.\n",
> +					pm->mnt_id);
>  			return -1;
> +		}
>  
>  		/* Let' check whether write end is still open */
>  		found = autofs_kernel_pipe_alive(entry->pgrp, entry->fd, pipe_ino);
> 
> _______________________________________________
> CRIU mailing list
> CRIU at openvz.org
> https://lists.openvz.org/mailman/listinfo/criu


More information about the CRIU mailing list