657 lines
16 KiB
C
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;
|
|
}
|