[BACK]Return to control.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / tmux

Diff for /src/usr.bin/tmux/control.c between version 1.34 and 1.35

version 1.34, 2020/05/26 08:41:47 version 1.35, 2020/06/01 09:43:01
Line 27 
Line 27 
   
 #include "tmux.h"  #include "tmux.h"
   
 /* Control client offset. */  /*
 struct control_offset {   * Block of data to output. Each client has one "all" queue of blocks and
         u_int                           pane;   * another queue for each pane (in struct client_offset). %output blocks are
    * added to both queues and other output lines (notifications) added only to
    * the client queue.
    *
    * When a client becomes writeable, data from blocks on the pane queue are sent
    * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written,
    * it is removed from both pane and client queues and if this means non-%output
    * blocks are now at the head of the client queue, they are written.
    *
    * This means a %output block holds up any subsequent non-%output blocks until
    * it is written which enforces ordering even if the client cannot accept the
    * entire block in one go.
    */
   struct control_block {
           size_t                           size;
           char                            *line;
   
         struct window_pane_offset       offset;          TAILQ_ENTRY(control_block)       entry;
         int                             flags;          TAILQ_ENTRY(control_block)       all_entry;
 #define CONTROL_OFFSET_OFF 0x1   };
   
         RB_ENTRY(control_offset)        entry;  /* Control client pane. */
   struct control_pane {
           u_int                            pane;
   
           /*
            * Offsets into the pane data. The first (offset) is the data we have
            * written; the second (queued) the data we have queued (pointed to by
            * a block).
            */
           struct window_pane_offset        offset;
           struct window_pane_offset        queued;
   
           int                              flags;
   #define CONTROL_PANE_OFF 0x1
   
           int                              pending_flag;
           TAILQ_ENTRY(control_pane)        pending_entry;
   
           TAILQ_HEAD(, control_block)      blocks;
   
           RB_ENTRY(control_pane)           entry;
 };  };
 RB_HEAD(control_offsets, control_offset);  RB_HEAD(control_panes, control_pane);
   
 /* Control client state. */  /* Control client state. */
 struct control_state {  struct control_state {
         struct control_offsets   offsets;          struct control_panes             panes;
   
         struct bufferevent      *read_event;          TAILQ_HEAD(, control_pane)       pending_list;
         struct bufferevent      *write_event;          u_int                            pending_count;
   
           TAILQ_HEAD(, control_block)      all_blocks;
   
           struct bufferevent              *read_event;
           struct bufferevent              *write_event;
 };  };
   
 /* Compare client offsets. */  /* Low watermark. */
   #define CONTROL_BUFFER_LOW 512
   #define CONTROL_BUFFER_HIGH 8192
   
   /* Minimum to write to each client. */
   #define CONTROL_WRITE_MINIMUM 32
   
   /* Flags to ignore client. */
   #define CONTROL_IGNORE_FLAGS \
           (CLIENT_CONTROL_NOOUTPUT| \
            CLIENT_UNATTACHEDFLAGS)
   
   /* Compare client panes. */
 static int  static int
 control_offset_cmp(struct control_offset *co1, struct control_offset *co2)  control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
 {  {
         if (co1->pane < co2->pane)          if (cp1->pane < cp2->pane)
                 return (-1);                  return (-1);
         if (co1->pane > co2->pane)          if (cp1->pane > cp2->pane)
                 return (1);                  return (1);
         return (0);          return (0);
 }  }
 RB_GENERATE_STATIC(control_offsets, control_offset, entry, control_offset_cmp);  RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
   
   /* Free a block. */
   static void
   control_free_block(struct control_state *cs, struct control_block *cb)
   {
           free(cb->line);
           TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
           free(cb);
   }
   
 /* Get pane offsets for this client. */  /* Get pane offsets for this client. */
 static struct control_offset *  static struct control_pane *
 control_get_offset(struct client *c, struct window_pane *wp)  control_get_pane(struct client *c, struct window_pane *wp)
 {  {
         struct control_state    *cs = c->control_state;          struct control_state    *cs = c->control_state;
         struct control_offset    co = { .pane = wp->id };          struct control_pane      cp = { .pane = wp->id };
   
         return (RB_FIND(control_offsets, &cs->offsets, &co));          return (RB_FIND(control_panes, &cs->panes, &cp));
 }  }
   
 /* Add pane offsets for this client. */  /* Add pane offsets for this client. */
 static struct control_offset *  static struct control_pane *
 control_add_offset(struct client *c, struct window_pane *wp)  control_add_pane(struct client *c, struct window_pane *wp)
 {  {
         struct control_state    *cs = c->control_state;          struct control_state    *cs = c->control_state;
         struct control_offset   *co;          struct control_pane     *cp;
   
         co = control_get_offset(c, wp);          cp = control_get_pane(c, wp);
         if (co != NULL)          if (cp != NULL)
                 return (co);                  return (cp);
   
         co = xcalloc(1, sizeof *co);          cp = xcalloc(1, sizeof *cp);
         co->pane = wp->id;          cp->pane = wp->id;
         RB_INSERT(control_offsets, &cs->offsets, co);          RB_INSERT(control_panes, &cs->panes, cp);
         memcpy(&co->offset, &wp->offset, sizeof co->offset);  
         return (co);          memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
           memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
           TAILQ_INIT(&cp->blocks);
   
           return (cp);
 }  }
   
 /* Free control offsets. */  /* Reset control offsets. */
 void  void
 control_free_offsets(struct client *c)  control_reset_offsets(struct client *c)
 {  {
         struct control_state    *cs = c->control_state;          struct control_state    *cs = c->control_state;
         struct control_offset   *co, *co1;          struct control_pane     *cp, *cp1;
   
         RB_FOREACH_SAFE(co, control_offsets, &cs->offsets, co1) {          RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
                 RB_REMOVE(control_offsets, &cs->offsets, co);                  RB_REMOVE(control_panes, &cs->panes, cp);
                 free(co);                  free(cp);
         }          }
   
           TAILQ_INIT(&cs->pending_list);
           cs->pending_count = 0;
 }  }
   
 /* Get offsets for client. */  /* Get offsets for client. */
 struct window_pane_offset *  struct window_pane_offset *
 control_pane_offset(struct client *c, struct window_pane *wp, int *off)  control_pane_offset(struct client *c, struct window_pane *wp, int *off)
 {  {
         struct control_offset   *co;          struct control_state    *cs = c->control_state;
           struct control_pane     *cp;
   
         if (c->flags & CLIENT_CONTROL_NOOUTPUT) {          if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
                 *off = 0;                  *off = 0;
                 return (NULL);                  return (NULL);
         }          }
   
         co = control_get_offset(c, wp);          cp = control_get_pane(c, wp);
         if (co == NULL) {          if (cp == NULL) {
                 *off = 0;                  *off = 0;
                 return (NULL);                  return (NULL);
         }          }
         if (co->flags & CONTROL_OFFSET_OFF) {          if (cp->flags & CONTROL_PANE_OFF) {
                 *off = 1;                  *off = 1;
                 return (NULL);                  return (NULL);
         }          }
         return (&co->offset);          *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW);
           return (&cp->offset);
 }  }
   
 /* Set pane as on. */  /* Set pane as on. */
 void  void
 control_set_pane_on(struct client *c, struct window_pane *wp)  control_set_pane_on(struct client *c, struct window_pane *wp)
 {  {
         struct control_offset   *co;          struct control_pane     *cp;
   
         co = control_get_offset(c, wp);          cp = control_get_pane(c, wp);
         if (co != NULL) {          if (cp != NULL) {
                 co->flags &= ~CONTROL_OFFSET_OFF;                  cp->flags &= ~CONTROL_PANE_OFF;
                 memcpy(&co->offset, &wp->offset, sizeof co->offset);                  memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
                   memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
         }          }
 }  }
   
Line 140 
Line 211 
 void  void
 control_set_pane_off(struct client *c, struct window_pane *wp)  control_set_pane_off(struct client *c, struct window_pane *wp)
 {  {
         struct control_offset   *co;          struct control_pane     *cp;
   
         co = control_add_offset(c, wp);          cp = control_add_pane(c, wp);
         co->flags |= CONTROL_OFFSET_OFF;          cp->flags |= CONTROL_PANE_OFF;
 }  }
   
 /* Write a line. */  /* Write a line. */
 void  static void
 control_write(struct client *c, const char *fmt, ...)  control_vwrite(struct client *c, const char *fmt, va_list ap)
 {  {
         struct control_state    *cs = c->control_state;          struct control_state    *cs = c->control_state;
         va_list                  ap;  
         char                    *s;          char                    *s;
   
         va_start(ap, fmt);  
         xvasprintf(&s, fmt, ap);          xvasprintf(&s, fmt, ap);
         va_end(ap);          log_debug("%s: %s: writing line: %s", __func__, c->name, s);
   
         bufferevent_write(cs->write_event, s, strlen(s));          bufferevent_write(cs->write_event, s, strlen(s));
         bufferevent_write(cs->write_event, "\n", 1);          bufferevent_write(cs->write_event, "\n", 1);
   
           bufferevent_enable(cs->write_event, EV_WRITE);
         free(s);          free(s);
 }  }
   
   /* Write a line. */
   void
   control_write(struct client *c, const char *fmt, ...)
   {
           struct control_state    *cs = c->control_state;
           struct control_block    *cb;
           va_list                  ap;
   
           va_start(ap, fmt);
   
           if (TAILQ_EMPTY(&cs->all_blocks)) {
                   control_vwrite(c, fmt, ap);
                   va_end(ap);
                   return;
           }
   
           cb = xcalloc(1, sizeof *cb);
           xvasprintf(&cb->line, fmt, ap);
           TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
   
           log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
           bufferevent_enable(cs->write_event, EV_WRITE);
   
           va_end(ap);
   }
   
 /* Write output from a pane. */  /* Write output from a pane. */
 void  void
 control_write_output(struct client *c, struct window_pane *wp)  control_write_output(struct client *c, struct window_pane *wp)
 {  {
         struct control_state    *cs = c->control_state;          struct control_state    *cs = c->control_state;
         struct control_offset   *co;          struct control_pane     *cp;
         struct evbuffer         *message;          struct control_block    *cb;
         u_char                  *new_data;          size_t                   new_size;
         size_t                   new_size, i;  
   
         if (c->flags & CLIENT_CONTROL_NOOUTPUT)  
                 return;  
         if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)          if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
                 return;                  return;
   
         co = control_add_offset(c, wp);          if (c->flags & CONTROL_IGNORE_FLAGS) {
         if (co->flags & CONTROL_OFFSET_OFF) {                  cp = control_get_pane(c, wp);
                 window_pane_update_used_data(wp, &co->offset, SIZE_MAX, 1);                  if (cp != NULL)
                           goto ignore;
                 return;                  return;
         }          }
         new_data = window_pane_get_new_data(wp, &co->offset, &new_size);          cp = control_add_pane(c, wp);
           if (cp->flags & CONTROL_PANE_OFF)
                   goto ignore;
   
           window_pane_get_new_data(wp, &cp->queued, &new_size);
         if (new_size == 0)          if (new_size == 0)
                 return;                  return;
           window_pane_update_used_data(wp, &cp->queued, new_size);
   
         message = evbuffer_new();          cb = xcalloc(1, sizeof *cb);
         if (message == NULL)          cb->size = new_size;
                 fatalx("out of memory");          TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
         evbuffer_add_printf(message, "%%output %%%u ", wp->id);  
   
         for (i = 0; i < new_size; i++) {          TAILQ_INSERT_TAIL(&cp->blocks, cb, entry);
                 if (new_data[i] < ' ' || new_data[i] == '\\')          log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name,
                         evbuffer_add_printf(message, "\\%03o", new_data[i]);              cb->size, wp->id);
                 else  
                         evbuffer_add_printf(message, "%c", new_data[i]);          if (!cp->pending_flag) {
                   log_debug("%s: %s: %%%u now pending", __func__, c->name,
                       wp->id);
                   TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry);
                   cp->pending_flag = 1;
                   cs->pending_count++;
         }          }
         evbuffer_add(message, "\n", 1);          bufferevent_enable(cs->write_event, EV_WRITE);
           return;
   
         bufferevent_write_buffer(cs->write_event, message);  ignore:
         evbuffer_free(message);          log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id);
           window_pane_update_used_data(wp, &cp->offset, SIZE_MAX);
         window_pane_update_used_data(wp, &co->offset, new_size, 1);          window_pane_update_used_data(wp, &cp->queued, SIZE_MAX);
 }  }
   
 /* Control client error callback. */  /* Control client error callback. */
Line 246 
Line 351 
                 line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);                  line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
                 if (line == NULL)                  if (line == NULL)
                         break;                          break;
                 log_debug("%s: %s", __func__, line);                  log_debug("%s: %s: %s", __func__, c->name, line);
                 if (*line == '\0') { /* empty line exit */                  if (*line == '\0') { /* empty line detach */
                         free(line);                          free(line);
                         c->flags |= CLIENT_EXIT;                          c->flags |= CLIENT_EXIT;
                         break;                          break;
Line 263 
Line 368 
         }          }
 }  }
   
   /* Does this control client have outstanding data to write? */
   int
   control_all_done(struct client *c)
   {
           struct control_state    *cs = c->control_state;
   
           if (!TAILQ_EMPTY(&cs->all_blocks))
                   return (0);
           return (EVBUFFER_LENGTH(cs->write_event->output) == 0);
   }
   
   /* Flush all blocks until output. */
   static void
   control_flush_all_blocks(struct client *c)
   {
           struct control_state    *cs = c->control_state;
           struct control_block    *cb, *cb1;
   
           TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) {
                   if (cb->size != 0)
                           break;
                   log_debug("%s: %s: flushing line: %s", __func__, c->name,
                       cb->line);
   
                   bufferevent_write(cs->write_event, cb->line, strlen(cb->line));
                   bufferevent_write(cs->write_event, "\n", 1);
                   control_free_block(cs, cb);
           }
   }
   
   /* Append data to buffer. */
   static struct evbuffer *
   control_append_data(struct control_pane *cp, struct evbuffer *message,
       struct window_pane *wp, size_t size)
   {
           u_char  *new_data;
           size_t   new_size;
           u_int    i;
   
           if (message == NULL) {
                   message = evbuffer_new();
                   if (message == NULL)
                           fatalx("out of memory");
                   evbuffer_add_printf(message, "%%output %%%u ", wp->id);
           }
   
           new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
           if (new_size < size)
                   fatalx("not enough data: %zu < %zu", new_size, size);
           for (i = 0; i < size; i++) {
                   if (new_data[i] < ' ' || new_data[i] == '\\')
                           evbuffer_add_printf(message, "\\%03o", new_data[i]);
                   else
                           evbuffer_add_printf(message, "%c", new_data[i]);
           }
           window_pane_update_used_data(wp, &cp->offset, size);
           return (message);
   }
   
   /* Write buffer. */
   static void
   control_write_data(struct client *c, struct evbuffer *message)
   {
           struct control_state    *cs = c->control_state;
   
           log_debug("%s: %s: %.*s", __func__, c->name,
               (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message));
   
           evbuffer_add(message, "\n", 1);
           bufferevent_write_buffer(cs->write_event, message);
           evbuffer_free(message);
   }
   
   /* Write output to client. */
   static int
   control_write_pending(struct client *c, struct control_pane *cp, size_t limit)
   {
           struct control_state    *cs = c->control_state;
           struct session          *s = c->session;
           struct window_pane      *wp = NULL;
           struct evbuffer         *message = NULL;
           size_t                   used = 0, size;
           struct control_block    *cb, *cb1;
   
           if (s == NULL ||
               (wp = window_pane_find_by_id(cp->pane)) == NULL ||
               winlink_find_by_window(&s->windows, wp->window) == NULL) {
                   TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1)
                           control_free_block(cs, cb);
                   control_flush_all_blocks(c);
                   return (0);
           }
   
           while (used != limit && !TAILQ_EMPTY(&cp->blocks)) {
                   cb = TAILQ_FIRST(&cp->blocks);
                   log_debug("%s: %s: output block %zu for %%%u (used %zu/%zu)",
                       __func__, c->name, cb->size, cp->pane, used, limit);
   
                   size = cb->size;
                   if (size > limit - used)
                           size = limit - used;
                   used += size;
   
                   message = control_append_data(cp, message, wp, size);
   
                   cb->size -= size;
                   if (cb->size == 0) {
                           TAILQ_REMOVE(&cp->blocks, cb, entry);
                           control_free_block(cs, cb);
   
                           cb = TAILQ_FIRST(&cs->all_blocks);
                           if (cb != NULL && cb->size == 0) {
                                   if (wp != NULL && message != NULL) {
                                           control_write_data(c, message);
                                           message = NULL;
                                   }
                                   control_flush_all_blocks(c);
                           }
                   }
           }
           if (message != NULL)
                   control_write_data(c, message);
           return (!TAILQ_EMPTY(&cp->blocks));
   }
   
   /* Control client write callback. */
   static void
   control_write_callback(__unused struct bufferevent *bufev, void *data)
   {
           struct client           *c = data;
           struct control_state    *cs = c->control_state;
           struct control_pane     *cp, *cp1;
           struct evbuffer         *evb = cs->write_event->output;
           size_t                   space, limit;
   
           control_flush_all_blocks(c);
   
           while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) {
                   if (cs->pending_count == 0)
                           break;
                   space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb);
                   log_debug("%s: %s: %zu bytes available, %u panes", __func__,
                       c->name, space, cs->pending_count);
   
                   limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */
                   if (limit < CONTROL_WRITE_MINIMUM)
                           limit = CONTROL_WRITE_MINIMUM;
   
                   TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) {
                           if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH)
                                   break;
                           if (control_write_pending(c, cp, limit))
                                   continue;
                           TAILQ_REMOVE(&cs->pending_list, cp, pending_entry);
                           cp->pending_flag = 0;
                           cs->pending_count--;
                   }
           }
           if (EVBUFFER_LENGTH(evb) == 0)
                   bufferevent_disable(cs->write_event, EV_WRITE);
   }
   
 /* Initialize for control mode. */  /* Initialize for control mode. */
 void  void
 control_start(struct client *c)  control_start(struct client *c)
Line 277 
Line 544 
         setblocking(c->fd, 0);          setblocking(c->fd, 0);
   
         cs = c->control_state = xcalloc(1, sizeof *cs);          cs = c->control_state = xcalloc(1, sizeof *cs);
         RB_INIT(&cs->offsets);          RB_INIT(&cs->panes);
           TAILQ_INIT(&cs->pending_list);
           TAILQ_INIT(&cs->all_blocks);
   
         cs->read_event = bufferevent_new(c->fd, control_read_callback, NULL,          cs->read_event = bufferevent_new(c->fd, control_read_callback,
             control_error_callback, c);              control_write_callback, control_error_callback, c);
         bufferevent_enable(cs->read_event, EV_READ);          bufferevent_enable(cs->read_event, EV_READ);
   
         if (c->flags & CLIENT_CONTROLCONTROL)          if (c->flags & CLIENT_CONTROLCONTROL)
                 cs->write_event = cs->read_event;                  cs->write_event = cs->read_event;
         else {          else {
                 cs->write_event = bufferevent_new(c->out_fd, NULL, NULL,                  cs->write_event = bufferevent_new(c->out_fd, NULL,
                     control_error_callback, c);                      control_write_callback, control_error_callback, c);
         }          }
         bufferevent_enable(cs->write_event, EV_WRITE);          bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
               0);
   
         if (c->flags & CLIENT_CONTROLCONTROL)          if (c->flags & CLIENT_CONTROLCONTROL) {
                 control_write(c, "\033P1000p");                  bufferevent_write(cs->write_event, "\033P1000p", 7);
                   bufferevent_enable(cs->write_event, EV_WRITE);
           }
 }  }
   
   /* Flush all output for a client that is detaching. */
   void
   control_flush(struct client *c)
   {
           struct control_state    *cs = c->control_state;
           struct control_pane     *cp;
           struct control_block    *cb, *cb1;
   
           RB_FOREACH(cp, control_panes, &cs->panes) {
                   TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
                           TAILQ_REMOVE(&cp->blocks, cb, entry);
                           control_free_block(cs, cb);
                   }
           }
   }
   
 /* Stop control mode. */  /* Stop control mode. */
 void  void
 control_stop(struct client *c)  control_stop(struct client *c)
 {  {
         struct control_state    *cs = c->control_state;          struct control_state    *cs = c->control_state;
           struct control_block    *cb, *cb1;
   
         if (~c->flags & CLIENT_CONTROLCONTROL)          if (~c->flags & CLIENT_CONTROLCONTROL)
                 bufferevent_free(cs->write_event);                  bufferevent_free(cs->write_event);
         bufferevent_free(cs->read_event);          bufferevent_free(cs->read_event);
   
         control_free_offsets(c);          TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
                   control_free_block(cs, cb);
           control_reset_offsets(c);
   
         free(cs);          free(cs);
 }  }

Legend:
Removed from v.1.34  
changed lines
  Added in v.1.35