blob: d68ca05238ac06ee55d1dc923a0a3b1fae170c71 [file] [log] [blame]
/* $Id: tmesh.c,v 1.4 2009/08/30 17:06:38 fredette Exp $ */
/* tmesh/tmesh.c - the tme shell: */
/*
* Copyright (c) 2003 Matt Fredette
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by Matt Fredette.
* 4. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <tme/common.h>
_TME_RCSID("$Id: tmesh.c,v 1.4 2009/08/30 17:06:38 fredette Exp $");
/* includes: */
#include <tme/tme.h>
#include <tme/tmesh.h>
#include <tme/hash.h>
#include <stdio.h>
#include <string.h>
/* macros: */
/* the binary log message buffer size: */
#define _TMESH_LOG_MESSAGE_BINARY_BUFFER_SIZE (5 * TME_LOG_MESSAGE_SIZE_MAX_BINARY)
/* the binary log message handle, size, and errno: */
#define _TMESH_LOG_MESSAGE_BINARY_ERRNO (0xff)
#if TME_LOG_MESSAGE_SIZE_MAX_BINARY & (TME_LOG_MESSAGE_SIZE_MAX_BINARY - 1)
#error "TME_LOG_MESSAGE_SIZE_MAX_BINARY must be a power of two"
#endif
#define _TMESH_LOG_MESSAGE_BINARY_SIZE \
((TME_LOG_MESSAGE_SIZE_MAX_BINARY - 1) * (_TMESH_LOG_MESSAGE_BINARY_ERRNO + 1))
#define _TMESH_LOG_MESSAGE_BINARY_HANDLE \
(~ (tme_uint32_t) (_TMESH_LOG_MESSAGE_BINARY_SIZE + _TMESH_LOG_MESSAGE_BINARY_ERRNO))
/* types: */
/* an input buffer: */
struct _tmesh_input {
FILE *_tmesh_input_fp;
char _tmesh_input_buffer[1024];
unsigned int _tmesh_input_buffer_head;
unsigned int _tmesh_input_buffer_tail;
int _tmesh_input_buffer_eof;
};
/* a binary log message: */
struct _tmesh_log_message_binary {
tme_uint32_t _tmesh_log_message_binary_handle_size_errno;
tme_uint32_t _tmesh_log_message_binary_level;
};
/* globals: */
const char *argv0;
/* our shell instance: */
static void *_tmesh;
/* our current input: */
static struct tmesh_io *_tmesh_io;
/* nonzero if we're doing the pre-threads commands: */
static int _tmesh_doing_pre_threads;
/* our log and its mutex: */
static FILE *_tmesh_log;
static tme_mutex_t _tmesh_log_mutex;
/* the log mode: */
static unsigned int _tmesh_log_mode = TME_LOG_MODE_TEXT;
/* the next log handle number: */
static tme_uint32_t _tmesh_log_handle_next;
/* a format hash: */
static tme_hash_t _tmesh_log_hash_format;
/* this removes all consumed characters from a buffer, and shifts
everything else down: */
static void
_tmesh_remove_consumed(struct _tmesh_input *input)
{
input->_tmesh_input_buffer_head -= input->_tmesh_input_buffer_tail;
memmove(input->_tmesh_input_buffer,
input->_tmesh_input_buffer
+ input->_tmesh_input_buffer_tail,
input->_tmesh_input_buffer_head);
input->_tmesh_input_buffer_tail = 0;
}
/* our pre-getc function: */
static int
_tmesh_pre_getc(struct tmesh_io *io)
{
struct _tmesh_input *input;
int c;
/* recover our input: */
input = io->tmesh_io_private;
/* get the next character: */
c = getc(input->_tmesh_input_fp);
return (c == EOF ? TMESH_C_EOF : c);
}
/* our getc function: */
static int
_tmesh_getc(struct tmesh_io *io)
{
struct _tmesh_input *input;
int c;
/* recover our input: */
input = io->tmesh_io_private;
/* if the buffer isn't empty yet, return the next character: */
if (input->_tmesh_input_buffer_tail
< input->_tmesh_input_buffer_head) {
c = input->_tmesh_input_buffer[input->_tmesh_input_buffer_tail++];
return (c);
}
/* if we're at EOF, return EOF: */
if (input->_tmesh_input_buffer_eof) {
return (TMESH_C_EOF);
}
/* otherwise, we must yield: */
return (TMESH_C_YIELD);
}
/* our close function: */
static void
_tmesh_close(struct tmesh_io *io_old, struct tmesh_io *io_new)
{
struct _tmesh_input *input;
/* recover our input: */
input = io_old->tmesh_io_private;
/* close the file and free the input: */
fclose(input->_tmesh_input_fp);
tme_free(input);
/* set the new, emerging input: */
_tmesh_io = io_new;
_tmesh_remove_consumed(io_new->tmesh_io_private);
}
/* our open function: */
static int
_tmesh_open(struct tmesh_io *io_new, struct tmesh_io *io_old, char **_output)
{
struct _tmesh_input *input;
int saved_errno;
/* allocate a new input: */
input = tme_new0(struct _tmesh_input, 1);
/* try to open the file: */
input->_tmesh_input_fp = fopen(io_new->tmesh_io_name, "r");
/* if the open failed: */
if (input->_tmesh_input_fp == NULL) {
saved_errno = errno;
tme_free(input);
_tmesh_doing_pre_threads = FALSE;
return (saved_errno);
}
/* set the new input: */
io_new->tmesh_io_private = input;
io_new->tmesh_io_getc = (_tmesh_doing_pre_threads
? _tmesh_pre_getc
: _tmesh_getc);
io_new->tmesh_io_close = _tmesh_close;
io_new->tmesh_io_open = _tmesh_open;
_tmesh_io = io_new;
_tmesh_doing_pre_threads = FALSE;
return (TME_OK);
}
/* our log output function: */
static void
_tmesh_log_output(struct tme_log_handle *handle)
{
FILE *fp;
/* lock the log mutex: */
tme_mutex_lock(&_tmesh_log_mutex);
/* if this is an error, report it to stderr, else it goes to the
log: */
fp = (handle->tme_log_handle_errno != TME_OK
? stderr
: _tmesh_log);
fprintf(fp,
"[%s.%lu]",
(char *) handle->tme_log_handle_private,
handle->tme_log_handle_level);
if (handle->tme_log_handle_message != NULL) {
fprintf(fp, ": %s", handle->tme_log_handle_message);
tme_free(handle->tme_log_handle_message);
handle->tme_log_handle_message = NULL;
}
if (handle->tme_log_handle_errno != TME_OK) {
fprintf(fp, ": %s", strerror(handle->tme_log_handle_errno));
}
fputc('\n', fp);
/* unlock the log mutex: */
tme_mutex_unlock(&_tmesh_log_mutex);
}
/* our binary log output function: */
static void
_tmesh_log_output_binary(struct tme_log_handle *handle)
{
struct _tmesh_log_message_binary *binary_message;
tme_uint32_t handle_size_errno;
struct _tmesh_log_message_binary binary_message_buffer;
tme_uint32_t buffer_size;
/* lock the log mutex: */
tme_mutex_lock(&_tmesh_log_mutex);
/* make values for the binary message header: */
binary_message = (struct _tmesh_log_message_binary *) handle->tme_log_handle_private;
handle_size_errno = binary_message->_tmesh_log_message_binary_handle_size_errno;
TME_FIELD_MASK_DEPOSITU(handle_size_errno,
_TMESH_LOG_MESSAGE_BINARY_SIZE,
(handle->tme_log_handle_message_size
- sizeof(*binary_message)));
TME_FIELD_MASK_DEPOSITU(handle_size_errno,
_TMESH_LOG_MESSAGE_BINARY_ERRNO,
handle->tme_log_handle_errno);
/* write the binary message header: */
binary_message = (struct _tmesh_log_message_binary *) handle->tme_log_handle_message;
if (_TME_ALIGNOF_INT32_T == 1) {
binary_message->_tmesh_log_message_binary_handle_size_errno = handle_size_errno;
binary_message->_tmesh_log_message_binary_level = handle->tme_log_handle_level;
}
else {
binary_message_buffer._tmesh_log_message_binary_handle_size_errno = handle_size_errno;
binary_message_buffer._tmesh_log_message_binary_level = handle->tme_log_handle_level;
memcpy(binary_message,
&binary_message_buffer,
sizeof(binary_message_buffer));
}
/* if there isn't enough room in the buffer for a maximum-sized
message: */
buffer_size
= ((((char *) binary_message)
+ handle->tme_log_handle_message_size)
- (char *) handle->tme_log_handle_private);
if (buffer_size
> (_TMESH_LOG_MESSAGE_BINARY_BUFFER_SIZE
- TME_LOG_MESSAGE_SIZE_MAX_BINARY)) {
/* write out the buffer: */
fwrite(handle->tme_log_handle_private,
1,
buffer_size,
_tmesh_log);
/* reset the buffer: */
handle->tme_log_handle_message = handle->tme_log_handle_private;
}
/* otherwise, there is enough room in the buffer for a maximum-sized
message: */
else {
/* advance the buffer: */
handle->tme_log_handle_message += handle->tme_log_handle_message_size;
}
/* reset for the next message: */
handle->tme_log_handle_message_size = sizeof(*binary_message);
/* unlock the log mutex: */
tme_mutex_unlock(&_tmesh_log_mutex);
}
/* our log open function: */
static void
_tmesh_log_open(struct tmesh_support *support,
struct tme_log_handle *handle,
const char *pathname,
const char *module)
{
struct _tmesh_log_message_binary *binary_message;
/* lock the log mutex: */
tme_mutex_lock(&_tmesh_log_mutex);
handle->tme_log_handle_level_max = 0;
handle->tme_log_handle_mode = _tmesh_log_mode;
/* if the log is binary: */
if (handle->tme_log_handle_mode == TME_LOG_MODE_BINARY) {
/* allocate the binary buffer: */
handle->tme_log_handle_private = tme_malloc(_TMESH_LOG_MESSAGE_BINARY_BUFFER_SIZE);
/* allocate the handle number and make the first message
structure: */
binary_message = (struct _tmesh_log_message_binary *) handle->tme_log_handle_private;
memset (binary_message, 0, sizeof(*binary_message));
TME_FIELD_MASK_DEPOSITU(binary_message->_tmesh_log_message_binary_handle_size_errno,
_TMESH_LOG_MESSAGE_BINARY_HANDLE,
_tmesh_log_handle_next);
_tmesh_log_handle_next++;
handle->tme_log_handle_message = (char *) binary_message;
handle->tme_log_handle_message_size = sizeof(*binary_message);
assert (handle->tme_log_handle_message_size < TME_LOG_MESSAGE_SIZE_MAX_BINARY);
/* write a dummy first message for the handle that is only the
pathname: */
TME_FIELD_MASK_DEPOSITU(binary_message->_tmesh_log_message_binary_handle_size_errno,
_TMESH_LOG_MESSAGE_BINARY_SIZE,
strlen(pathname) + 1);
fwrite(binary_message,
sizeof(*binary_message),
1,
_tmesh_log);
fwrite(pathname,
1,
(strlen(pathname) + 1),
_tmesh_log);
/* set the output function: */
handle->tme_log_handle_output = _tmesh_log_output_binary;
/* set the format hash: */
handle->tme_log_handle_hash_format = _tmesh_log_hash_format;
}
/* otherwise, the log is text: */
else {
/* set the output function: */
handle->tme_log_handle_message = NULL;
handle->tme_log_handle_output = _tmesh_log_output;
handle->tme_log_handle_private = tme_strdup(pathname);
}
/* unlock the log mutex: */
tme_mutex_unlock(&_tmesh_log_mutex);
}
/* our log close function: */
static void
_tmesh_log_close(struct tmesh_support *support,
struct tme_log_handle *handle)
{
tme_free(handle->tme_log_handle_private);
}
/* our thread: */
static void
_tmesh_thread(void *junk)
{
int yield, rc;
struct tmesh_io *io;
struct _tmesh_input *input;
char *output;
unsigned int consumed;
/* loop while we have a current input buffer: */
for (; (io = _tmesh_io) != NULL;) {
input = io->tmesh_io_private;
/* remove all consumed characters: */
_tmesh_remove_consumed(input);
/* if the current input buffer is full, a command is too long: */
if (input->_tmesh_input_buffer_head
== sizeof(input->_tmesh_input_buffer)) {
fprintf(stderr, "%s: command too long\n", argv0);
input->_tmesh_input_buffer_head = 0;
}
/* try to read more input: */
rc = tme_thread_read_yield(fileno(input->_tmesh_input_fp),
input->_tmesh_input_buffer
+ input->_tmesh_input_buffer_head,
sizeof(input->_tmesh_input_buffer)
- input->_tmesh_input_buffer_head);
/* if the read failed: */
if (rc < 0) {
fprintf(stderr, "%s: %s\n",
io->tmesh_io_name,
strerror(errno));
continue;
}
/* add characters in our current input buffer, or set EOF: */
if (rc > 0) {
input->_tmesh_input_buffer_head += rc;
}
else {
input->_tmesh_input_buffer_eof = TRUE;
}
/* run commands until we have to yield: */
for (;;) {
/* all characters already read have been consumed: */
consumed = input->_tmesh_input_buffer_tail;
/* run a command: */
rc = tmesh_eval(_tmesh, &output, &yield);
/* if we're yielding: */
if (yield) {
/* if the current io has not changed, mark how many
characters were consumed by successful commands: */
if (io == _tmesh_io) {
input->_tmesh_input_buffer_tail = consumed;
}
break;
}
/* this command may have changed the current io, so reload: */
io = _tmesh_io;
input = io->tmesh_io_private;
/* display this command's output: */
if (rc == TME_OK) {
if (output != NULL
&& *output != '\0') {
printf("%s\n", output);
}
}
else {
fprintf(stderr, "%s:%lu: ",
io->tmesh_io_name,
io->tmesh_io_input_line);
if (output != NULL
&& *output != '\0') {
fprintf(stderr, "%s: ", output);
}
fprintf(stderr, "%s\n", strerror(rc));
}
if (output != NULL) {
tme_free(output);
}
/* put up the next prompt: */
if (isatty(fileno(input->_tmesh_input_fp))
&& isatty(fileno(stdout))) {
printf("%s> ", argv0);
fflush(stdout);
}
}
}
}
int
main(int argc, char **argv)
{
int usage;
const char *opt;
int arg_i;
const char *pre_threads_filename;
const char *log_filename;
int interactive;
struct tmesh_io io;
struct tmesh_support support;
struct _tmesh_input *input_stdin;
char *output;
int yield, rc;
/* check our command line: */
usage = FALSE;
pre_threads_filename = NULL;
log_filename = "/dev/null";
interactive = TRUE;
if ((argv0 = strrchr(argv[0], '/')) == NULL) argv0 = argv[0]; else argv0++;
for (arg_i = 1;
(arg_i < argc
&& *argv[arg_i] == '-');
arg_i++) {
opt = argv[arg_i];
if (!strcmp(opt, "--log")) {
if (++arg_i < argc) {
log_filename = argv[arg_i];
}
else {
usage = TRUE;
break;
}
}
else if (!strcmp(opt, "--log-mode")) {
++arg_i;
if (arg_i >= argc
|| strcmp(argv[arg_i], "binary")) {
usage = TRUE;
break;
}
_tmesh_log_mode = TME_LOG_MODE_BINARY;
if (_tmesh_log_hash_format == NULL) {
_tmesh_log_hash_format = tme_hash_new(tme_direct_hash, tme_direct_compare, TME_HASH_DATA_NULL);
}
}
else if (!strcmp(opt, "-c")
|| !strcmp(opt, "--noninteractive")) {
interactive = FALSE;
}
else {
if (strcmp(opt, "-h")
&& strcmp(opt, "--help")
&& strcmp(opt, "-h")) {
fprintf(stderr, "%s: unknown option %s\n",
argv0, opt);
}
usage = TRUE;
break;
}
}
if (arg_i < argc) {
pre_threads_filename = argv[arg_i++];
}
else {
usage = TRUE;
}
if (usage) {
fprintf(stderr, "\
usage: %s [OPTIONS] INITIAL-CONFIG\n\
where OPTIONS are:\n\
--log LOGFILE log to LOGFILE\n\
-c, --noninteractive read no commands from standard input\n\
",
argv0);
exit(1);
}
if (!strcmp(log_filename, "-")) {
_tmesh_log = stdout;
}
else {
_tmesh_log = fopen(log_filename, "a");
if (_tmesh_log == NULL) {
perror(log_filename);
exit(1);
}
}
/* initialize libtme: */
(void) tme_init();
/* initialize libtmesh: */
(void) tmesh_init();
/* create our stdin input buffer, and stuff it with the command to
source the pre-threads commands: */
input_stdin = tme_new0(struct _tmesh_input, 1);
input_stdin->_tmesh_input_fp = stdin;
snprintf(input_stdin->_tmesh_input_buffer,
sizeof(input_stdin->_tmesh_input_buffer) - 1,
"source %s\n",
pre_threads_filename);
input_stdin->_tmesh_input_buffer[sizeof(input_stdin->_tmesh_input_buffer) - 1] = '\0';
input_stdin->_tmesh_input_buffer_head = strlen(input_stdin->_tmesh_input_buffer);
/* create our stdin io: */
io.tmesh_io_name = "*stdin*";
io.tmesh_io_private = input_stdin;
io.tmesh_io_input_line = 0;
io.tmesh_io_getc = _tmesh_getc;
io.tmesh_io_close = _tmesh_close;
io.tmesh_io_open = _tmesh_open;
_tmesh_io = &io;
/* the next open we do will be for the pre-threads commands: */
_tmesh_doing_pre_threads = TRUE;
/* create our support: */
tme_mutex_init(&_tmesh_log_mutex);
support.tmesh_support_log_open = _tmesh_log_open;
support.tmesh_support_log_close = _tmesh_log_close;
/* create our shell: */
_tmesh = tmesh_new(&support, &io);
/* run commands until we get a yield: */
for (;;) {
rc = tmesh_eval(_tmesh, &output, &yield);
if (yield) {
break;
}
if (rc == TME_OK) {
if (output != NULL
&& *output != '\0') {
printf("%s\n", output);
}
}
else {
fprintf(stderr, "%s:%lu: ",
_tmesh_io->tmesh_io_name,
_tmesh_io->tmesh_io_input_line);
if (output != NULL
&& *output != '\0') {
fprintf(stderr, "%s: ", output);
}
fprintf(stderr, "%s\n", strerror(rc));
}
if (output != NULL) {
tme_free(output);
}
}
/* if we're interactive: */
if (interactive) {
/* put up our first prompt: */
if (isatty(fileno(stdin))
&& isatty(fileno(stdout))) {
printf("%s> ", argv0);
fflush(stdout);
}
/* create our thread: */
tme_thread_create((tme_thread_t) _tmesh_thread, NULL);
}
/* run the threads: */
tme_threads_run();
/* done: */
exit(0);
}