[libvirt] [PATCH 1/2] tools: console: cleanup console on errors in main thread

Nikolay Shirokovskiy nshirokovskiy at virtuozzo.com
Mon Feb 25 12:52:23 UTC 2019



On 22.02.2019 17:55, John Ferlan wrote:
> 
> 
> On 2/15/19 9:39 AM, Nikolay Shirokovskiy wrote:
>> We only check now for virObjectWait failures in virshRunConsole but
>> we'd better check and for other failures too. Anyway if failure
>> happened we need to shutdown console to stop delivering events
>> from event loop thread or we are in trouble as console is freed
>> on virshRunConsole exit.
>>
>> And we need to add refcounter to console object because event
>> can be delivered after we remove fd handle/stream callback.
>>
>> Signed-off-by: Nikolay Shirokovskiy <nshirokovskiy at virtuozzo.com>
>> ---
>>  tools/virsh-console.c | 161 +++++++++++++++++++++++++++++++++++++-------------
>>  1 file changed, 119 insertions(+), 42 deletions(-)
>>
> 
> There's three things going on in this patch, let's split into convert
> virConsole to virObjectLockable, the usage of locks in the event
> callback methods, and finally handling the late event.
> 
> FWIW: I could see the order being
> 
> 1. Add the virMutex{Lock|Unlock} into the functions and use cleanup path
> logic since right now @con is being altered without the lock.
> 
> 2. Convert to using virObjectLockable
> 
> 3. Add logic to handle late event
> 
> 
>> diff --git a/tools/virsh-console.c b/tools/virsh-console.c
>> index 045a636..c0c3f90 100644
>> --- a/tools/virsh-console.c
>> +++ b/tools/virsh-console.c
>> @@ -60,9 +60,10 @@ struct virConsoleBuffer {
>>  typedef struct virConsole virConsole;
>>  typedef virConsole *virConsolePtr;
>>  struct virConsole {
>> +    virObjectLockable parent;
>> +
>>      virStreamPtr st;
>>      bool quit;
>> -    virMutex lock;
>>      virCond cond;
>>  
>>      int stdinWatch;
>> @@ -74,6 +75,19 @@ struct virConsole {
>>      char escapeChar;
>>  };
>>  
>> +static virClassPtr virConsoleClass;
>> +static void virConsoleDispose(void *obj);
>> +
>> +static int
>> +virConsoleOnceInit(void)
>> +{
>> +    if (!VIR_CLASS_NEW(virConsole, virClassForObjectLockable()))
>> +        return -1;
>> +
>> +    return 0;
>> +}
>> +
>> +VIR_ONCE_GLOBAL_INIT(virConsole);
>>  
>>  static void
>>  virConsoleHandleSignal(int sig ATTRIBUTE_UNUSED)
>> @@ -104,16 +118,14 @@ virConsoleShutdown(virConsolePtr con)
>>  
>>  
>>  static void
>> -virConsoleFree(virConsolePtr con)
>> +virConsoleDispose(void *obj)
>>  {
>> -    if (!con)
>> -        return;
>> +    virConsolePtr con = obj;
>>  
>>      if (con->st)
>>          virStreamFree(con->st);
>> -    virMutexDestroy(&con->lock);
>> +
>>      virCondDestroy(&con->cond);
>> -    VIR_FREE(con);
>>  }
>>  
>>  
>> @@ -123,6 +135,12 @@ virConsoleEventOnStream(virStreamPtr st,
>>  {
>>      virConsolePtr con = opaque;
>>  
>> +    virObjectLock(con);
>> +
>> +    /* we got late event after console shutdowned */
> 
> s/shutdowned/was shutdown/
> 
> (repeats)
> 
>> +    if (!con->st)
>> +        goto cleanup;
>> +
>>      if (events & VIR_STREAM_EVENT_READABLE) {
>>          size_t avail = con->streamToTerminal.length -
>>              con->streamToTerminal.offset;
>> @@ -132,7 +150,7 @@ virConsoleEventOnStream(virStreamPtr st,
>>              if (VIR_REALLOC_N(con->streamToTerminal.data,
>>                                con->streamToTerminal.length + 1024) < 0) {
>>                  virConsoleShutdown(con);
>> -                return;
>> +                goto cleanup;
>>              }
>>              con->streamToTerminal.length += 1024;
>>              avail += 1024;
>> @@ -143,10 +161,10 @@ virConsoleEventOnStream(virStreamPtr st,
>>                              con->streamToTerminal.offset,
>>                              avail);
>>          if (got == -2)
>> -            return; /* blocking */
>> +            goto cleanup; /* blocking */
>>          if (got <= 0) {
>>              virConsoleShutdown(con);
>> -            return;
>> +            goto cleanup;
>>          }
>>          con->streamToTerminal.offset += got;
>>          if (con->streamToTerminal.offset)
>> @@ -162,10 +180,10 @@ virConsoleEventOnStream(virStreamPtr st,
>>                               con->terminalToStream.data,
>>                               con->terminalToStream.offset);
>>          if (done == -2)
>> -            return; /* blocking */
>> +            goto cleanup; /* blocking */
>>          if (done < 0) {
>>              virConsoleShutdown(con);
>> -            return;
>> +            goto cleanup;
>>          }
>>          memmove(con->terminalToStream.data,
>>                  con->terminalToStream.data + done,
>> @@ -187,6 +205,9 @@ virConsoleEventOnStream(virStreamPtr st,
>>          events & VIR_STREAM_EVENT_HANGUP) {
>>          virConsoleShutdown(con);
>>      }
>> +
>> + cleanup:
>> +    virObjectUnlock(con);
>>  }
>>  
>>  
>> @@ -198,6 +219,12 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>>  {
>>      virConsolePtr con = opaque;
>>  
>> +    virObjectLock(con);
>> +
>> +    /* we got late event after console shutdowned */
>> +    if (!con->st)
>> +        goto cleanup;
>> +
>>      if (events & VIR_EVENT_HANDLE_READABLE) {
>>          size_t avail = con->terminalToStream.length -
>>              con->terminalToStream.offset;
>> @@ -207,7 +234,7 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>>              if (VIR_REALLOC_N(con->terminalToStream.data,
>>                                con->terminalToStream.length + 1024) < 0) {
>>                  virConsoleShutdown(con);
>> -                return;
>> +                goto cleanup;
>>              }
>>              con->terminalToStream.length += 1024;
>>              avail += 1024;
>> @@ -220,15 +247,15 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>>          if (got < 0) {
>>              if (errno != EAGAIN)
>>                  virConsoleShutdown(con);
>> -            return;
>> +            goto cleanup;
>>          }
>>          if (got == 0) {
>>              virConsoleShutdown(con);
>> -            return;
>> +            goto cleanup;
>>          }
>>          if (con->terminalToStream.data[con->terminalToStream.offset] == con->escapeChar) {
>>              virConsoleShutdown(con);
>> -            return;
>> +            goto cleanup;
>>          }
>>  
>>          con->terminalToStream.offset += got;
>> @@ -242,6 +269,9 @@ virConsoleEventOnStdin(int watch ATTRIBUTE_UNUSED,
>>          events & VIR_EVENT_HANDLE_HANGUP) {
>>          virConsoleShutdown(con);
>>      }
>> +
>> + cleanup:
>> +    virObjectUnlock(con);
>>  }
>>  
>>  
>> @@ -253,6 +283,12 @@ virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
>>  {
>>      virConsolePtr con = opaque;
>>  
>> +    virObjectLock(con);
>> +
>> +    /* we got late event after console shutdowned */
>> +    if (!con->st)
>> +        goto cleanup;
>> +
>>      if (events & VIR_EVENT_HANDLE_WRITABLE &&
>>          con->streamToTerminal.offset) {
>>          ssize_t done;
>> @@ -263,7 +299,7 @@ virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
>>          if (done < 0) {
>>              if (errno != EAGAIN)
>>                  virConsoleShutdown(con);
>> -            return;
>> +            goto cleanup;
>>          }
>>          memmove(con->streamToTerminal.data,
>>                  con->streamToTerminal.data + done,
>> @@ -285,6 +321,38 @@ virConsoleEventOnStdout(int watch ATTRIBUTE_UNUSED,
>>          events & VIR_EVENT_HANDLE_HANGUP) {
>>          virConsoleShutdown(con);
>>      }
>> +
>> + cleanup:
>> +    virObjectUnlock(con);
>> +}
>> +
>> +
>> +static virConsolePtr
>> +virConsoleNew(void)
>> +{
>> +    virConsolePtr con;
>> +
>> +    if (virConsoleInitialize() < 0)
>> +        return NULL;
>> +
>> +    if (!(con = virObjectNew(virConsoleClass)))
>> +        return NULL;
>> +
>> +    if (virCondInit(&con->cond) < 0) {
>> +        virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
>> +                       _("cannot initialize console condition"));
>> +
>> +        goto error;
>> +    }
>> +
>> +    con->stdinWatch = -1;
>> +    con->stdoutWatch = -1;
>> +
>> +    return con;
>> +
>> + error:
>> +    virObjectUnref(con);
>> +    return NULL;
>>  }
>>  
>>  
>> @@ -324,6 +392,11 @@ virshRunConsole(vshControl *ctl,
>>      if (vshTTYMakeRaw(ctl, true) < 0)
>>          goto resettty;
>>  
>> +    if (!(con = virConsoleNew()))
>> +        goto resettty;
>> +
>> +    virObjectLock(con);
>> +
>>      /* Trap all common signals so that we can safely restore the original
>>       * terminal settings on STDIN before the process exits - people don't like
>>       * being left with a messed up terminal ! */
>> @@ -333,9 +406,6 @@ virshRunConsole(vshControl *ctl,
>>      sigaction(SIGHUP,  &sighandler, &old_sighup);
>>      sigaction(SIGPIPE, &sighandler, &old_sigpipe);
>>  
>> -    if (VIR_ALLOC(con) < 0)
>> -        goto cleanup;
>> -
>>      con->escapeChar = virshGetEscapeChar(priv->escapeChar);
>>      con->st = virStreamNew(virDomainGetConnect(dom),
>>                             VIR_STREAM_NONBLOCK);
>> @@ -345,42 +415,49 @@ virshRunConsole(vshControl *ctl,
>>      if (virDomainOpenConsole(dom, dev_name, con->st, flags) < 0)
>>          goto cleanup;
>>  
>> -    if (virCondInit(&con->cond) < 0 || virMutexInit(&con->lock) < 0)
>> +    virObjectRef(con);
>> +    if ((con->stdinWatch = virEventAddHandle(STDIN_FILENO,
>> +                                             VIR_EVENT_HANDLE_READABLE,
>> +                                             virConsoleEventOnStdin,
>> +                                             con,
>> +                                             virObjectFreeCallback)) < 0) {
>> +        virObjectUnref(con);
>>          goto cleanup;
>> +    }
> 
> Is there a specific reason to go with:
> 
>    ObjectRef
>    if condition
>       error path
>       ObjectUnref> 
> instead of
> 
>    if condition
>    ObjectRef

We can use second construction safely here because we hold console lock and
virConsoleEventOnStdin grabs the lock too. If not we could have error
event/console shutdown/removing handle/calling free callback before we
have chance to reference console on behalf of the watch and this would
lead to use after free in virshRunConsole.

So first construction is a bit more robust. You just first refcount object
and then pass it further without extra consideration.

> 
> I guess it doesn't matter too much, but looks odd to me... If the
> virEventAddHandle fails, the virObjectFreeCallback isn't called so @con
> wouldn't be Unref'd until at least you Unlock'd @con.

That's why we unref console explicitly on virEventAddHandle failure -
because callback does not get called.

> 
>>  
>> -    virMutexLock(&con->lock);
>> -
>> -    con->stdinWatch = virEventAddHandle(STDIN_FILENO,
>> -                                        VIR_EVENT_HANDLE_READABLE,
>> -                                        virConsoleEventOnStdin,
>> -                                        con,
>> -                                        NULL);
>> -    con->stdoutWatch = virEventAddHandle(STDOUT_FILENO,
>> -                                         0,
>> -                                         virConsoleEventOnStdout,
>> -                                         con,
>> -                                         NULL);
>> +    virObjectRef(con);
>> +    if ((con->stdoutWatch = virEventAddHandle(STDOUT_FILENO,
>> +                                              0,
>> +                                              virConsoleEventOnStdout,
>> +                                              con,
>> +                                              virObjectFreeCallback)) < 0) {
>> +        virObjectUnref(con);
>> +        goto cleanup;
>> +    }
>>  
>> -    virStreamEventAddCallback(con->st,
>> -                              VIR_STREAM_EVENT_READABLE,
>> -                              virConsoleEventOnStream,
>> -                              con,
>> -                              NULL);
>> +    virObjectRef(con);
>> +    if (virStreamEventAddCallback(con->st,
>> +                                  VIR_STREAM_EVENT_READABLE,
>> +                                  virConsoleEventOnStream,
>> +                                  con,
>> +                                  virObjectFreeCallback) < 0) {
>> +        virObjectUnref(con);
>> +        goto cleanup;
>> +    }
>>  
>>      while (!con->quit) {
>> -        if (virCondWait(&con->cond, &con->lock) < 0) {
>> -            virMutexUnlock(&con->lock);
>> +        if (virCondWait(&con->cond, &con->parent.lock) < 0) {
>>              VIR_ERROR(_("unable to wait on console condition"));
>>              goto cleanup;
>>          }
>>      }
>>  
>> -    virMutexUnlock(&con->lock);
>> -
>>      ret = 0;
>>  
>>   cleanup:
>> -    virConsoleFree(con);
>> +    virConsoleShutdown(con);
> 
> Should virConsoleShutdown be adjusted to avoid virCondSignal if
> con->quit is tree or this call be adjusted to not call if con->quit is
> true... IOW: Don't call it twice...
> 

I'd better use first variant - this way we make virConsoleShutdown idempotent.

Nikolay

> 
>> +    virObjectUnlock(con);
>> +    virObjectUnref(con);
>>  
>>      /* Restore original signal handlers */
>>      sigaction(SIGQUIT, &old_sigquit, NULL);
>>




More information about the libvir-list mailing list