root/src/noit_console.c

Revision c730adec9b98429ce1368131ab29151fde18d34b, 16.3 kB (checked in by Theo Schlossnagle <jesus@omniti.com>, 6 months ago)

Add a "memory" logger type and expose it via console and rest.
This logger is allocation free on the write path. Logging was
refactored to pass the timeval all the way into the write(v)
operations; it is ignored by previous loggers.

  • Property mode set to 100644
Line 
1 /*
2  * Copyright (c) 2007, OmniTI Computer Consulting, Inc.
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are
7  * met:
8  *
9  *     * Redistributions of source code must retain the above copyright
10  *       notice, this list of conditions and the following disclaimer.
11  *     * Redistributions in binary form must reproduce the above
12  *       copyright notice, this list of conditions and the following
13  *       disclaimer in the documentation and/or other materials provided
14  *       with the distribution.
15  *     * Neither the name OmniTI Computer Consulting, Inc. nor the names
16  *       of its contributors may be used to endorse or promote products
17  *       derived from this software without specific prior written
18  *       permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 #include "noit_defines.h"
34
35 #include <assert.h>
36 #include <stdio.h>
37 #include <unistd.h>
38 #ifdef HAVE_ALLOCA_H
39 #include <alloca.h>
40 #endif
41 #include <errno.h>
42 #ifdef HAVE_SYS_IOCTL_H
43 #include <sys/ioctl.h>
44 #endif
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <fcntl.h>
48 #ifdef HAVE_STROPTS_H
49 #include <stropts.h>
50 #endif
51 #ifdef HAVE_SYS_STREAM_H
52 #include <sys/stream.h>
53 #endif
54 #ifdef HAVE_TERMIOS_H
55 #include <termios.h>
56 #endif
57 #ifdef HAVE_PTY_H
58 #include <pty.h>
59 #endif
60 #ifdef HAVE_UTIL_H
61 #include <util.h>
62 #endif
63 #ifdef HAVE_BSD_LIBUTIL_H
64 #include <bsd/libutil.h>
65 #elif HAVE_LIBUTIL_H
66 #include <libutil.h>
67 #endif
68 #include <arpa/telnet.h>
69 #include <signal.h>
70
71 #include "eventer/eventer.h"
72 #include "utils/noit_log.h"
73 #include "noit_listener.h"
74 #include "noit_console.h"
75 #include "noit_tokenizer.h"
76
77 #include "noitedit/sys.h"
78 #include "noitedit/el.h"
79 #include "noitedit/fcns.h"
80 #include "noitedit/map.h"
81
82 static void
83 nc_telnet_cooker(noit_console_closure_t ncct) {
84   char *tmpbuf, *p, *n;
85   int r;
86
87   tmpbuf = ncct->outbuf;
88   if(ncct->outbuf_len == 0) return;
89
90   p = ncct->outbuf + ncct->outbuf_completed;
91   r = ncct->outbuf_len - ncct->outbuf_completed;
92   n = memchr(p, '\n', r);
93   /* No '\n'? Nothin' to do */
94   if(!n) {
95     ncct->outbuf_cooked = ncct->outbuf_len;
96     return;
97   }
98
99   /* Forget the outbuf -- it is now tmpbuf */
100   ncct->outbuf = NULL;
101   ncct->outbuf_allocd = 0;
102   ncct->outbuf_len = 0;
103   ncct->outbuf_completed = 0;
104   ncct->outbuf_cooked = 0;
105   do {
106     nc_write(ncct, p, n-p);   r -= n-p;
107     if(n == tmpbuf || *(n-1) != '\r')
108       nc_write(ncct, "\r", 1);
109     p = n;
110     n = memchr(p+1, '\n', r-1);
111   } while(n);
112   nc_write(ncct, p, r);
113   ncct->outbuf_cooked = ncct->outbuf_len;
114   free(tmpbuf);
115 }
116 int
117 nc_printf(noit_console_closure_t ncct, const char *fmt, ...) {
118   int len;
119   va_list arg;
120   va_start(arg, fmt);
121   len = nc_vprintf(ncct, fmt, arg);
122   va_end(arg);
123   return len;
124 }
125 int
126 nc_vprintf(noit_console_closure_t ncct, const char *fmt, va_list arg) {
127 #ifdef va_copy
128   va_list copy;
129 #endif
130   int lenwanted;
131  
132   if(!ncct->outbuf_allocd) {
133     ncct->outbuf = malloc(4096);
134     if(!ncct->outbuf) return 0;
135     ncct->outbuf_allocd = 4096;
136   }
137   while(1) {
138     char *newbuf;
139 #ifdef va_copy
140     va_copy(copy, arg);
141     lenwanted = vsnprintf(ncct->outbuf + ncct->outbuf_len,
142                           ncct->outbuf_allocd - ncct->outbuf_len,
143                           fmt, copy);
144     va_end(copy);
145 #else
146     lenwanted = vsnprintf(ncct->outbuf + ncct->outbuf_len,
147                           ncct->outbuf_allocd - ncct->outbuf_len,
148                           fmt, arg);
149 #endif
150     if(ncct->outbuf_len + lenwanted < ncct->outbuf_allocd) {
151       /* All went well, things are as we want them. */
152       ncct->outbuf_len += lenwanted;
153       return lenwanted;
154     }
155
156     /* We need to enlarge the buffer */
157     lenwanted += ncct->outbuf_len;
158     lenwanted /= 4096;
159     lenwanted += 1;
160     lenwanted *= 4096;
161     newbuf = realloc(ncct->outbuf, lenwanted);
162     if(!newbuf) {
163       return 0;
164     }
165     ncct->outbuf = newbuf;
166     ncct->outbuf_allocd = lenwanted;
167   }
168   /* NOTREACHED */
169 }
170 int
171 nc_write(noit_console_closure_t ncct, const void *buf, int len) {
172   if(len == 0) return 0;
173   if(!ncct->outbuf_allocd) {
174     ncct->outbuf = malloc(len);
175     if(!ncct->outbuf) return 0;
176     ncct->outbuf_allocd = len;
177   }
178   else if(ncct->outbuf_allocd < ncct->outbuf_len + len) {
179     char *newbuf;
180     newbuf = realloc(ncct->outbuf, ncct->outbuf_len + len);
181     if(!newbuf) return 0;
182     ncct->outbuf = newbuf;
183   }
184   memcpy(ncct->outbuf + ncct->outbuf_len, buf, len);
185   ncct->outbuf_len += len;
186   return len;
187 }
188
189 static void
190 noit_console_userdata_free(void *data) {
191   noit_console_userdata_t *userdata = data;
192   if(userdata) {
193     if(userdata->name) free(userdata->name);
194     if(userdata->freefunc)
195       userdata->freefunc(userdata->data);
196     free(userdata);
197   }
198 }
199 void
200 noit_console_closure_free(void *vncct) {
201   noit_console_closure_t ncct = (noit_console_closure_t) vncct;
202   noit_log_stream_t lf;
203   noitL(noit_debug, "ncct free(%p)\n", (void *)ncct);
204   if(ncct->el) el_end(ncct->el);
205   if(ncct->hist) {
206     history_end(ncct->hist);
207     noitL(noit_debug, "ncct free->hist(%p)\n", (void *)ncct->hist);
208     free(ncct->hist);
209   }
210   if(ncct->pty_master >= 0) close(ncct->pty_master);
211   if(ncct->pty_slave >= 0) close(ncct->pty_slave);
212   if(ncct->outbuf) free(ncct->outbuf);
213   if(ncct->telnet) noit_console_telnet_free(ncct->telnet);
214   noit_hash_destroy(&ncct->userdata, NULL, noit_console_userdata_free);
215   while(ncct->state_stack) {
216     noit_console_state_stack_t *tmp;
217     tmp = ncct->state_stack;
218     ncct->state_stack = tmp->last;
219     if(tmp->name) free(tmp->name);
220     free(tmp);
221   }
222   lf = noit_log_stream_find(ncct->feed_path);
223   noit_log_stream_remove(ncct->feed_path);
224   if(lf) {
225     noit_log_stream_free(lf);
226   }
227   free(ncct);
228 }
229
230 noit_console_closure_t
231 noit_console_closure_alloc() {
232   noit_console_closure_t new_ncct;
233   new_ncct = calloc(1, sizeof(*new_ncct));
234   noit_hash_init(&new_ncct->userdata);
235   noit_console_state_push_state(new_ncct, noit_console_state_initial());
236   new_ncct->pty_master = -1;
237   new_ncct->pty_slave = -1;
238   return new_ncct;
239 }
240
241 void
242 noit_console_userdata_set(struct __noit_console_closure *ncct,
243                           const char *name, void *data,
244                           state_userdata_free_func_t freefunc) {
245   noit_console_userdata_t *item;
246   item = calloc(1, sizeof(*item));
247   item->name = strdup(name);
248   item->data = data;
249   item->freefunc = freefunc;
250   noit_hash_replace(&ncct->userdata, item->name, strlen(item->name),
251                     item, NULL, noit_console_userdata_free);
252 }
253  
254 void *
255 noit_console_userdata_get(struct __noit_console_closure *ncct,
256                           const char *name) {
257   void *vitem;
258   if(noit_hash_retrieve(&ncct->userdata, name, strlen(name),
259                         &vitem))
260     return ((noit_console_userdata_t *)vitem)->data;
261   return NULL;
262 }
263
264
265 int
266 noit_console_continue_sending(noit_console_closure_t ncct,
267                               int *mask) {
268   int len;
269   eventer_t e = ncct->e;
270   if(!ncct->outbuf_len) return 0;
271   if(ncct->output_cooker) ncct->output_cooker(ncct);
272   while(ncct->outbuf_len > ncct->outbuf_completed) {
273     len = e->opset->write(e->fd, ncct->outbuf + ncct->outbuf_completed,
274                           ncct->outbuf_len - ncct->outbuf_completed,
275                           mask, e);
276     if(len < 0) {
277       if(errno == EAGAIN) return -1;
278       /* Do something else here? */
279       return -1;
280     }
281     ncct->outbuf_completed += len;
282   }
283   len = ncct->outbuf_len;
284   free(ncct->outbuf);
285   ncct->outbuf = NULL;
286   ncct->outbuf_allocd = ncct->outbuf_len =
287     ncct->outbuf_completed = ncct->outbuf_cooked = 0;
288   return len;
289 }
290
291 void
292 noit_console_dispatch(eventer_t e, const char *buffer,
293                       noit_console_closure_t ncct) {
294   char **cmds;
295   HistEvent ev;
296   int i, cnt = 32;
297
298   cmds = alloca(32 * sizeof(*cmds));
299   i = noit_tokenize(buffer, cmds, &cnt);
300
301   /* < 0 is an error, that's fine.  We want it in the history to "fix" */
302   /* > 0 means we had arguments, so let's put it in the history */
303   /* 0 means nothing -- and that isn't worthy of history inclusion */
304   if(i) history(ncct->hist, &ev, H_ENTER, buffer);
305
306   if(i>cnt) nc_printf(ncct, "Command length too long.\n");
307   else if(i<0) nc_printf(ncct, "Error at offset: %d\n", 0-i);
308   else noit_console_state_do(ncct, cnt, cmds);
309   while(cnt>0) free(cmds[--cnt]);
310 }
311
312 void
313 noit_console_motd(eventer_t e, acceptor_closure_t *ac,
314                   noit_console_closure_t ncct) {
315   int ssl;
316   ssl = eventer_get_eventer_ssl_ctx(e) ? 1 : 0;
317   nc_printf(ncct, "noitd%s: %s\n",
318             ssl ? "(secure)" : "",
319             ac->remote_cn ? ac->remote_cn : "(no auth)");
320 }
321
322 int
323 allocate_pty(int *master, int *slave) {
324 #if defined(HAVE_OPENPTY) || (defined(HAVE_DECL_OPENPTY) && HAVE_DECL_OPENPTY != 0)
325   if(openpty(master, slave, NULL, NULL, NULL)) return -1;
326 #else
327   /* STREAMS... sigh */
328   char   *slavename;
329   extern char *ptsname();
330
331   *master = open("/dev/ptmx", O_RDWR);  /* open master */
332   if(*master < 0) return -1;
333   grantpt(*master);                     /* change permission of   slave */
334   unlockpt(*master);                    /* unlock slave */
335   slavename = ptsname(*master);         /* get name of slave */
336   *slave = open(slavename, O_RDWR);    /* open slave */
337   if(*slave < 0) {
338     close(*master);
339     *master = -1;
340     return -1;
341   }
342   /* This is a bit backwards as we using the PTY backwards.
343    * We want to make the master a tty instead of the slave... odd, I know.
344    */
345   ioctl(*master, I_PUSH, "ptem");       /* push ptem */
346   ioctl(*master, I_PUSH, "ldterm");     /* push ldterm*/
347 #endif
348   if(eventer_set_fd_nonblocking(*master)) return -1;
349   noitL(noit_debug, "allocate_pty -> %d,%d\n", *master, *slave);
350   return 0;
351 }
352
353 int
354 noit_console_handler(eventer_t e, int mask, void *closure,
355                      struct timeval *now) {
356   int newmask = EVENTER_READ | EVENTER_EXCEPTION;
357   int keep_going;
358   acceptor_closure_t *ac = closure;
359   noit_console_closure_t ncct = ac->service_ctx;
360
361   if(mask & EVENTER_EXCEPTION || (ncct && ncct->wants_shutdown)) {
362 socket_error:
363     /* Exceptions cause us to simply snip the connection */
364
365     /* This removes the log feed which is important to do before calling close */
366     eventer_remove_fd(e->fd);
367     acceptor_closure_free(ac);
368     e->opset->close(e->fd, &newmask, e);
369     return 0;
370   }
371
372   if(!ac->service_ctx) {
373     ncct = ac->service_ctx = noit_console_closure_alloc();
374     noitL(noit_debug, "ncct alloc() -> %p\n", (void *)ncct);
375     ac->service_ctx_free = noit_console_closure_free;
376   }
377   if(!ncct->initialized) {
378     ncct->e = e;
379     if(allocate_pty(&ncct->pty_master, &ncct->pty_slave)) {
380       nc_printf(ncct, "Failed to open pty: %s\n", strerror(errno));
381       ncct->wants_shutdown = 1;
382       goto socket_error;
383     }
384     else {
385       int i;
386       const char *line_protocol;
387       HistEvent ev;
388
389       ncct->hist = history_init();
390       history(ncct->hist, &ev, H_SETSIZE, 500);
391       ncct->el = el_init("noitd", ncct->pty_master, NULL,
392                          e->fd, e, e->fd, e);
393       if(!ncct->el) goto socket_error;
394       if(el_set(ncct->el, EL_USERDATA, ncct)) {
395         noitL(noit_error, "Cannot set userdata on noitedit session\n");
396         goto socket_error;
397       }
398       if(el_set(ncct->el, EL_EDITOR, "emacs"))
399         noitL(noit_error, "Cannot set emacs mode on console\n");
400       if(el_set(ncct->el, EL_HIST, history, ncct->hist))
401         noitL(noit_error, "Cannot set history on console\n");
402       el_set(ncct->el, EL_ADDFN, "noit_complete",
403              "auto completion functions for noit", noit_edit_complete);
404       el_set(ncct->el, EL_BIND, "^I", "noit_complete", NULL);
405       for(i=EL_NUM_FCNS; i < ncct->el->el_map.nfunc; i++) {
406         if(ncct->el->el_map.func[i] == noit_edit_complete) {
407           ncct->noit_edit_complete_cmdnum = i;
408           break;
409         }
410       }
411
412       if(!noit_hash_retr_str(ac->config,
413                              "line_protocol", strlen("line_protocol"),
414                              &line_protocol)) {
415         line_protocol = NULL;
416       }
417       if(line_protocol && !strcasecmp(line_protocol, "telnet")) {
418         ncct->telnet = noit_console_telnet_alloc(ncct);
419         ncct->output_cooker = nc_telnet_cooker;
420       }
421       noit_console_state_init(ncct);
422     }
423     snprintf(ncct->feed_path, sizeof(ncct->feed_path), "console/%d", e->fd);
424     noit_log_stream_new(ncct->feed_path, "noit_console", ncct->feed_path,
425                         ncct, NULL);
426     noit_console_motd(e, ac, ncct);
427     ncct->initialized = 1;
428   }
429
430   /* If we still have data to send back to the client, this will take
431    * care of that
432    */
433   if(noit_console_continue_sending(ncct, &newmask) < 0) {
434     if(ncct->wants_shutdown || errno != EAGAIN) goto socket_error;
435     return newmask | EVENTER_EXCEPTION;
436   }
437
438   for(keep_going=1 ; keep_going ; ) {
439     int len, plen;
440     char sbuf[4096];
441     const char *buffer;
442
443     keep_going = 0;
444
445     buffer = el_gets(ncct->el, &plen);
446     if(!el_eagain(ncct->el)) {
447       if(!buffer) {
448         buffer = "exit";
449         plen = 4;
450         nc_write(ncct, "\n", 1);
451       }
452       keep_going++;
453     }
454
455     len = e->opset->read(e->fd, sbuf, sizeof(sbuf)-1, &newmask, e);
456     if(len == 0 || (len < 0 && errno != EAGAIN)) {
457       eventer_remove_fd(e->fd);
458       acceptor_closure_free(ac);
459       e->opset->close(e->fd, &newmask, e);
460       return 0;
461     }
462     if(len > 0) {
463       keep_going++;
464       sbuf[len] = '\0';
465       if(ncct->telnet) {
466         noit_console_telnet_telrcv(ncct, sbuf, len);
467         ptyflush(ncct);
468       }
469       else {
470         int written;
471         written = write(ncct->pty_slave, sbuf, len);
472         if(written <= 0) goto socket_error;
473         assert(written == len);
474       }
475     }
476     if(buffer) {
477       char *cmd_buffer;
478       cmd_buffer = malloc(plen+1);
479       memcpy(cmd_buffer, buffer, plen);
480       /* chomp */
481       cmd_buffer[plen] = '\0';
482       if(cmd_buffer[plen-1] == '\n') cmd_buffer[plen-1] = '\0';
483       noitL(noit_debug, "IN[%d]: '%s'\n", plen, cmd_buffer);
484       noit_console_dispatch(e, cmd_buffer, ncct);
485       free(cmd_buffer);
486     }
487     if(noit_console_continue_sending(ncct, &newmask) == -1) {
488       if(ncct->wants_shutdown || errno != EAGAIN) goto socket_error;
489       return newmask | EVENTER_EXCEPTION;
490     }
491     if(ncct->wants_shutdown) goto socket_error;
492   }
493   return newmask | EVENTER_EXCEPTION;
494 }
495
496 static int
497 noit_console_logio_open(noit_log_stream_t ls) {
498   return 0;
499 }
500 static int
501 noit_console_logio_reopen(noit_log_stream_t ls) {
502   /* no op */
503   return 0;
504 }
505 static int
506 noit_console_logio_write(noit_log_stream_t ls, const struct timeval *whence,
507                          const void *buf, size_t len) {
508   noit_console_closure_t ncct;
509   ncct = noit_log_stream_get_ctx(ls);
510   int rv, rlen, mask;
511   (void)whence;
512   if(!ncct) return 0;
513   rlen = nc_write(ncct, buf, len);
514   while((rv = noit_console_continue_sending(ncct, &mask)) == -1 && errno == EINTR);
515   if(rv == -1 && errno == EAGAIN) {
516     eventer_update(ncct->e, mask | EVENTER_EXCEPTION);
517   }
518   return rlen;
519 }
520 static int
521 noit_console_logio_close(noit_log_stream_t ls) {
522   noit_console_closure_t ncct;
523   ncct = noit_log_stream_get_ctx(ls);
524   if(!ncct) return 0;
525   ncct->e = NULL;
526   noit_log_stream_set_ctx(ls, NULL);
527   return 0;
528 }
529 static logops_t noit_console_logio_ops = {
530   noit_console_logio_open,
531   noit_console_logio_reopen,
532   noit_console_logio_write,
533   NULL,
534   noit_console_logio_close,
535   NULL,
536   NULL
537 };
538
539 int
540 noit_console_write_xml(void *vncct, const char *buffer, int len) {
541   noit_console_closure_t ncct = vncct;
542   return nc_write(ncct, buffer, len);
543 }
544
545 int
546 noit_console_close_xml(void *vncct) {
547   return 0;
548 }
549
550 void
551 noit_console_init(const char *progname) {
552   if(progname) {
553     char buff[32];
554     snprintf(buff, sizeof(buff), "%s# ", progname);
555     noit_console_set_default_prompt(buff);
556   }
557   el_multi_init();
558   signal(SIGTTOU, SIG_IGN);
559   noit_register_logops("noit_console", &noit_console_logio_ops);
560   eventer_name_callback("noit_console", noit_console_handler);
561 }
562
Note: See TracBrowser for help on using the browser.