1 /* $Id: tmesh.c,v 1.4 2009/08/30 17:06:38 fredette Exp $ */
2
3 /* tmesh/tmesh.c - the tme shell: */
4
5 /*
6 * Copyright (c) 2003 Matt Fredette
7 * All rights reserved.
8 *
9 * Redistribution and use in source and binary forms, with or without
10 * modification, are permitted provided that the following conditions
11 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * 3. All advertising materials mentioning features or use of this software
18 * must display the following acknowledgement:
19 * This product includes software developed by Matt Fredette.
20 * 4. The name of the author may not be used to endorse or promote products
21 * derived from this software without specific prior written permission.
22 *
23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
24 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
25 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
27 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
28 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
31 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
32 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33 * POSSIBILITY OF SUCH DAMAGE.
34 */
35
36 #include <tme/common.h>
37 _TME_RCSID("$Id: tmesh.c,v 1.4 2009/08/30 17:06:38 fredette Exp $");
38
39 /* includes: */
40 #include <tme/tme.h>
41 #include <tme/tmesh.h>
42 #include <tme/hash.h>
43 #include <stdio.h>
44 #include <string.h>
45
46 /* macros: */
47
48 /* the binary log message buffer size: */
49 #define _TMESH_LOG_MESSAGE_BINARY_BUFFER_SIZE (5 * TME_LOG_MESSAGE_SIZE_MAX_BINARY)
50
51 /* the binary log message handle, size, and errno: */
52 #define _TMESH_LOG_MESSAGE_BINARY_ERRNO (0xff)
53 #if TME_LOG_MESSAGE_SIZE_MAX_BINARY & (TME_LOG_MESSAGE_SIZE_MAX_BINARY - 1)
54 #error "TME_LOG_MESSAGE_SIZE_MAX_BINARY must be a power of two"
55 #endif
56 #define _TMESH_LOG_MESSAGE_BINARY_SIZE \
57 ((TME_LOG_MESSAGE_SIZE_MAX_BINARY - 1) * (_TMESH_LOG_MESSAGE_BINARY_ERRNO + 1))
58 #define _TMESH_LOG_MESSAGE_BINARY_HANDLE \
59 (~ (tme_uint32_t) (_TMESH_LOG_MESSAGE_BINARY_SIZE + _TMESH_LOG_MESSAGE_BINARY_ERRNO))
60
61 /* types: */
62
63 /* an input buffer: */
64 struct _tmesh_input {
65 FILE *_tmesh_input_fp;
66 char _tmesh_input_buffer[1024];
67 unsigned int _tmesh_input_buffer_head;
68 unsigned int _tmesh_input_buffer_tail;
69 int _tmesh_input_buffer_eof;
70 };
71
72 /* a binary log message: */
73 struct _tmesh_log_message_binary {
74 tme_uint32_t _tmesh_log_message_binary_handle_size_errno;
75 tme_uint32_t _tmesh_log_message_binary_level;
76 };
77
78 /* globals: */
79 const char *argv0;
80
81 /* our shell instance: */
82 static void *_tmesh;
83
84 /* our current input: */
85 static struct tmesh_io *_tmesh_io;
86
87 /* nonzero if we're doing the pre-threads commands: */
88 static int _tmesh_doing_pre_threads;
89
90 /* our log and its mutex: */
91 static FILE *_tmesh_log;
92 static tme_mutex_t _tmesh_log_mutex;
93
94 /* the log mode: */
95 static unsigned int _tmesh_log_mode = TME_LOG_MODE_TEXT;
96
97 /* the next log handle number: */
98 static tme_uint32_t _tmesh_log_handle_next;
99
100 /* a format hash: */
101 static tme_hash_t _tmesh_log_hash_format;
102
103 /* this removes all consumed characters from a buffer, and shifts
104 everything else down: */
105 static void
_tmesh_remove_consumed(struct _tmesh_input * input)106 _tmesh_remove_consumed(struct _tmesh_input *input)
107 {
108 input->_tmesh_input_buffer_head -= input->_tmesh_input_buffer_tail;
109 memmove(input->_tmesh_input_buffer,
110 input->_tmesh_input_buffer
111 + input->_tmesh_input_buffer_tail,
112 input->_tmesh_input_buffer_head);
113 input->_tmesh_input_buffer_tail = 0;
114 }
115
116 /* our pre-getc function: */
117 static int
_tmesh_pre_getc(struct tmesh_io * io)118 _tmesh_pre_getc(struct tmesh_io *io)
119 {
120 struct _tmesh_input *input;
121 int c;
122
123 /* recover our input: */
124 input = io->tmesh_io_private;
125
126 /* get the next character: */
127 c = getc(input->_tmesh_input_fp);
128
129 return (c == EOF ? TMESH_C_EOF : c);
130 }
131
132 /* our getc function: */
133 static int
_tmesh_getc(struct tmesh_io * io)134 _tmesh_getc(struct tmesh_io *io)
135 {
136 struct _tmesh_input *input;
137 int c;
138
139 /* recover our input: */
140 input = io->tmesh_io_private;
141
142 /* if the buffer isn't empty yet, return the next character: */
143 if (input->_tmesh_input_buffer_tail
144 < input->_tmesh_input_buffer_head) {
145 c = input->_tmesh_input_buffer[input->_tmesh_input_buffer_tail++];
146 return (c);
147 }
148
149 /* if we're at EOF, return EOF: */
150 if (input->_tmesh_input_buffer_eof) {
151 return (TMESH_C_EOF);
152 }
153
154 /* otherwise, we must yield: */
155 return (TMESH_C_YIELD);
156 }
157
158 /* our close function: */
159 static void
_tmesh_close(struct tmesh_io * io_old,struct tmesh_io * io_new)160 _tmesh_close(struct tmesh_io *io_old, struct tmesh_io *io_new)
161 {
162 struct _tmesh_input *input;
163
164 /* recover our input: */
165 input = io_old->tmesh_io_private;
166
167 /* close the file and free the input: */
168 fclose(input->_tmesh_input_fp);
169 tme_free(input);
170
171 /* set the new, emerging input: */
172 _tmesh_io = io_new;
173 _tmesh_remove_consumed(io_new->tmesh_io_private);
174 }
175
176 /* our open function: */
177 static int
_tmesh_open(struct tmesh_io * io_new,struct tmesh_io * io_old,char ** _output)178 _tmesh_open(struct tmesh_io *io_new, struct tmesh_io *io_old, char **_output)
179 {
180 struct _tmesh_input *input;
181 int saved_errno;
182
183 /* allocate a new input: */
184 input = tme_new0(struct _tmesh_input, 1);
185
186 /* try to open the file: */
187 input->_tmesh_input_fp = fopen(io_new->tmesh_io_name, "r");
188
189 /* if the open failed: */
190 if (input->_tmesh_input_fp == NULL) {
191 saved_errno = errno;
192 tme_free(input);
193 _tmesh_doing_pre_threads = FALSE;
194 return (saved_errno);
195 }
196
197 /* set the new input: */
198 io_new->tmesh_io_private = input;
199 io_new->tmesh_io_getc = (_tmesh_doing_pre_threads
200 ? _tmesh_pre_getc
201 : _tmesh_getc);
202 io_new->tmesh_io_close = _tmesh_close;
203 io_new->tmesh_io_open = _tmesh_open;
204 _tmesh_io = io_new;
205 _tmesh_doing_pre_threads = FALSE;
206
207 return (TME_OK);
208 }
209
210 /* our log output function: */
211 static void
_tmesh_log_output(struct tme_log_handle * handle)212 _tmesh_log_output(struct tme_log_handle *handle)
213 {
214 FILE *fp;
215
216 /* lock the log mutex: */
217 tme_mutex_lock(&_tmesh_log_mutex);
218
219 /* if this is an error, report it to stderr, else it goes to the
220 log: */
221 fp = (handle->tme_log_handle_errno != TME_OK
222 ? stderr
223 : _tmesh_log);
224
225 fprintf(fp,
226 "[%s.%lu]",
227 (char *) handle->tme_log_handle_private,
228 handle->tme_log_handle_level);
229
230 if (handle->tme_log_handle_message != NULL) {
231 fprintf(fp, ": %s", handle->tme_log_handle_message);
232 tme_free(handle->tme_log_handle_message);
233 handle->tme_log_handle_message = NULL;
234 }
235
236 if (handle->tme_log_handle_errno != TME_OK) {
237 fprintf(fp, ": %s", strerror(handle->tme_log_handle_errno));
238 }
239
240 fputc('\n', fp);
241
242 /* unlock the log mutex: */
243 tme_mutex_unlock(&_tmesh_log_mutex);
244 }
245
246 /* our binary log output function: */
247 static void
_tmesh_log_output_binary(struct tme_log_handle * handle)248 _tmesh_log_output_binary(struct tme_log_handle *handle)
249 {
250 struct _tmesh_log_message_binary *binary_message;
251 tme_uint32_t handle_size_errno;
252 struct _tmesh_log_message_binary binary_message_buffer;
253 tme_uint32_t buffer_size;
254
255 /* lock the log mutex: */
256 tme_mutex_lock(&_tmesh_log_mutex);
257
258 /* make values for the binary message header: */
259 binary_message = (struct _tmesh_log_message_binary *) handle->tme_log_handle_private;
260 handle_size_errno = binary_message->_tmesh_log_message_binary_handle_size_errno;
261 TME_FIELD_MASK_DEPOSITU(handle_size_errno,
262 _TMESH_LOG_MESSAGE_BINARY_SIZE,
263 (handle->tme_log_handle_message_size
264 - sizeof(*binary_message)));
265 TME_FIELD_MASK_DEPOSITU(handle_size_errno,
266 _TMESH_LOG_MESSAGE_BINARY_ERRNO,
267 handle->tme_log_handle_errno);
268
269 /* write the binary message header: */
270 binary_message = (struct _tmesh_log_message_binary *) handle->tme_log_handle_message;
271 if (_TME_ALIGNOF_INT32_T == 1) {
272 binary_message->_tmesh_log_message_binary_handle_size_errno = handle_size_errno;
273 binary_message->_tmesh_log_message_binary_level = handle->tme_log_handle_level;
274 }
275 else {
276 binary_message_buffer._tmesh_log_message_binary_handle_size_errno = handle_size_errno;
277 binary_message_buffer._tmesh_log_message_binary_level = handle->tme_log_handle_level;
278 memcpy(binary_message,
279 &binary_message_buffer,
280 sizeof(binary_message_buffer));
281 }
282
283 /* if there isn't enough room in the buffer for a maximum-sized
284 message: */
285 buffer_size
286 = ((((char *) binary_message)
287 + handle->tme_log_handle_message_size)
288 - (char *) handle->tme_log_handle_private);
289 if (buffer_size
290 > (_TMESH_LOG_MESSAGE_BINARY_BUFFER_SIZE
291 - TME_LOG_MESSAGE_SIZE_MAX_BINARY)) {
292
293 /* write out the buffer: */
294 fwrite(handle->tme_log_handle_private,
295 1,
296 buffer_size,
297 _tmesh_log);
298
299 /* reset the buffer: */
300 handle->tme_log_handle_message = handle->tme_log_handle_private;
301 }
302
303 /* otherwise, there is enough room in the buffer for a maximum-sized
304 message: */
305 else {
306
307 /* advance the buffer: */
308 handle->tme_log_handle_message += handle->tme_log_handle_message_size;
309 }
310
311 /* reset for the next message: */
312 handle->tme_log_handle_message_size = sizeof(*binary_message);
313
314 /* unlock the log mutex: */
315 tme_mutex_unlock(&_tmesh_log_mutex);
316 }
317
318 /* our log open function: */
319 static void
_tmesh_log_open(struct tmesh_support * support,struct tme_log_handle * handle,const char * pathname,const char * module)320 _tmesh_log_open(struct tmesh_support *support,
321 struct tme_log_handle *handle,
322 const char *pathname,
323 const char *module)
324 {
325 struct _tmesh_log_message_binary *binary_message;
326
327 /* lock the log mutex: */
328 tme_mutex_lock(&_tmesh_log_mutex);
329
330 handle->tme_log_handle_level_max = 0;
331 handle->tme_log_handle_mode = _tmesh_log_mode;
332
333 /* if the log is binary: */
334 if (handle->tme_log_handle_mode == TME_LOG_MODE_BINARY) {
335
336 /* allocate the binary buffer: */
337 handle->tme_log_handle_private = tme_malloc(_TMESH_LOG_MESSAGE_BINARY_BUFFER_SIZE);
338
339 /* allocate the handle number and make the first message
340 structure: */
341 binary_message = (struct _tmesh_log_message_binary *) handle->tme_log_handle_private;
342 memset (binary_message, 0, sizeof(*binary_message));
343 TME_FIELD_MASK_DEPOSITU(binary_message->_tmesh_log_message_binary_handle_size_errno,
344 _TMESH_LOG_MESSAGE_BINARY_HANDLE,
345 _tmesh_log_handle_next);
346 _tmesh_log_handle_next++;
347 handle->tme_log_handle_message = (char *) binary_message;
348 handle->tme_log_handle_message_size = sizeof(*binary_message);
349 assert (handle->tme_log_handle_message_size < TME_LOG_MESSAGE_SIZE_MAX_BINARY);
350
351 /* write a dummy first message for the handle that is only the
352 pathname: */
353 TME_FIELD_MASK_DEPOSITU(binary_message->_tmesh_log_message_binary_handle_size_errno,
354 _TMESH_LOG_MESSAGE_BINARY_SIZE,
355 strlen(pathname) + 1);
356 fwrite(binary_message,
357 sizeof(*binary_message),
358 1,
359 _tmesh_log);
360 fwrite(pathname,
361 1,
362 (strlen(pathname) + 1),
363 _tmesh_log);
364
365 /* set the output function: */
366 handle->tme_log_handle_output = _tmesh_log_output_binary;
367
368 /* set the format hash: */
369 handle->tme_log_handle_hash_format = _tmesh_log_hash_format;
370 }
371
372 /* otherwise, the log is text: */
373 else {
374
375 /* set the output function: */
376 handle->tme_log_handle_message = NULL;
377 handle->tme_log_handle_output = _tmesh_log_output;
378 handle->tme_log_handle_private = tme_strdup(pathname);
379 }
380
381 /* unlock the log mutex: */
382 tme_mutex_unlock(&_tmesh_log_mutex);
383 }
384
385 /* our log close function: */
386 static void
_tmesh_log_close(struct tmesh_support * support,struct tme_log_handle * handle)387 _tmesh_log_close(struct tmesh_support *support,
388 struct tme_log_handle *handle)
389 {
390 tme_free(handle->tme_log_handle_private);
391 }
392
393 /* our thread: */
394 static void
_tmesh_thread(void * junk)395 _tmesh_thread(void *junk)
396 {
397 int yield, rc;
398 struct tmesh_io *io;
399 struct _tmesh_input *input;
400 char *output;
401 unsigned int consumed;
402
403 /* loop while we have a current input buffer: */
404 for (; (io = _tmesh_io) != NULL;) {
405 input = io->tmesh_io_private;
406
407 /* remove all consumed characters: */
408 _tmesh_remove_consumed(input);
409
410 /* if the current input buffer is full, a command is too long: */
411 if (input->_tmesh_input_buffer_head
412 == sizeof(input->_tmesh_input_buffer)) {
413 fprintf(stderr, "%s: command too long\n", argv0);
414 input->_tmesh_input_buffer_head = 0;
415 }
416
417 /* try to read more input: */
418 rc = tme_thread_read_yield(fileno(input->_tmesh_input_fp),
419 input->_tmesh_input_buffer
420 + input->_tmesh_input_buffer_head,
421 sizeof(input->_tmesh_input_buffer)
422 - input->_tmesh_input_buffer_head);
423
424 /* if the read failed: */
425 if (rc < 0) {
426 fprintf(stderr, "%s: %s\n",
427 io->tmesh_io_name,
428 strerror(errno));
429 continue;
430 }
431
432 /* add characters in our current input buffer, or set EOF: */
433 if (rc > 0) {
434 input->_tmesh_input_buffer_head += rc;
435 }
436 else {
437 input->_tmesh_input_buffer_eof = TRUE;
438 }
439
440 /* run commands until we have to yield: */
441 for (;;) {
442
443 /* all characters already read have been consumed: */
444 consumed = input->_tmesh_input_buffer_tail;
445
446 /* run a command: */
447 rc = tmesh_eval(_tmesh, &output, &yield);
448
449 /* if we're yielding: */
450 if (yield) {
451
452 /* if the current io has not changed, mark how many
453 characters were consumed by successful commands: */
454 if (io == _tmesh_io) {
455 input->_tmesh_input_buffer_tail = consumed;
456 }
457
458 break;
459 }
460
461 /* this command may have changed the current io, so reload: */
462 io = _tmesh_io;
463 input = io->tmesh_io_private;
464
465 /* display this command's output: */
466 if (rc == TME_OK) {
467 if (output != NULL
468 && *output != '\0') {
469 printf("%s\n", output);
470 }
471 }
472 else {
473 fprintf(stderr, "%s:%lu: ",
474 io->tmesh_io_name,
475 io->tmesh_io_input_line);
476 if (output != NULL
477 && *output != '\0') {
478 fprintf(stderr, "%s: ", output);
479 }
480 fprintf(stderr, "%s\n", strerror(rc));
481 }
482 if (output != NULL) {
483 tme_free(output);
484 }
485
486 /* put up the next prompt: */
487 if (isatty(fileno(input->_tmesh_input_fp))
488 && isatty(fileno(stdout))) {
489 printf("%s> ", argv0);
490 fflush(stdout);
491 }
492 }
493 }
494 }
495
496 int
main(int argc,char ** argv)497 main(int argc, char **argv)
498 {
499 int usage;
500 const char *opt;
501 int arg_i;
502 const char *pre_threads_filename;
503 const char *log_filename;
504 int interactive;
505 struct tmesh_io io;
506 struct tmesh_support support;
507 struct _tmesh_input *input_stdin;
508 char *output;
509 int yield, rc;
510
511 /* check our command line: */
512 usage = FALSE;
513 pre_threads_filename = NULL;
514 log_filename = "/dev/null";
515 interactive = TRUE;
516 if ((argv0 = strrchr(argv[0], '/')) == NULL) argv0 = argv[0]; else argv0++;
517 for (arg_i = 1;
518 (arg_i < argc
519 && *argv[arg_i] == '-');
520 arg_i++) {
521 opt = argv[arg_i];
522 if (!strcmp(opt, "--log")) {
523 if (++arg_i < argc) {
524 log_filename = argv[arg_i];
525 }
526 else {
527 usage = TRUE;
528 break;
529 }
530 }
531 else if (!strcmp(opt, "--log-mode")) {
532 ++arg_i;
533 if (arg_i >= argc
534 || strcmp(argv[arg_i], "binary")) {
535 usage = TRUE;
536 break;
537 }
538 _tmesh_log_mode = TME_LOG_MODE_BINARY;
539 if (_tmesh_log_hash_format == NULL) {
540 _tmesh_log_hash_format = tme_hash_new(tme_direct_hash, tme_direct_compare, TME_HASH_DATA_NULL);
541 }
542 }
543 else if (!strcmp(opt, "-c")
544 || !strcmp(opt, "--noninteractive")) {
545 interactive = FALSE;
546 }
547 else {
548 if (strcmp(opt, "-h")
549 && strcmp(opt, "--help")
550 && strcmp(opt, "-h")) {
551 fprintf(stderr, "%s: unknown option %s\n",
552 argv0, opt);
553 }
554 usage = TRUE;
555 break;
556 }
557 }
558 if (arg_i < argc) {
559 pre_threads_filename = argv[arg_i++];
560 }
561 else {
562 usage = TRUE;
563 }
564 if (usage) {
565 fprintf(stderr, "\
566 usage: %s [OPTIONS] INITIAL-CONFIG\n\
567 where OPTIONS are:\n\
568 --log LOGFILE log to LOGFILE\n\
569 -c, --noninteractive read no commands from standard input\n\
570 ",
571 argv0);
572 exit(1);
573 }
574
575 if (!strcmp(log_filename, "-")) {
576 _tmesh_log = stdout;
577 }
578 else {
579 _tmesh_log = fopen(log_filename, "a");
580 if (_tmesh_log == NULL) {
581 perror(log_filename);
582 exit(1);
583 }
584 }
585
586 /* initialize libtme: */
587 (void) tme_init();
588
589 /* initialize libtmesh: */
590 (void) tmesh_init();
591
592 /* create our stdin input buffer, and stuff it with the command to
593 source the pre-threads commands: */
594 input_stdin = tme_new0(struct _tmesh_input, 1);
595 input_stdin->_tmesh_input_fp = stdin;
596 snprintf(input_stdin->_tmesh_input_buffer,
597 sizeof(input_stdin->_tmesh_input_buffer) - 1,
598 "source %s\n",
599 pre_threads_filename);
600 input_stdin->_tmesh_input_buffer[sizeof(input_stdin->_tmesh_input_buffer) - 1] = '\0';
601 input_stdin->_tmesh_input_buffer_head = strlen(input_stdin->_tmesh_input_buffer);
602
603 /* create our stdin io: */
604 io.tmesh_io_name = "*stdin*";
605 io.tmesh_io_private = input_stdin;
606 io.tmesh_io_input_line = 0;
607 io.tmesh_io_getc = _tmesh_getc;
608 io.tmesh_io_close = _tmesh_close;
609 io.tmesh_io_open = _tmesh_open;
610 _tmesh_io = &io;
611
612 /* the next open we do will be for the pre-threads commands: */
613 _tmesh_doing_pre_threads = TRUE;
614
615 /* create our support: */
616 tme_mutex_init(&_tmesh_log_mutex);
617 support.tmesh_support_log_open = _tmesh_log_open;
618 support.tmesh_support_log_close = _tmesh_log_close;
619
620 /* create our shell: */
621 _tmesh = tmesh_new(&support, &io);
622
623 /* run commands until we get a yield: */
624 for (;;) {
625 rc = tmesh_eval(_tmesh, &output, &yield);
626 if (yield) {
627 break;
628 }
629 if (rc == TME_OK) {
630 if (output != NULL
631 && *output != '\0') {
632 printf("%s\n", output);
633 }
634 }
635 else {
636 fprintf(stderr, "%s:%lu: ",
637 _tmesh_io->tmesh_io_name,
638 _tmesh_io->tmesh_io_input_line);
639 if (output != NULL
640 && *output != '\0') {
641 fprintf(stderr, "%s: ", output);
642 }
643 fprintf(stderr, "%s\n", strerror(rc));
644 }
645 if (output != NULL) {
646 tme_free(output);
647 }
648 }
649
650 /* if we're interactive: */
651 if (interactive) {
652
653 /* put up our first prompt: */
654 if (isatty(fileno(stdin))
655 && isatty(fileno(stdout))) {
656 printf("%s> ", argv0);
657 fflush(stdout);
658 }
659
660 /* create our thread: */
661 tme_thread_create((tme_thread_t) _tmesh_thread, NULL);
662 }
663
664 /* run the threads: */
665 tme_threads_run();
666
667 /* done: */
668 exit(0);
669 }
670