657 lines
16 KiB
C

/* This file is part of the Project Athena Zephyr Notification System.
* It is one of the source files comprising zwgc, the Zephyr WindowGram
* client.
*
* Created by: Marc Horowitz <marc@athena.mit.edu>
*
* $Id$
*
* Copyright (c) 1989 by the Massachusetts Institute of Technology.
* For copying and distribution information, see the file
* "mit-copyright.h".
*/
#include <sysdep.h>
#if (!defined(lint) && !defined(SABER))
static const char rcsid_port_c[] = "$Id$";
#endif
#include <zephyr/mit-copyright.h>
/****************************************************************************/
/* */
/* The Implementation of the port type: */
/* */
/****************************************************************************/
#include "new_string.h"
#include "port_dictionary.h"
#include "port.h"
#include "notice.h"
#include "variables.h"
/****************************************************************************/
/* */
/* Port methods (internal): */
/* */
/****************************************************************************/
static string
port_get(port *p)
{
char *(*get_proc)(port *, char **);
char *error = NULL;
char *result;
if (p->status & INPUT_CLOSED) {
var_set_variable("error",
"Attempt to read from a port whose input has been closed");
return(string_Copy(""));
}
get_proc = p->get;
if (!get_proc) {
var_set_variable("error",
"Attempt to read from a port which does not support reading");
return(string_Copy(""));
}
result = get_proc(p, &error);
if (!result) {
var_set_variable("error", error);
return(string_Copy(""));
} else
return(result);
}
static void
port_put(port *p,
char *data,
int length)
{
char *(*put_proc)(port *, char *, int);
char *error;
if (p->status & OUTPUT_CLOSED) {
var_set_variable("error",
"Attempt to write to a port whose output has been closed");
return;
}
put_proc = p->put;
if (!put_proc) {
var_set_variable("error",
"Attempt to write to a port which does not support writing");
return;
}
error = put_proc(p, data, length);
if (error)
var_set_variable("error", error);
}
static void
port_close_input(port *p)
{
char *(*close_input_proc)(port *);
char *error;
if (p->status & INPUT_CLOSED)
return;
p->status |= INPUT_CLOSED;
close_input_proc = p->close_input;
if (!close_input_proc)
return;
error = close_input_proc(p);
if (error)
var_set_variable("error", error);
}
static void
port_close_output(port *p)
{
char *(*close_output_proc)(port *);
char *error;
if (p->status & OUTPUT_CLOSED)
return;
p->status |= OUTPUT_CLOSED;
close_output_proc = p->close_output;
if (!close_output_proc)
return;
error = close_output_proc(p);
if (error)
var_set_variable("error", error);
}
/****************************************************************************/
/* */
/* Code to implement a namespace of ports: */
/* */
/****************************************************************************/
/*
* port_dict - the dictionary mapping portnames to ports
*/
static port_dictionary port_dict = NULL;
/*
* void init_ports()
* Modifies: all ports
* Effects: Closes all existing ports. Must be called before
* any other port call is made.
*/
static void
close_port_from_binding(port_dictionary_binding *b)
{
port_close_input(&(b->value));
port_close_output(&(b->value));
}
void
init_ports(void)
{
if (port_dict) {
port_dictionary_Enumerate(port_dict, close_port_from_binding);
port_dictionary_Destroy(port_dict);
}
port_dict = port_dictionary_Create(31);
}
/*
* Internal Routine:
*
* port *create_named_port(string name)
* Modifies: the port named name
* Requires: init_ports has been called
* Effects: If a port with name name already exists, it is first
* closed (& destroyed). A new unfilled in port is then
* created and assigned the name name. Its address is
* then returned. It is up to the caller to fill in its
* various fields correctly.
*/
static port *
create_named_port(string name)
{
int already_exists;
port_dictionary_binding *binding;
binding = port_dictionary_Define(port_dict, name, &already_exists);
if (already_exists) {
port_close_input(&(binding->value));
port_close_output(&(binding->value));
}
return(&(binding->value));
}
/*
* Internal Routine:
*
* port *get_named_port(string name)
* Requires: init_ports has been called
* Effects: If there is a port by name name, returns a pointer to
* it. Otherwise returns NULL.
*/
static port *
get_named_port(string name)
{
port_dictionary_binding *binding;
binding = port_dictionary_Lookup(port_dict, name);
if (!binding)
return(NULL);
return(&(binding->value));
}
/****************************************************************************/
/* */
/* External interface to named ports: */
/* */
/****************************************************************************/
/*
* string read_from_port(string name)
* Requires: init_ports has been called
* Modifies: the port named name if any, $error
* Effects: If a port by name name does not exist, sets $error to
* "No such port" & returns "". Otherwise, attempts to
* read from that port. If an error occurs, $error is
* set to the error message and "" returned. Otherwise
* the read string is returned. The returned string is
* on the heap & must be eventually freed.
*/
string
read_from_port(string name)
{
port *p;
if (!(p = get_named_port(name))) {
var_set_variable("error", "No such port");
return(string_Copy(""));
}
return(port_get(p));
}
/*
* void write_on_port(string name, char *text, int length)
* Requires: init_ports has been called, length>=0
* Modifies: the port named name if any, $error
* Effects: If a port by name name does not exist, sets $error to
* "No such port" & returns. Otherwise, attempts to
* write text[0..length-1] on that port. If an error
* occurs, $error is set to the error message.
*/
void
write_on_port(string name,
char *text,
int length)
{
port *p;
if (!(p = get_named_port(name))) {
var_set_variable("error", "No such port");
return;
}
port_put(p, text, length);
}
/*
* void close_port_input(string name)
* Requires: init_ports has been called
* Modifies: the port named name if any, $error
* Effects: If a port by name name does not exist, sets $error to
* "No such port" & returns. Otherwise, closes the
* input part of the port by name name. When both a
* port's input & output parts have been closed, the
* port is deleted to save space. If an error
* occurs, $error is set to the error message.
*/
void
close_port_input(string name)
{
port_dictionary_binding *binding;
binding = port_dictionary_Lookup(port_dict, name);
if (!binding)
return;
port_close_input(&(binding->value));
if (binding->value.status == PORT_CLOSED)
port_dictionary_Delete(port_dict, binding);
}
/*
* void close_port_output(string name)
* Requires: init_ports has been called
* Modifies: the port named name if any, $error
* Effects: If a port by name name does not exist, sets $error to
* "No such port" & returns. Otherwise, closes the
* output part of the port by name name. When both a
* port's input & output parts have been closed, the
* port is deleted to save space. If an error
* occurs, $error is set to the error message.
*/
void
close_port_output(string name)
{
port_dictionary_binding *binding;
binding = port_dictionary_Lookup(port_dict, name);
if (!binding)
return;
port_close_output(&(binding->value));
if (binding->value.status == PORT_CLOSED)
port_dictionary_Delete(port_dict, binding);
}
/****************************************************************************/
/* */
/* Code to implement a port given some FILE *'s: */
/* */
/****************************************************************************/
static string
get_file(port *p,
char **error_p)
{
char buffer[10000]; /* <<<>>> */
if (!p->data.file.input_connector) {
*error_p = "Attempt to read past end of file";
return(NULL);
}
buffer[0] = 0;
errno = 0;
if (!fgets(buffer, 9999, p->data.file.input_connector)) {
if (errno)
*error_p = strerror(errno);
else
*error_p = "Attempt to read past end of file";
return(NULL);
}
buffer[9999] = 0;
return(string_Copy(buffer));
}
static char *
put_file(port *p,
string text,
int length)
{
if (!p->data.file.output_connector)
return(NULL);
errno = 0;
fwrite(text, 1, length, p->data.file.output_connector);
fflush(p->data.file.output_connector);
if (errno)
return(strerror(errno));
return(NULL);
}
static char *
close_file_input(port *p)
{
errno = 0;
if (p->data.file.input_connector) {
fclose(p->data.file.input_connector);
p->data.file.input_connector = 0;
}
if (errno)
return(strerror(errno));
return(NULL);
}
static char *
close_file_output(port *p)
{
errno = 0;
if (p->data.file.output_connector) {
fclose(p->data.file.output_connector);
p->data.file.output_connector = 0;
}
if (errno)
return(strerror(errno));
return(NULL);
}
void create_port_from_files(string name,
FILE *input,
FILE *output)
{
port *p = create_named_port(name);
#if !defined(__HIGHC__)
p->get = input ? get_file : NULL;
p->put = output ? put_file : NULL;
#else
/* RT compiler (hc2.1y) bug workaround */
if (input)
p->get = get_file;
else
p->get = NULL;
if (output)
p->put = put_file;
else
p->put = NULL;
#endif
p->close_input = close_file_input;
p->close_output = close_file_output;
p->status = 0;
p->data.file.input_connector = input;
p->data.file.output_connector = output;
}
/****************************************************************************/
/* */
/* Code for creating various types of FILE * ports: */
/* */
/****************************************************************************/
void
create_subprocess_port(string name,
char **argv)
{
int pid;
int to_child_descriptors[2];
int to_parent_descriptors[2];
FILE *in = 0;
FILE *out = 0;
/* <<<>>> (file leak) */
if (pipe(to_child_descriptors)!=0 || pipe(to_parent_descriptors)!=0)
return;
pid = fork();
if (pid == -1) {
fprintf(stderr, "zwgc: error while attempting to fork: ");
perror("");
return; /* <<<>>> */
} else if (pid == 0) { /* in child */
close(0);
close(1);
dup2(to_child_descriptors[0], 0);
dup2(to_parent_descriptors[1], 1);
close(to_child_descriptors[1]);
close(to_parent_descriptors[0]);
execvp(argv[0], argv);
fprintf(stderr,"zwgc: unable to exec %s: ", argv[0]);
perror("");
_exit(errno);
}
fcntl(to_parent_descriptors[0], F_SETFD, 1);
fcntl(to_child_descriptors[1], F_SETFD, 1);
in = fdopen(to_parent_descriptors[0],"r");
out = fdopen(to_child_descriptors[1],"w");
close(to_child_descriptors[0]);
close(to_parent_descriptors[1]);
create_port_from_files(name, in, out);
}
void
create_file_append_port(string name,
string filename)
{
FILE *out;
int oumask;
errno = 0;
oumask = umask(077); /* allow read/write for us only */
out = fopen(filename, "a");
(void) umask(oumask);
if (out == NULL) {
var_set_variable("error", strerror(errno));
return;
}
create_port_from_files(name, 0, out);
}
void
create_file_input_port(string name,
string filename)
{
FILE *in;
errno = 0;
in = fopen(filename, "r");
if (in == NULL) {
var_set_variable("error", strerror(errno));
return;
}
create_port_from_files(name, in, 0);
}
void
create_file_output_port(string name,
string filename)
{
FILE *out;
int oumask;
errno = 0;
oumask = umask(077); /* allow read/write for us only */
out = fopen(filename, "w");
(void) umask(oumask);
if (out == NULL) {
var_set_variable("error", strerror(errno));
return;
}
create_port_from_files(name, 0, out);
}
/****************************************************************************/
/* */
/* Code to implement a port given a filter function: */
/* */
/****************************************************************************/
static string
get_filter(port *p,
char **error_p)
{
string result;
if (string_stack_empty(p->data.filter.waiting_packets)) {
*error_p = "Attempt to read from port when no data available";
return(NULL);
}
result = string_stack_top(p->data.filter.waiting_packets);
string_stack_pop(p->data.filter.waiting_packets);
return(result);
}
static char *
put_filter(port *p,
string text,
int length)
{
string input;
string output;
if (p->status & INPUT_CLOSED)
return(NULL);
input = convert_nulls_to_newlines(text, length);
output = (*(p->data.filter.filter))(input);
free(input);
string_stack_push(p->data.filter.waiting_packets, output);
return(NULL);
}
static char *
close_filter_input(port *p)
{
while (!string_stack_empty(p->data.filter.waiting_packets))
string_stack_pop(p->data.filter.waiting_packets);
return(NULL);
}
/*ARGSUSED*/
static char *
close_filter_output(port *p)
{
return(NULL);
}
void
create_port_from_filter(string name,
string (*filter)(string))
{
port *p = create_named_port(name);
p->get = get_filter;
p->put = put_filter;
p->close_input = close_filter_input;
p->close_output = close_filter_output;
p->status = 0;
p->data.filter.waiting_packets = string_stack_create();
p->data.filter.filter = filter;
}
/****************************************************************************/
/* */
/* Code to implement a port given an output function: */
/* */
/****************************************************************************/
static char *
put_output(port *p,
string text,
int length)
{
string input;
char *error;
input = convert_nulls_to_newlines(text, length);
error = p->data.output.output(input);
free(input);
return(error);
}
/*ARGSUSED*/
static char *
close_output(port *p)
{
return(NULL);
}
void
create_port_from_output_proc(string name,
char *(*output)(string))
{
#ifdef SABER /* Yes, it's another ANSI incompatiblity */
port *p;
#else
port *p = create_named_port(name);
#endif
#ifdef SABER
p = create_named_port(name);
#endif
p->get = NULL;
p->put = put_output;
p->close_input = close_output;
p->close_output = close_output;
p->status = 0;
p->data.output.output = output;
}