Handle multiple commands per connection via netconsole
authorTerry Wilson <twilson@digium.com>
Thu, 19 Apr 2012 14:35:56 +0000 (14:35 +0000)
committerTerry Wilson <twilson@digium.com>
Thu, 19 Apr 2012 14:35:56 +0000 (14:35 +0000)
Asterisk would accept multiple NULL-delimited CLI commands via the
netconsole socket, but would occasionally miss a command due to the
command not being completely read into the buffer. This patch ensures
that any partial commands get moved to the front of the read buffer,
appended to, and properly sent.

(closes issue ASTERISK-18308)
Review: https://reviewboard.asterisk.org/r/1876/
........

Merged revisions 362536 from http://svn.asterisk.org/svn/asterisk/branches/1.8
........

Merged revisions 362537 from http://svn.asterisk.org/svn/asterisk/branches/10

git-svn-id: https://origsvn.digium.com/svn/asterisk/trunk@362538 65c4cc65-6c06-0410-ace0-fbb531ad65f3

main/asterisk.c

index 937259c..539a37e 100644 (file)
@@ -1264,14 +1264,17 @@ static void *netconsole(void *vconsole)
 {
        struct console *con = vconsole;
        char hostname[MAXHOSTNAMELEN] = "";
-       char tmp[512];
+       char inbuf[512];
+       char outbuf[512];
+       const char *end_buf = inbuf + sizeof(inbuf);
+       char *start_read = inbuf;
        int res;
        struct pollfd fds[2];
 
        if (gethostname(hostname, sizeof(hostname)-1))
                ast_copy_string(hostname, "<Unknown>", sizeof(hostname));
-       snprintf(tmp, sizeof(tmp), "%s/%ld/%s\n", hostname, (long)ast_mainpid, ast_get_version());
-       fdprint(con->fd, tmp);
+       snprintf(outbuf, sizeof(outbuf), "%s/%ld/%s\n", hostname, (long)ast_mainpid, ast_get_version());
+       fdprint(con->fd, outbuf);
        for (;;) {
                fds[0].fd = con->fd;
                fds[0].events = POLLIN;
@@ -1287,24 +1290,49 @@ static void *netconsole(void *vconsole)
                        continue;
                }
                if (fds[0].revents) {
-                       res = read_credentials(con->fd, tmp, sizeof(tmp) - 1, con);
-                       if (res < 1) {
+                       int cmds_read, bytes_read;
+                       if ((bytes_read = read_credentials(con->fd, start_read, end_buf - start_read, con)) < 1) {
                                break;
                        }
-                       tmp[res] = 0;
-                       if (strncmp(tmp, "cli quit after ", 15) == 0) {
-                               ast_cli_command_multiple_full(con->uid, con->gid, con->fd, res - 15, tmp + 15);
+                       /* XXX This will only work if it is the first command, and I'm not sure fixing it is worth the effort. */
+                       if (strncmp(inbuf, "cli quit after ", 15) == 0) {
+                               ast_cli_command_multiple_full(con->uid, con->gid, con->fd, bytes_read - 15, inbuf + 15);
                                break;
                        }
-                       ast_cli_command_multiple_full(con->uid, con->gid, con->fd, res, tmp);
+                       /* ast_cli_command_multiple_full will only process individual commands terminated by a
+                        * NULL and not trailing partial commands. */
+                       if (!(cmds_read = ast_cli_command_multiple_full(con->uid, con->gid, con->fd, bytes_read + start_read - inbuf, inbuf))) {
+                               /* No commands were read. We either have a short read on the first command
+                                * with space left, or a command that is too long */
+                               if (start_read + bytes_read < end_buf) {
+                                       start_read += bytes_read;
+                               } else {
+                                       ast_log(LOG_ERROR, "Command too long! Skipping\n");
+                                       start_read = inbuf;
+                               }
+                               continue;
+                       }
+                       if (start_read[bytes_read - 1] == '\0') {
+                               /* The read ended on a command boundary, start reading again at the head of inbuf */
+                               start_read = inbuf;
+                               continue;
+                       }
+                       /* If we get this far, we have left over characters that have not been processed.
+                        * Advance to the character after the last command read by ast_cli_command_multiple_full.
+                        * We are guaranteed to have at least cmds_read NULLs */
+                       while (cmds_read-- && (start_read = rawmemchr(start_read, '\0'))) {
+                               start_read++;
+                       }
+                       memmove(inbuf, start_read, end_buf - start_read);
+                       start_read = end_buf - start_read + inbuf;
                }
                if (fds[1].revents) {
-                       res = read_credentials(con->p[0], tmp, sizeof(tmp), con);
+                       res = read_credentials(con->p[0], outbuf, sizeof(outbuf), con);
                        if (res < 1) {
                                ast_log(LOG_ERROR, "read returned %d\n", res);
                                break;
                        }
-                       res = write(con->fd, tmp, res);
+                       res = write(con->fd, outbuf, res);
                        if (res < 1)
                                break;
                }