| /* vi:set ts=8 sts=4 sw=4: |
| * |
| * VIM - Vi IMproved by Bram Moolenaar |
| * |
| * Do ":help uganda" in Vim to read copying and usage conditions. |
| * Do ":help credits" in Vim to see a list of people who contributed. |
| */ |
| |
| /* |
| * Implements communication through a socket or any file handle. |
| */ |
| |
| #include "vim.h" |
| |
| #if defined(FEAT_CHANNEL) || defined(PROTO) |
| |
| /* |
| * Change the zero to 1 to enable debugging. |
| * This will write a file "channel_debug.log". |
| */ |
| #if 0 |
| # define CHERROR(fmt, arg) cherror(fmt, arg) |
| # define CHLOG(idx, send, buf) chlog(idx, send, buf) |
| # define CHFILE "channel_debug.log" |
| |
| static void cherror(char *fmt, char *arg); |
| static void chlog(int send, char_u *buf); |
| #else |
| # define CHERROR(fmt, arg) |
| # define CHLOG(idx, send, buf) |
| #endif |
| |
| /* TRUE when netbeans is running with a GUI. */ |
| #ifdef FEAT_GUI |
| # define CH_HAS_GUI (gui.in_use || gui.starting) |
| #endif |
| |
| /* Note: when making changes here also adjust configure.in. */ |
| #ifdef WIN32 |
| /* WinSock API is separated from C API, thus we can't use read(), write(), |
| * errno... */ |
| # define SOCK_ERRNO errno = WSAGetLastError() |
| # undef ECONNREFUSED |
| # define ECONNREFUSED WSAECONNREFUSED |
| # ifdef EINTR |
| # undef EINTR |
| # endif |
| # define EINTR WSAEINTR |
| # define sock_write(sd, buf, len) send(sd, buf, len, 0) |
| # define sock_read(sd, buf, len) recv(sd, buf, len, 0) |
| # define sock_close(sd) closesocket(sd) |
| # define sleep(t) Sleep(t*1000) /* WinAPI Sleep() accepts milliseconds */ |
| #else |
| # include <netdb.h> |
| # include <netinet/in.h> |
| |
| # include <sys/socket.h> |
| # ifdef HAVE_LIBGEN_H |
| # include <libgen.h> |
| # endif |
| # define SOCK_ERRNO |
| # define sock_write(sd, buf, len) write(sd, buf, len) |
| # define sock_read(sd, buf, len) read(sd, buf, len) |
| # define sock_close(sd) close(sd) |
| #endif |
| |
| #ifdef FEAT_GUI_W32 |
| extern HWND s_hwnd; /* Gvim's Window handle */ |
| #endif |
| |
| struct readqueue |
| { |
| char_u *buffer; |
| struct readqueue *next; |
| struct readqueue *prev; |
| }; |
| typedef struct readqueue queue_T; |
| |
| typedef struct { |
| sock_T ch_fd; /* the socket, -1 for a closed channel */ |
| int ch_idx; /* used by channel_poll_setup() */ |
| queue_T ch_head; /* dummy node, header for circular queue */ |
| |
| int ch_error; /* When TRUE an error was reported. Avoids giving |
| * pages full of error messages when the other side |
| * has exited, only mention the first error until the |
| * connection works again. */ |
| #ifdef FEAT_GUI_X11 |
| XtInputId ch_inputHandler; /* Cookie for input */ |
| #endif |
| #ifdef FEAT_GUI_GTK |
| gint ch_inputHandler; /* Cookie for input */ |
| #endif |
| #ifdef FEAT_GUI_W32 |
| int ch_inputHandler = -1; /* simply ret.value of WSAAsyncSelect() */ |
| #endif |
| |
| void (*ch_close_cb)(void); /* callback invoked when channel is closed */ |
| } channel_T; |
| |
| /* |
| * Information about all channels. |
| * There can be gaps for closed channels, they will be reused later. |
| */ |
| static channel_T *channels = NULL; |
| static int channel_count = 0; |
| |
| /* |
| * TODO: open debug file when desired. |
| */ |
| FILE *debugfd = NULL; |
| |
| /* |
| * Add a new channel slot, return the index. |
| * The channel isn't actually used into ch_fd is set >= 0; |
| * Returns -1 if all channels are in use. |
| */ |
| static int |
| add_channel(void) |
| { |
| int idx; |
| channel_T *new_channels; |
| |
| if (channels != NULL) |
| for (idx = 0; idx < channel_count; ++idx) |
| if (channels[idx].ch_fd < 0) |
| /* re-use a closed channel slot */ |
| return idx; |
| if (channel_count == MAX_OPEN_CHANNELS) |
| return -1; |
| new_channels = (channel_T *)alloc(sizeof(channel_T) * (channel_count + 1)); |
| if (new_channels == NULL) |
| return -1; |
| if (channels != NULL) |
| mch_memmove(new_channels, channels, sizeof(channel_T) * channel_count); |
| channels = new_channels; |
| (void)vim_memset(&channels[channel_count], 0, sizeof(channel_T)); |
| |
| channels[channel_count].ch_fd = (sock_T)-1; |
| #ifdef FEAT_GUI_X11 |
| channels[channel_count].ch_inputHandler = (XtInputId)NULL; |
| #endif |
| #ifdef FEAT_GUI_GTK |
| channels[channel_count].ch_inputHandler = 0; |
| #endif |
| #ifdef FEAT_GUI_W32 |
| channels[channel_count].ch_inputHandler = -1; |
| #endif |
| |
| return channel_count++; |
| } |
| |
| #if defined(FEAT_GUI) || defined(PROTO) |
| /* |
| * Read a command from netbeans. |
| */ |
| #ifdef FEAT_GUI_X11 |
| static void |
| messageFromNetbeans(XtPointer clientData, |
| int *unused1 UNUSED, |
| XtInputId *unused2 UNUSED) |
| { |
| channel_read((int)(long)clientData); |
| } |
| #endif |
| |
| #ifdef FEAT_GUI_GTK |
| static void |
| messageFromNetbeans(gpointer clientData, |
| gint unused1 UNUSED, |
| GdkInputCondition unused2 UNUSED) |
| { |
| channel_read((int)(long)clientData); |
| } |
| #endif |
| |
| static void |
| channel_gui_register(int idx) |
| { |
| channel_T *channel = &channels[idx]; |
| |
| if (!CH_HAS_GUI) |
| return; |
| |
| # ifdef FEAT_GUI_X11 |
| /* tell notifier we are interested in being called |
| * when there is input on the editor connection socket |
| */ |
| if (channel->ch_inputHandler == (XtInputId)NULL) |
| channel->ch_inputHandler = |
| XtAppAddInput((XtAppContext)app_context, channel->ch_fd, |
| (XtPointer)(XtInputReadMask + XtInputExceptMask), |
| messageFromNetbeans, (XtPointer)idx); |
| # else |
| # ifdef FEAT_GUI_GTK |
| /* |
| * Tell gdk we are interested in being called when there |
| * is input on the editor connection socket |
| */ |
| if (channel->ch_inputHandler == 0) |
| channel->ch_inputHandler = |
| gdk_input_add((gint)channel->ch_fd, (GdkInputCondition) |
| ((int)GDK_INPUT_READ + (int)GDK_INPUT_EXCEPTION), |
| messageFromNetbeans, (gpointer)(long)idx); |
| # else |
| # ifdef FEAT_GUI_W32 |
| /* |
| * Tell Windows we are interested in receiving message when there |
| * is input on the editor connection socket. |
| * TODO: change WM_NETBEANS to something related to the channel index. |
| */ |
| if (channel->ch_inputHandler == -1) |
| channel->ch_inputHandler = |
| WSAAsyncSelect(channel->ch_fd, s_hwnd, WM_NETBEANS, FD_READ); |
| # endif |
| # endif |
| # endif |
| } |
| |
| /* |
| * Register any of our file descriptors with the GUI event handling system. |
| * Called when the GUI has started. |
| */ |
| void |
| channel_gui_register_all(void) |
| { |
| int i; |
| |
| for (i = 0; i < channel_count; ++i) |
| if (channels[i].ch_fd >= 0) |
| channel_gui_register(i); |
| } |
| |
| static void |
| channel_gui_unregister(int idx) |
| { |
| channel_T *channel = &channels[idx]; |
| |
| # ifdef FEAT_GUI_X11 |
| if (channel->ch_inputHandler != (XtInputId)NULL) |
| { |
| XtRemoveInput(channel->ch_inputHandler); |
| channel->ch_inputHandler = (XtInputId)NULL; |
| } |
| # else |
| # ifdef FEAT_GUI_GTK |
| if (channel->ch_inputHandler != 0) |
| { |
| gdk_input_remove(channel->ch_inputHandler); |
| channel->ch_inputHandler = 0; |
| } |
| # else |
| # ifdef FEAT_GUI_W32 |
| if (channel->ch_inputHandler == 0) |
| { |
| WSAAsyncSelect(nbsock, s_hwnd, 0, 0); |
| channel->ch_inputHandler = -1; |
| } |
| # endif |
| # endif |
| # endif |
| } |
| |
| #endif |
| |
| /* |
| * Open a channel to "hostname":"port". |
| * Returns the channel number for success. |
| * Returns a negative number for failure. |
| */ |
| int |
| channel_open(char *hostname, int port_in, void (*close_cb)(void)) |
| { |
| int sd; |
| struct sockaddr_in server; |
| struct hostent * host; |
| #ifdef FEAT_GUI_W32 |
| u_short port = port_in; |
| #else |
| int port = port_in; |
| #endif |
| int idx; |
| |
| #ifdef FEAT_GUI_W32 |
| channel_init_winsock(); |
| #endif |
| |
| idx = add_channel(); |
| if (idx < 0) |
| { |
| CHERROR("All channels are in use\n", ""); |
| EMSG(_("E999: All channels are in use")); |
| return -1; |
| } |
| |
| if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) |
| { |
| CHERROR("error in socket() in channel_open()\n", ""); |
| PERROR("E999: socket() in channel_open()"); |
| return -1; |
| } |
| |
| /* Get the server internet address and put into addr structure */ |
| /* fill in the socket address structure and connect to server */ |
| vim_memset((char *)&server, 0, sizeof(server)); |
| server.sin_family = AF_INET; |
| server.sin_port = htons(port); |
| if ((host = gethostbyname(hostname)) == NULL) |
| { |
| CHERROR("error in gethostbyname() in channel_open()\n", ""); |
| PERROR("E999: gethostbyname() in channel_open()"); |
| sock_close(sd); |
| return -1; |
| } |
| memcpy((char *)&server.sin_addr, host->h_addr, host->h_length); |
| |
| /* Connect to server */ |
| if (connect(sd, (struct sockaddr *)&server, sizeof(server))) |
| { |
| SOCK_ERRNO; |
| CHERROR("channel_open: Connect failed with errno %d\n", errno); |
| if (errno == ECONNREFUSED) |
| { |
| sock_close(sd); |
| if ((sd = (sock_T)socket(AF_INET, SOCK_STREAM, 0)) == (sock_T)-1) |
| { |
| SOCK_ERRNO; |
| CHERROR("socket() retry in channel_open()\n", ""); |
| PERROR("E999: socket() retry in channel_open()"); |
| return -1; |
| } |
| if (connect(sd, (struct sockaddr *)&server, sizeof(server))) |
| { |
| int retries = 36; |
| int success = FALSE; |
| |
| SOCK_ERRNO; |
| while (retries-- && ((errno == ECONNREFUSED) |
| || (errno == EINTR))) |
| { |
| CHERROR("retrying...\n", ""); |
| mch_delay(3000L, TRUE); |
| ui_breakcheck(); |
| if (got_int) |
| { |
| errno = EINTR; |
| break; |
| } |
| if (connect(sd, (struct sockaddr *)&server, |
| sizeof(server)) == 0) |
| { |
| success = TRUE; |
| break; |
| } |
| SOCK_ERRNO; |
| } |
| if (!success) |
| { |
| /* Get here when the server can't be found. */ |
| CHERROR("Cannot connect to port after retry\n", ""); |
| PERROR(_("E999: Cannot connect to port after retry2")); |
| sock_close(sd); |
| return -1; |
| } |
| } |
| } |
| else |
| { |
| CHERROR("Cannot connect to port\n", ""); |
| PERROR(_("E999: Cannot connect to port")); |
| sock_close(sd); |
| return -1; |
| } |
| } |
| |
| channels[idx].ch_fd = sd; |
| channels[idx].ch_close_cb = close_cb; |
| |
| #ifdef FEAT_GUI |
| channel_gui_register(idx); |
| #endif |
| |
| return idx; |
| } |
| |
| /* |
| * Return TRUE when channel "idx" is open. |
| */ |
| int |
| channel_is_open(int idx) |
| { |
| return channels[idx].ch_fd >= 0; |
| } |
| |
| /* |
| * Close channel "idx". |
| * This does not trigger the close callback. |
| */ |
| void |
| channel_close(int idx) |
| { |
| channel_T *channel = &channels[idx]; |
| |
| if (channel->ch_fd >= 0) |
| { |
| sock_close(channel->ch_fd); |
| channel->ch_fd = -1; |
| #ifdef FEAT_GUI |
| channel_gui_unregister(idx); |
| #endif |
| } |
| } |
| |
| /* |
| * Store "buf[len]" on channel "idx". |
| */ |
| void |
| channel_save(int idx, char_u *buf, int len) |
| { |
| queue_T *node; |
| queue_T *head = &channels[idx].ch_head; |
| |
| node = (queue_T *)alloc(sizeof(queue_T)); |
| if (node == NULL) |
| return; /* out of memory */ |
| node->buffer = alloc(len + 1); |
| if (node->buffer == NULL) |
| { |
| vim_free(node); |
| return; /* out of memory */ |
| } |
| mch_memmove(node->buffer, buf, (size_t)len); |
| node->buffer[len] = NUL; |
| |
| if (head->next == NULL) /* initialize circular queue */ |
| { |
| head->next = head; |
| head->prev = head; |
| } |
| |
| /* insert node at tail of queue */ |
| node->next = head; |
| node->prev = head->prev; |
| head->prev->next = node; |
| head->prev = node; |
| |
| if (debugfd != NULL) |
| { |
| fprintf(debugfd, "RECV on %d: ", idx); |
| fwrite(buf, len, 1, debugfd); |
| fprintf(debugfd, "\n"); |
| } |
| } |
| |
| /* |
| * Return the first buffer from the channel without removing it. |
| * Returns NULL if there is nothing. |
| */ |
| char_u * |
| channel_peek(int idx) |
| { |
| queue_T *head = &channels[idx].ch_head; |
| |
| if (head->next == head || head->next == NULL) |
| return NULL; |
| return head->next->buffer; |
| } |
| |
| /* |
| * Return the first buffer from the channel and remove it. |
| * The caller must free it. |
| * Returns NULL if there is nothing. |
| */ |
| char_u * |
| channel_get(int idx) |
| { |
| queue_T *head = &channels[idx].ch_head; |
| queue_T *node; |
| char_u *p; |
| |
| if (head->next == head || head->next == NULL) |
| return NULL; |
| node = head->next; |
| /* dispose of the node but keep the buffer */ |
| p = node->buffer; |
| head->next = node->next; |
| node->next->prev = node->prev; |
| vim_free(node); |
| return p; |
| } |
| |
| /* |
| * Collapses the first and second buffer in the channel "idx". |
| * Returns FAIL if that is not possible. |
| */ |
| int |
| channel_collapse(int idx) |
| { |
| queue_T *head = &channels[idx].ch_head; |
| queue_T *node = head->next; |
| char_u *p; |
| |
| if (node == head || node == NULL || node->next == head) |
| return FAIL; |
| |
| p = alloc((unsigned)(STRLEN(node->buffer) |
| + STRLEN(node->next->buffer) + 1)); |
| if (p == NULL) |
| return FAIL; /* out of memory */ |
| STRCPY(p, node->buffer); |
| STRCAT(p, node->next->buffer); |
| vim_free(node->next->buffer); |
| node->next->buffer = p; |
| |
| /* dispose of the node and buffer */ |
| head->next = node->next; |
| node->next->prev = node->prev; |
| vim_free(node->buffer); |
| vim_free(node); |
| return OK; |
| } |
| |
| /* |
| * Clear the read buffer on channel "idx". |
| */ |
| void |
| channel_clear(int idx) |
| { |
| queue_T *head = &channels[idx].ch_head; |
| queue_T *node = head->next; |
| queue_T *next; |
| |
| while (node != NULL && node != head) |
| { |
| next = node->next; |
| vim_free(node->buffer); |
| vim_free(node); |
| if (next == head) |
| { |
| head->next = head; |
| head->prev = head; |
| break; |
| } |
| node = next; |
| } |
| } |
| |
| /* Sent when the channel is found closed when reading. */ |
| #define DETACH_MSG "\"DETACH\"\n" |
| |
| /* Buffer size for reading incoming messages. */ |
| #define MAXMSGSIZE 4096 |
| |
| /* |
| * Read from channel "idx". The data is put in the read queue. |
| */ |
| void |
| channel_read(int idx) |
| { |
| static char_u *buf = NULL; |
| int len = 0; |
| int readlen = 0; |
| #ifdef HAVE_SELECT |
| struct timeval tval; |
| fd_set rfds; |
| #else |
| # ifdef HAVE_POLL |
| struct pollfd fds; |
| # endif |
| #endif |
| channel_T *channel = &channels[idx]; |
| |
| if (channel->ch_fd < 0) |
| { |
| CHLOG(idx, FALSE, "channel_read() called while socket is closed\n"); |
| return; |
| } |
| |
| /* Allocate a buffer to read into. */ |
| if (buf == NULL) |
| { |
| buf = alloc(MAXMSGSIZE); |
| if (buf == NULL) |
| return; /* out of memory! */ |
| } |
| |
| /* Keep on reading for as long as there is something to read. |
| * Use select() or poll() to avoid blocking on a message that is exactly |
| * MAXMSGSIZE long. */ |
| for (;;) |
| { |
| #ifdef HAVE_SELECT |
| FD_ZERO(&rfds); |
| FD_SET(channel->ch_fd, &rfds); |
| tval.tv_sec = 0; |
| tval.tv_usec = 0; |
| if (select(channel->ch_fd + 1, &rfds, NULL, NULL, &tval) <= 0) |
| break; |
| #else |
| # ifdef HAVE_POLL |
| fds.fd = channel->ch_fd; |
| fds.events = POLLIN; |
| if (poll(&fds, 1, 0) <= 0) |
| break; |
| # endif |
| #endif |
| len = sock_read(channel->ch_fd, buf, MAXMSGSIZE); |
| if (len <= 0) |
| break; /* error or nothing more to read */ |
| |
| /* Store the read message in the queue. */ |
| channel_save(idx, buf, len); |
| readlen += len; |
| if (len < MAXMSGSIZE) |
| break; /* did read everything that's available */ |
| } |
| |
| /* Reading a socket disconnection (readlen == 0), or a socket error. */ |
| if (readlen <= 0) |
| { |
| /* Queue a "DETACH" netbeans message in the command queue in order to |
| * terminate the netbeans session later. Do not end the session here |
| * directly as we may be running in the context of a call to |
| * netbeans_parse_messages(): |
| * netbeans_parse_messages |
| * -> autocmd triggered while processing the netbeans cmd |
| * -> ui_breakcheck |
| * -> gui event loop or select loop |
| * -> channel_read() |
| */ |
| channel_save(idx, (char_u *)DETACH_MSG, (int)STRLEN(DETACH_MSG)); |
| |
| channel_close(idx); |
| if (channel->ch_close_cb != NULL) |
| (*channel->ch_close_cb)(); |
| |
| if (len < 0) |
| { |
| /* Todo: which channel? */ |
| CHERROR("%s(): cannot from channel\n", "channel_read"); |
| PERROR(_("E999: read from channel")); |
| } |
| } |
| |
| #if defined(CH_HAS_GUI) && defined(FEAT_GUI_GTK) |
| if (CH_HAS_GUI && gtk_main_level() > 0) |
| gtk_main_quit(); |
| #endif |
| } |
| |
| /* |
| * Write "buf" (NUL terminated string) to channel "idx". |
| * When "fun" is not NULL an error message might be given. |
| */ |
| void |
| channel_send(int idx, char_u *buf, char *fun) |
| { |
| channel_T *channel = &channels[idx]; |
| int len = (int)STRLEN(buf); |
| |
| if (channel->ch_fd < 0) |
| { |
| if (!channel->ch_error && fun != NULL) |
| { |
| CHERROR(" %s(): write while not connected\n", fun); |
| EMSG2("E630: %s(): write while not connected", fun); |
| } |
| channel->ch_error = TRUE; |
| } |
| else if (sock_write(channel->ch_fd, buf, len) != len) |
| { |
| if (!channel->ch_error && fun != NULL) |
| { |
| CHERROR(" %s(): write failed\n", fun); |
| EMSG2("E631: %s(): write failed", fun); |
| } |
| channel->ch_error = TRUE; |
| } |
| else |
| channel->ch_error = FALSE; |
| } |
| |
| # if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO) |
| /* |
| * Add open channels to the poll struct. |
| * Return the adjusted struct index. |
| * The type of "fds" is hidden to avoid problems with the function proto. |
| */ |
| int |
| channel_poll_setup(int nfd_in, void *fds_in) |
| { |
| int nfd = nfd_in; |
| int i; |
| struct pollfd *fds = fds_in; |
| |
| for (i = 0; i < channel_count; ++i) |
| if (channels[i].ch_fd >= 0) |
| { |
| channels[i].ch_idx = nfd; |
| fds[nfd].fd = channels[i].ch_fd; |
| fds[nfd].events = POLLIN; |
| nfd++; |
| } |
| else |
| channels[i].ch_idx = -1; |
| |
| return nfd; |
| } |
| |
| /* |
| * The type of "fds" is hidden to avoid problems with the function proto. |
| */ |
| int |
| channel_poll_check(int ret_in, void *fds_in) |
| { |
| int ret = ret_in; |
| int i; |
| struct pollfd *fds = fds_in; |
| |
| for (i = 0; i < channel_count; ++i) |
| if (ret > 0 && channels[i].ch_idx != -1 |
| && fds[channels[i].ch_idx].revents & POLLIN) |
| { |
| channel_read(i); |
| --ret; |
| } |
| |
| return ret; |
| } |
| # endif /* UNIX && !HAVE_SELECT */ |
| |
| # if (defined(UNIX) && defined(HAVE_SELECT)) || defined(PROTO) |
| /* |
| * The type of "rfds" is hidden to avoid problems with the function proto. |
| */ |
| int |
| channel_select_setup(int maxfd_in, void *rfds_in) |
| { |
| int maxfd = maxfd_in; |
| int i; |
| fd_set *rfds = rfds_in; |
| |
| for (i = 0; i < channel_count; ++i) |
| if (channels[i].ch_fd >= 0) |
| { |
| FD_SET(channels[i].ch_fd, rfds); |
| if (maxfd < channels[i].ch_fd) |
| maxfd = channels[i].ch_fd; |
| } |
| |
| return maxfd; |
| } |
| |
| /* |
| * The type of "rfds" is hidden to avoid problems with the function proto. |
| */ |
| int |
| channel_select_check(int ret_in, void *rfds_in) |
| { |
| int ret = ret_in; |
| int i; |
| fd_set *rfds = rfds_in; |
| |
| for (i = 0; i < channel_count; ++i) |
| if (ret > 0 && channels[i].ch_fd >= 0 |
| && FD_ISSET(channels[i].ch_fd, rfds)) |
| { |
| channel_read(i); |
| --ret; |
| } |
| |
| return ret; |
| } |
| # endif /* UNIX && HAVE_SELECT */ |
| |
| #endif /* FEAT_CHANNEL */ |