Re: [PATCH kvmtool 2/2] Add detach feature

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Fri, 5 Jul 2019 12:04:41 +0100
Dave Martin <Dave.Martin@xxxxxxx> wrote:

Hi Dave,

thanks for having a look!

> On Fri, Jul 05, 2019 at 10:59:14AM +0100, Andre Przywara wrote:
> > At the moment a kvmtool process started on a terminal has no way of
> > detaching from the terminal without killing the guest. Existing
> > workarounds are starting kvmtool in a screen/tmux session or using a
> > pseudoterminal (--tty 0), both of which have to be done upon guest
> > creation.
> > 
> > Introduce a terminal command to create a pseudoterminal during the
> > guest's runtime and redirect the console output to that. This will be
> > triggered by typing the letter 'd' after the kvmtool escape sequence
> > (default Ctrl+a). This also daemonises kvmtool, so gives back the shell
> > prompt, and the user can log out without affecting the guest.
> > 
> > Naively daemonising kvmtool at that point doesn't work, though, as the
> > fork() doesn't inherit the threads, so they keep running in the
> > grandparent process and would be killed by its exit.
> > The trick used here is to do the double fork() already right at the
> > beginning of kvmtool's runtime, before spawning the first thread.
> > We then don't end the parent and grandparent processes yet, instead let
> > them block until the user actually requests the detach.
> > This will let all the threads be created in the grandchild process, but
> > keeps kvmtool still attached to the terminal until the user requests a
> > detach.
> > 
> > Signed-off-by: Andre Przywara <andre.przywara@xxxxxxx>
> > ---
> >  builtin-run.c     |  3 ++
> >  include/kvm/kvm.h |  2 ++
> >  term.c            | 91 +++++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 96 insertions(+)
> > 
> > diff --git a/builtin-run.c b/builtin-run.c
> > index f8dc6c72..fa391419 100644
> > --- a/builtin-run.c
> > +++ b/builtin-run.c
> > @@ -592,6 +592,9 @@ static struct kvm *kvm_cmd_run_init(int argc, const char **argv)
> >  		}
> >  	}
> >  
> > +	/* Fork twice already now to create the threads in the right process. */
> > +	kvm__prepare_daemonize();
> > +
> >  	if (!kvm->cfg.guest_name) {
> >  		if (kvm->cfg.custom_rootfs) {
> >  			kvm->cfg.guest_name = kvm->cfg.custom_rootfs_name;
> > diff --git a/include/kvm/kvm.h b/include/kvm/kvm.h
> > index 7a738183..801f9474 100644
> > --- a/include/kvm/kvm.h
> > +++ b/include/kvm/kvm.h
> > @@ -90,6 +90,8 @@ struct kvm {
> >  void kvm__set_dir(const char *fmt, ...);
> >  const char *kvm__get_dir(void);
> >  
> > +int kvm__prepare_daemonize(void);
> > +
> >  int kvm__init(struct kvm *kvm);
> >  struct kvm *kvm__new(void);
> >  int kvm__recommended_cpus(struct kvm *kvm);
> > diff --git a/term.c b/term.c
> > index 7fbd98c6..df8328f8 100644
> > --- a/term.c
> > +++ b/term.c
> > @@ -22,6 +22,7 @@ static struct termios	orig_term;
> >  
> >  static int term_fds[TERM_MAX_DEVS][2];
> >  
> > +static int daemon_fd;
> >  static int inotify_fd;
> >  
> >  static pthread_t term_poll_thread;
> > @@ -29,6 +30,92 @@ static pthread_t term_poll_thread;
> >  /* ctrl-a is used for escape */
> >  #define term_escape_char	0x01
> >  
> > +/* This needs to be called *before* we create any threads. */
> > +int kvm__prepare_daemonize(void)
> > +{
> > +	pid_t pid;
> > +	char dummy;
> > +	int child_pipe[2], parent_pipe[2];
> > +
> > +	if (pipe(parent_pipe))
> > +		return -1;
> > +
> > +	pid = fork();
> > +	if (pid < 0)
> > +		return pid;
> > +	if (pid > 0) {			/* parent process */
> > +
> > +		close(parent_pipe[1]);
> > +
> > +		/* Block until we are told to exit. */
> > +		if (read(parent_pipe[0], &dummy, 1) != 1)
> > +			perror("reading exit pipe");
> > +
> > +		exit(0);  
> 
> Can we have the child write some status value instead?
> 
> Right now, if the child goes wrong after forking we will just exit with
> status 0 regardless.

Sure, but what would we do in this case and why would we care? The child
is just a "trick", so if this somehow fails, it does early and we are
screwed anyways.

> Instead, we could have the child send us the exit status... or if
> the child disappears we'll just get EOF and can return failure
> appropriately.
> 
> Also, how does the invoker kill the guest now that kvmtool has exited?

Either by using "lkvm stop -n ..." or by exiting from within the guest.
This would be the same as one would create a guest with --tty and put that
in the background right now.

> > +	}
> > +
> > +	close(parent_pipe[0]);
> > +	if (pipe(child_pipe))
> > +		return -1;
> > +	daemon_fd = child_pipe[1];
> > +
> > +	/* Become a session leader. */
> > +	setsid();
> > +	pid = fork();
> > +	if (pid > 0) {
> > +		close(child_pipe[1]);
> > +
> > +		/* Block until we are told to exit. */
> > +		if (read(child_pipe[0], &dummy, 1) != 1)
> > +			perror("reading child exit pipe");
> > +
> > +		if (write(parent_pipe[1], &dummy, 1) != 1)
> > +			pr_warning("could not kill daemon's parent");
> > +
> > +		exit(0);
> > +	}
> > +	close(child_pipe[0]);
> > +	close(parent_pipe[1]);  
> 
> Why do we need to fork twice?  The extra process seems redundant.

This is the ~50 year old textbook method of creating a UNIX daemon: By
forking twice and killing the child (the "middle" process), the grandchild
becomes an orphan, and will be picked up by PID 1 (init). This prevents
any interactions with controlling terminals in the future. Dr. Google or a
local library should have more information.

> > +
> > +	/* Only the grandchild returns here, to do the actual work. */
> > +	return 0;
> > +}
> > +
> > +static void term_set_tty(int term);
> > +
> > +static void detach_terminal(int term)
> > +{
> > +	char dummy = 0;
> > +
> > +	/* Detaching only make sense if we use the process' terminal. */
> > +	if (term_fds[term][TERM_FD_IN] != STDIN_FILENO)
> > +		return;
> > +
> > +	/* Clean up just this terminal, leave the others alone. */
> > +	tcsetattr(term_fds[term][TERM_FD_IN], TCSANOW, &orig_term);
> > +
> > +	/* Redirect this terminal to a PTY */
> > +	term_set_tty(term);
> > +
> > +	/*
> > +	 * Replace STDIN/STDOUT with this new PTY. This will automatically
> > +	 * transfer all the other serial terminals over.
> > +	 */
> > +	dup2(term_fds[term][TERM_FD_IN], STDIN_FILENO);
> > +	dup2(term_fds[term][TERM_FD_OUT], STDOUT_FILENO);  
> 
> Output to a pty master before the slave is opened just disappears.

Not always, it seems. When I do:
$ lkvm run -k Image --tty 0
then wait for a bit and open the pseudo-terminal, I get the full boot log
"replayed". Not sure who in the chain is actually buffering this, though?

> Possibly it would be useful to buffer it somewhere.

Sounds useful, but it's more a story for another time, I guess, since we
have the same issue with --tty already.
One related problem is the gap between starting/detaching a guest and
connecting to the terminal for the first time, as one cannot do this fast
and reliable enough without loosing characters, I guess. Maybe it's worth
to introduce something like "start as paused", so we can connect to the
terminal and then do a "lkvm resume".

> I wonder whether it would make sense to redirect to a notional dummy
> terminal (which might or might not buffer output), and start talking to
> a pty or socket only once opened by a client.

Mmmh, sounds like that would fit in nicely since with patch 1/2 we now
know about connects and possibly even disconnects.

Cheers,
Andre.

> > +
> > +	/* Tell the (waiting) child process to exit now. */
> > +	if (write(daemon_fd, &dummy, 1) != 1)
> > +		pr_warning("could not kill daemon's parent");
> > +
> > +	/* To not hog the current directory unnecessarily. */
> > +	if (chdir("/"))
> > +		perror("changing to root directory");
> > +	umask(0);
> > +
> > +	close(STDERR_FILENO);
> > +}
> > +  
> 
> [...]
> 
> Cheers
> ---Dave




[Index of Archives]     [KVM ARM]     [KVM ia64]     [KVM ppc]     [Virtualization Tools]     [Spice Development]     [Libvirt]     [Libvirt Users]     [Linux USB Devel]     [Linux Audio Users]     [Yosemite Questions]     [Linux Kernel]     [Linux SCSI]     [XFree86]

  Powered by Linux