This is task.c in view mode; [Download] [Up]
/*
* $Header: /disk/d/src/devel/gated/dist/src/RCS/task.c,v 2.1 92/02/24 14:13:05 jch Exp $
*/
/*%Copyright%*/
/************************************************************************
* *
* GateD, Release 2 *
* *
* Copyright (c) 1990,1991,1992 by Cornell University *
* All rights reserved. *
* *
* THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY *
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT *
* LIMITATION, THE IMPLIED WARRANTIES OF MERCHANTABILITY *
* AND FITNESS FOR A PARTICULAR PURPOSE. *
* *
* Royalty-free licenses to redistribute GateD Release *
* 2 in whole or in part may be obtained by writing to: *
* *
* GateDaemon Project *
* Information Technologies/Network Resources *
* 143 Caldwell Hall *
* Cornell University *
* Ithaca, NY 14853-2602 *
* *
* GateD is based on Kirton's EGP, UC Berkeley's routing *
* daemon (routed), and DCN's HELLO routing Protocol. *
* Development of Release 2 has been supported by the *
* National Science Foundation. *
* *
* Please forward bug fixes, enhancements and questions to the *
* gated mailing list: gated-people@gated.cornell.edu. *
* *
* Authors: *
* *
* Jeffrey C Honig <jch@gated.cornell.edu> *
* Scott W Brim <swb@gated.cornell.edu> *
* *
*************************************************************************
* *
* Portions of this software may fall under the following *
* copyrights: *
* *
* Copyright (c) 1988 Regents of the University of California. *
* All rights reserved. *
* *
* Redistribution and use in source and binary forms are *
* permitted provided that the above copyright notice and *
* this paragraph are duplicated in all such forms and that *
* any documentation, advertising materials, and other *
* materials related to such distribution and use *
* acknowledge that the software was developed by the *
* University of California, Berkeley. The name of the *
* University may not be used to endorse or promote *
* products derived from this software without specific *
* prior written permission. THIS SOFTWARE IS PROVIDED *
* ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, *
* INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. *
* *
************************************************************************/
#include "include.h"
#include "bgp.h"
#include "egp.h"
#include "rip.h"
#include "hello.h"
#include "icmp.h"
#include "snmp.h"
#include "parse.h"
#include <signal.h>
#if defined(_IBMR2)
#include <time.h>
#endif /* defined(_IBMR2) */
#include <sys/time.h>
#ifdef SYSV
#include <sys/sioctl.h>
#include <sys/stropts.h>
#else /* SYSV */
#include <sys/resource.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#endif /* SYSV */
#include "task_sig.h"
char *task_path_name; /* Directory where we were started */
static timer *timer_active; /* Pointer to the active timer */
static timer timer_queue_active =
{&timer_queue_active, &timer_queue_active, "activeTimers"}; /* Doubly linked list of active timers */
static timer timer_queue_inactive =
{&timer_queue_inactive, &timer_queue_inactive, "inactiveTimers"}; /* Doubly linked list of inactive timers */
static task task_head =
{&task_head, &task_head, "taskHead"}; /* Head of doubly linked list of timers */
static fd_set task_select_readbits;
static fd_set task_select_writebits;
static fd_set task_select_exceptbits;
static int task_max_socket = 0;
static task **task_socket_tasks = (task **) 0;
#ifndef SYSV
static int task_signal_mask = 0; /* Signals that are blocked */
#endif /* SYSV */
static int task_signals[] = {
SIGTERM,
SIGALRM,
SIGUSR1,
SIGINT,
SIGHUP,
#ifndef NO_FORK
SIGCHLD,
#endif /* NO_FORK */
0};
#define SIGNAL_LIST(ip) { int *ip; for (ip = task_signals; *ip; ip++)
#define SIGNAL_LIST_END(ip) }
static bits task_flag_bits[] =
{
{TASKF_ACCEPT, "Accept"},
{TASKF_CONNECT, "Connect"},
{TASKF_IPHEADER, "IPHeader"},
{0}
};
static bits task_socket_options[] =
{
{TASKOPTION_RECVBUF, "RecvBuffer"},
{TASKOPTION_SENDBUF, "SendBuffer"},
{TASKOPTION_LINGER, "Linger"},
{TASKOPTION_REUSEADDR, "ReUseAddress"},
{TASKOPTION_BROADCAST, "Broadcast"},
{TASKOPTION_DONTROUTE, "DontRoute"},
{TASKOPTION_KEEPALIVE, "KeepAlive"},
{TASKOPTION_DEBUG, "Debug"},
{TASKOPTION_NONBLOCKING, "NonBlocking"},
{TASKOPTION_USELOOPBACK, "UseLoopback"},
{0, NULL}
};
static bits task_msg_bits[] =
{
{MSG_OOB, "MSG_OOB"},
{MSG_PEEK, "MSG_PEEK"},
{MSG_DONTROUTE, "MSG_DONTROUTE"},
#ifdef MSG_EOR
{MSG_EOR, "MSG_EOR"},
#endif /* MSG_EOR */
#ifdef MSG_TRUNC
{MSG_TRUNC, "MSG_TRUNC"},
#endif /* MSG_TRUNC */
#ifdef MSG_CTRUNC
{MSG_CTRUNC, "MSG_CTRUNC"},
#endif /* MSG_CTRUNC */
#ifdef MSG_WAITALL
{MSG_WAITALL, "MSG_WAITALL"},
#endif /* MSG_WAITALL */
{0, NULL}
};
static bits timer_flag_bits[] =
{
{TIMERF_ABSOLUTE, "Absolute"},
{TIMERF_DELETE, "Delete"},
{0}
};
/*
* Insert a timer on one of the queues. Inactive timers are
* inserted at the beginning of their queue, Active timers are
* inserted in order of their expiration.
*/
static void
timer_insert(tip)
timer *tip;
{
timer *tip1;
if (tip->timer_interval) {
TIMER_ACTIVE(tip1) {
if (tip->timer_next_time < tip1->timer_next_time) {
break;
}
} TIMER_ACTIVEEND(tip1);
} else {
tip1 = timer_queue_inactive.timer_forw;
}
insque((struct qelem *) tip, (struct qelem *) tip1->timer_back);
}
/*
* Return a pointer to a string containing the timer name
*/
char *
timer_name(tip)
timer *tip;
{
static char name[MAXHOSTNAMELENGTH];
if (tip->timer_task) {
if (tip->timer_task->task_addr.in.sin_addr.s_addr) {
(void) sprintf(name, "%s_%s.%A",
tip->timer_task->task_name,
tip->timer_name,
&tip->timer_task->task_addr);
} else {
(void) sprintf(name, "%s_%s",
tip->timer_task->task_name,
tip->timer_name);
}
} else {
strcpy(name, tip->timer_name);
}
return (name);
}
/*
* Create a timer - returns pointer to timer structure
*/
timer *
timer_create(tp, indx, name, flags, interval, job)
task *tp;
int indx;
const char *name;
flag_t flags;
time_t interval;
void (*job) ();
{
timer *tip;
tip = (timer *) calloc(1, sizeof(timer));
if (!tip) {
trace(TR_ALL, LOG_ERR, "timer_create: calloc: %m");
quit(errno);
}
tip->timer_name = name;
tip->timer_task = tp;
tip->timer_index = indx;
tip->timer_flags = flags;
tip->timer_interval = interval;
tip->timer_job = job;
/* Link timer to it's task if there is one */
if (tip->timer_task) {
tip->timer_task->task_timer[tip->timer_index] = tip;
}
/* If this timer is active, set the intervals */
if (tip->timer_interval) {
tip->timer_next_time = tip->timer_last_time = time_sec;
if (tip->timer_flags & TIMERF_ABSOLUTE) {
tip->timer_next_time += tip->timer_interval;
}
}
/* Insert in the correct queue */
timer_insert(tip);
/* If we have changed the wakeup time cause a wakeup now to recalculate */
if ((tip == timer_queue_active.timer_forw) &&
(timer_queue_active.timer_next_time > tip->timer_next_time)) {
(void) kill(my_pid, SIGALRM);
}
trace(TR_TIMER, 0, "timer_create: created timer %s flags <%s> interval %#T at %T",
timer_name(tip),
trace_bits(timer_flag_bits, tip->timer_flags),
tip->timer_interval,
tip->timer_next_time);
return (tip);
}
/*
* Delete a timer
*/
void
timer_delete(tip)
timer *tip;
{
trace(TR_TIMER, 0, "timer_delete: %s", timer_name(tip));
/* Unlink this timer from it's task if there is one */
if (tip->timer_task) {
tip->timer_task->task_timer[tip->timer_index] = (timer *) 0;
}
if (tip == timer_active) {
tip->timer_flags |= TIMERF_DELETE;
} else {
remque((struct qelem *) tip);
(void) free((caddr_t) tip);
}
}
/*
* Reset a timer - move it to the inactive queue
*/
void
timer_reset(tip)
timer *tip;
{
if (tip->timer_interval) {
tip->timer_next_time = tip->timer_last_time = tip->timer_interval = (time_t) 0;
remque((struct qelem *) tip);
timer_insert(tip);
trace(TR_TIMER, 0, "timer_reset: reset %s",
timer_name(tip));
}
}
/*
* Set a timer to fire in interval seconds from now
*/
void
timer_set(tip, interval)
timer *tip;
time_t interval;
{
tip->timer_interval = interval;
if (!tip->timer_next_time) {
tip->timer_last_time = tip->timer_next_time = time_sec;
}
tip->timer_next_time = time_sec + tip->timer_interval;
/* Re-insert this timer in the active queue in expiration order */
remque((struct qelem *) tip);
timer_insert(tip);
/* If we have changed the wakeup time cause a wakeup now to recalculate */
if ((tip == timer_queue_active.timer_forw) &&
(tip != timer_active) &&
(timer_queue_active.timer_next_time > tip->timer_next_time)) {
(void) kill(my_pid, SIGALRM);
}
trace(TR_TIMER, 0, "timer_set: timer %s interval set to %#T at %T",
timer_name(tip),
tip->timer_interval,
tip->timer_next_time);
}
/*
* Set a timer to fire in interval seconds from the last time it fired
*/
void
timer_interval(tip, interval)
timer *tip;
time_t interval;
{
if (tip->timer_interval != interval) {
tip->timer_interval = interval;
if (!tip->timer_next_time) {
tip->timer_last_time = tip->timer_next_time = time_sec;
}
tip->timer_next_time = tip->timer_last_time + tip->timer_interval;
/* Re-insert this timer in the active queue in expiration order */
remque((struct qelem *) tip);
timer_insert(tip);
/* If we have changed the wakeup time cause a wakeup now to recalculate */
if ((tip == timer_queue_active.timer_forw) &&
(tip != timer_active) &&
(timer_queue_active.timer_next_time > tip->timer_next_time)) {
(void) kill(my_pid, SIGALRM);
}
trace(TR_TIMER, 0, "timer_interval: timer %s interval set to %#T at %T",
timer_name(tip),
tip->timer_interval,
tip->timer_next_time);
}
}
/*
* Dump the provided timer
*/
static void
timer_dump(fd, tip)
FILE *fd;
timer *tip;
{
(void) fprintf(fd, "\t\t%s",
timer_name(tip));
if (tip->timer_interval) {
(void) fprintf(fd, "\tlast: %T\tnext: %T\tinterval: %#T",
tip->timer_last_time,
tip->timer_next_time,
tip->timer_interval);
}
if (tip->timer_flags) {
(void) fprintf(fd, "\t<%s>",
trace_bits(timer_flag_bits, tip->timer_flags));
}
(void) fprintf(fd, "\n");
}
/*
* timer control for periodic route-age and interface processing.
* timer_dispatch() is called when the periodic interrupt timer expires.
*/
static void
timer_dispatch()
{
time_t late = 0;
timer *tip;
struct itimerval value;
/* Log a message if the system dispatched us late */
if (timer_queue_active.timer_last_time) {
trace(TR_TIMER, 0, "timer_dispatch: requested interval: %#T actual interval: %#T",
timer_queue_active.timer_interval,
time_sec - timer_queue_active.timer_last_time);
late = time_sec - timer_queue_active.timer_last_time - timer_queue_active.timer_interval;
if (late < 0) {
late = 0;
}
if (late) {
trace(TR_INT, 0, "timer_dispatch: interval timer interrupt %d seconds late",
late);
}
} else {
trace(TR_TIMER, 0, "timer_dispatch: initializing");
}
/* Run the queues until all the expired timers have been serviced. This allows for timers that expire while we are */
/* working on other timers */
do {
TIMER_ACTIVE(tip) {
/* Timers are in time order so we don't have to scan the whole list */
if (time_sec < tip->timer_next_time) {
break;
}
/* Log the timer */
trace(TR_TIMER, 0, "timer_dispatch: call %s, due at %T, last at %T, interval %#T",
timer_name(tip),
tip->timer_next_time,
tip->timer_last_time,
tip->timer_next_time - tip->timer_last_time);
/* Update time of last firing */
tip->timer_last_time = time_sec;
/* Call the timer routine */
timer_active = tip;
tip->timer_job(tip, tip->timer_interval);
timer_active = (timer *) 0;
tracef("timer_dispatch: returned from %s, ",
timer_name(tip));
/* If the timer is a one shot, delete it now */
if (tip->timer_flags & TIMERF_DELETE) {
trace(TR_TIMER, 0, "deletion requested");
timer_delete(tip);
continue;
}
if (tip->timer_interval) {
/* Reschedule again at the next interval after the current time */
while (tip->timer_next_time <= time_sec) {
tip->timer_next_time += tip->timer_interval;
}
/* Remove and re-insert to maintain order in the queue */
remque((struct qelem *) tip);
timer_insert(tip);
tracef("rescheduled ");
if (tip->timer_interval > late) {
tracef("in %#T ",
tip->timer_interval - late);
}
trace(TR_TIMER, 0, "at %T",
tip->timer_next_time);
} else {
trace(TR_TIMER, 0, "now inactive");
}
} TIMER_ACTIVEEND(tip);
/* Get the current time */
getod();
} while (timer_queue_active.timer_forw->timer_next_time <= time_sec);
/* Calulate when we are supposed to wake up next and how long that is from now */
timer_queue_active.timer_next_time = timer_queue_active.timer_forw->timer_next_time;
timer_queue_active.timer_interval = timer_queue_active.timer_next_time - time_sec;
timer_queue_active.timer_last_time = time_sec;
trace(TR_TIMER, 0, "timer_dispatch: end, next job: %T delta: %#T",
timer_queue_active.timer_next_time,
timer_queue_active.timer_interval);
/* Check for invalid intervals (should not happen) */
if (timer_queue_active.timer_interval <= 0) {
trace(TR_INT, 0, "timer_dispatch: timer interval (%#T) invalid, using 1 second",
timer_queue_active.timer_interval);
timer_queue_active.timer_interval = 1;
}
/* Set the interval timer */
value.it_interval.tv_sec = 0; /* no auto timer reload */
value.it_interval.tv_usec = 0;
value.it_value.tv_sec = timer_queue_active.timer_interval;
value.it_value.tv_usec = 0;
if (setitimer(ITIMER_REAL, &value, (struct itimerval *) 0)) {
trace(TR_ALL, LOG_ERR, "timer_dispatch: setitimer: %m");
quit(errno);
}
}
/* */
/*
* Return a pointer to a string containing the task name
*/
char *
task_name(tp)
task *tp;
{
static char name[MAXHOSTNAMELENGTH];
if (tp->task_addr.in.sin_addr.s_addr) {
(void) sprintf(name, "%s.%A",
tp->task_name,
&tp->task_addr);
} else {
strcpy(name, tp->task_name);
}
if (tp->task_pid > 0) {
(void) sprintf(&name[strlen(name)], "[%d]",
tp->task_pid);
}
return (name);
}
/*
* Receive packet and check for errors
*/
int
task_receive_packet(tp, count)
task *tp;
int *count;
{
int iov;
struct sockaddr_in *from;
struct msghdr *msghdr;
msghdr = &recv_msghdr;
iov = tp->task_flags & TASKF_IPHEADER ? RECV_IOVEC_IP : RECV_IOVEC_DATA;
msghdr->msg_iov = &recv_iovec[iov];
msghdr->msg_iovlen = RECV_IOVEC_SIZE - iov;
msghdr->msg_namelen = sizeof(recv_addr); /* Set max size */
memset(msghdr->msg_name, (char) 0, msghdr->msg_namelen); /* Clean name */
from = (struct sockaddr_in *) msghdr->msg_name; /* Set pointer to address */
*count = recvmsg(tp->task_socket, msghdr, 0);
if (!*count) {
return (-1);
}
if (*count < 0) {
int do_log = LOG_ERR;
switch (errno) {
case EINTR:
break;
case ENETDOWN:
case ENETUNREACH:
case ENETRESET:
case ECONNABORTED:
case ECONNRESET:
case ENOBUFS:
case ETIMEDOUT:
case ECONNREFUSED:
case EHOSTDOWN:
case EHOSTUNREACH:
do_log = 0;
default:
trace(TR_ALL, do_log, "task_receive_packet: %s recvmsg: %m",
task_name(tp));
break;
}
return (errno);
}
trace(TR_TASK, 0, "task_receive_packet: task %s from %#A socket %d length %d",
task_name(tp),
from,
tp->task_socket,
*count);
if (msghdr->msg_namelen != socksize(from)) {
trace(TR_INT, LOG_ERR, "task_receive_packet: %s fromlen %d invalid, expected %d",
task_name(tp),
msghdr->msg_namelen,
socksize(from));
return (EINVAL);
}
return (0);
}
/*
* Send a packet
*/
int
task_send_packet(tp, msg, len, flags, addr)
task *tp;
caddr_t msg;
int len;
flag_t flags;
sockaddr_un *addr;
{
int rc = 0;
tracef("task_send_packet: task %s socket %d length %d",
task_name(tp),
tp->task_socket,
len);
if (flags) {
tracef(" flags %s(%X)",
trace_bits(task_msg_bits, flags),
flags);
}
if (addr) {
tracef(" to %#A",
addr);
rc = sendto(tp->task_socket, msg, len, (int) flags, addr, socksize(addr));
} else {
rc = send(tp->task_socket, msg, len, (int) flags);
}
if (rc < 0) {
trace(TR_ALL, LOG_ERR, ": %m");
} else if (rc != len) {
trace(TR_ALL, LOG_ERR, ": %d bytes not accepted",
len - rc);
} else {
trace(TR_TASK, 0, NULL);
}
return rc;
}
/*
* Wait for incoming packets
*/
void
task_main()
{
#ifndef SYSV
int sigmask_save;
#endif /* SYSV */
int n, count, socket;
fd_set read_bits, write_bits, except_bits;
int forever = TRUE;
task *tp;
/* Allocate receive buffer know that we know it's maximum size */
if (count = recv_iovec[RECV_IOVEC_DATA].iov_len) {
if (!(recv_iovec[RECV_IOVEC_DATA].iov_base = (caddr_t) malloc((u_int) count))) {
trace(TR_ALL, LOG_ERR, "task_main: malloc: %m");
quit(errno);
}
}
timer_dispatch();
trace(TR_TASK, 0, NULL);
while (forever) {
trace(TR_TASK, 0, NULL);
read_bits = task_select_readbits;
write_bits = task_select_writebits;
except_bits = task_select_exceptbits;
n = select(task_max_socket + 1, &read_bits, &write_bits, &except_bits, (struct timeval *) 0);
if (n < 0) {
if (errno == EINTR) {
trace(TR_TASK, 0, "task_main: select: %m");
continue;
} else {
trace(TR_ALL, LOG_ERR, "task_main: select: %m");
quit(errno);
}
}
getod(); /* current time */
#ifdef SYSV
SIGNAL_LIST(ip) {
sighold(*ip);
} SIGNAL_LIST_END(ip) ;
#else /* SYSV */
sigmask_save = sigblock(task_signal_mask);
#endif /* SYSV */
for (socket = 0; socket < task_max_socket + 1; socket++) {
tp = task_socket_tasks[socket];
/* Check for ready for read on socket */
if (FD_ISSET(socket, &read_bits)) {
if (tp) {
if (tp->task_flags & TASKF_ACCEPT) {
if (tp->task_accept) {
trace(TR_TASK, 0, "task_main: accept ready for %s socket %d, protocol %d, port %d",
task_name(tp),
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
(void) tp->task_accept(tp);
} else {
trace(TR_INT, 0, "task_main: no task for accept on socket %d", socket);
}
} else {
if (tp->task_recv) {
trace(TR_TASK, 0, "task_main: recv ready for %s socket %d, protocol %d, port %d",
task_name(tp),
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
(void) tp->task_recv(tp);
} else {
trace(TR_INT, 0, "task_main: no task for read on socket %d", socket);
}
}
}
}
/* Check for ready for write on socket */
if (FD_ISSET(socket, &write_bits)) {
if (tp) {
if (tp->task_flags & TASKF_CONNECT) {
if (tp->task_connect) {
trace(TR_TASK, 0, "task_main: connect ready for %s socket %d, protocol %d, port %d",
task_name(tp),
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
(void) tp->task_connect(tp);
} else {
trace(TR_TASK, 0, "task_main: no task for connect on socket %d, protocol %d, port %d",
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
}
} else {
if (tp->task_write) {
trace(TR_TASK, 0, "task_main: write ready for %s socket %d, protocol %d, port %d",
task_name(tp),
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
(void) tp->task_write(tp);
} else {
trace(TR_TASK, 0, "task_main: no task for write on socket %d, protocol %d, port %d",
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
}
}
} else {
trace(TR_INT, 0, "task_main: no task for write/connect socket %d", socket);
}
}
/* Check for exception on socket */
if (FD_ISSET(socket, &except_bits)) {
if (tp) {
trace(TR_TASK, 0, "task_main: exception for %s socket %d, protocol %d, port %d",
task_name(tp),
socket,
tp->task_proto,
ntohs(tp->task_addr.in.sin_port));
(void) tp->task_except(tp);
} else {
trace(TR_INT, 0, "task_main: no task for exception socket %d", socket);
}
}
}
#ifdef SYSV
SIGNAL_LIST(ip) {
sigrelse(*ip);
} SIGNAL_LIST_END(ip) ;
#else /* SYSV */
(void) sigsetmask(sigmask_save);
#endif /* SYSV */
}
}
/*
* Call all tasks that have posted a cleanup routine
*/
static void
task_cleanup()
{
task *tp;
trace(TR_TASK, 0, NULL);
TASK_TABLE(tp) {
if (tp->task_cleanup) {
trace(TR_TASK, 0, "task_cleanup: Starting cleanup for task %s",
task_name(tp));
tp->task_cleanup(tp);
trace(TR_TASK, 0, "task_cleanup: Finished cleanup for task %s",
task_name(tp));
}
} TASK_TABLEEND(tp);
}
/*
* Call all task that have posted a reinit routine
*/
static void
task_reinit()
{
task *tp;
trace(TR_TASK, 0, NULL);
TASK_TABLE(tp) {
if (tp->task_reinit) {
trace(TR_TASK, 0, "task_reinit: Starting reinit for task %s",
task_name(tp));
tp->task_reinit(tp);
trace(TR_TASK, 0, "task_reinit: Finished reinit for task %s",
task_name(tp));
}
} TASK_TABLEEND(tp);
}
/*
* Call all tasks that have posted an ifchange routine
*/
void
task_ifchange(ifp)
if_entry *ifp;
{
task *tp;
trace(TR_TASK, 0, NULL);
TASK_TABLE(tp) {
if (tp->task_ifchange) {
trace(TR_TASK, 0, "task_ifchange: Starting ifchange for task %s",
task_name(tp));
tp->task_ifchange(tp, ifp);
trace(TR_TASK, 0, "task_ifchange: Finished ifchange for task %s",
task_name(tp));
}
} TASK_TABLEEND(tp);
}
static void
task_reconfigure()
{
int i;
u_int count = recv_iovec[RECV_IOVEC_DATA].iov_len;
#ifndef SYSV
int sigmask_save;
#endif /* SYSV */
#ifdef SYSV
SIGNAL_LIST(ip) {
sighold(*ip);
} SIGNAL_LIST_END(ip) ;
#else /* SYSV */
sigmask_save = sigblock(task_signal_mask);
#endif /* SYSV */
trace(TR_ALL, 0, NULL);
trace(TR_ALL, LOG_NOTICE, "task_receive_signal: re-initializing from %s",
EGPINITFILE);
trace(TR_ALL, 0, NULL);
i = adv_n_allocated;
task_cleanup();
if (adv_n_allocated) {
trace(TR_ALL, LOG_ERR, "reinit: %d of %d adv_entry elements not freed",
adv_n_allocated, i);
}
/* Reset options */
install = TRUE;
if (parse_parse(EGPINITFILE)) {
quit(0);
}
task_reinit();
/* XXX - Do we need to reinit all of these? */
#if defined(PROTO_ICMP) && !defined(RTM_ADD)
icmp_init();
#endif /* defined(PROTO_ICMP) && !defined(RTM_ADD) */
#ifdef PROTO_EGP
egp_init();
#endif /* PROTO_EGP */
#ifdef PROTO_BGP
bgp_init();
#endif /* PROTO_BGP */
#ifdef PROTO_RIP
rip_init();
#endif /* PROTO_RIP */
#ifdef PROTO_HELLO
hello_init();
#endif /* PROTO_HELLO */
#ifdef AGENT_SNMP
snmp_init();
#endif /* AGENT_SNMP */
/* Reallocate receive buffer if it's size has been increased */
if (recv_iovec[RECV_IOVEC_DATA].iov_len > count) {
count = recv_iovec[RECV_IOVEC_DATA].iov_len;
free(recv_iovec[RECV_IOVEC_DATA]);
if (!(recv_iovec[RECV_IOVEC_DATA].iov_base = (caddr_t) malloc(count))) {
trace(TR_ALL, LOG_ERR, "task_receive_signal: malloc %m");
quit(errno);
}
}
trace(TR_ALL, 0, NULL);
trace(TR_ALL, LOG_NOTICE, "task_receive_signal: reinitializing done");
trace(TR_ALL, 0, NULL);
#ifdef SYSV
SIGNAL_LIST(ip) {
sigrelse(*ip);
} SIGNAL_LIST_END(ip) ;
#else /* SYSV */
(void) sigsetmask(sigmask_save);
#endif /* SYSV */
}
/*ARGSUSED*/
static SIGTYPE
task_receive_signal(sig, code, scp)
int sig, code;
struct sigcontext *scp;
{
static int terminate = 0;
task *tp;
static const char *term_names[] =
{
"first",
"second",
"third"
};
getod();
trace(TR_TASK, 0, NULL);
trace(TR_TASK, 0, "task_receive_signal: received SIG%s code %d", trace_state(signal_names, sig - 1), code);
switch (sig) {
case SIGTERM:
trace(TR_INT, LOG_NOTICE, "task_receive_signal: %s terminate signal received", term_names[terminate]);
/* Subprocesses terminate immediately for now */
if (my_pid != my_mpid) {
exit(0);
}
terminate++;
if (terminate > 2) {
quit(0);
}
TASK_TABLE(tp) {
if (tp->task_terminate) {
trace(TR_TASK, 0, "task_receive_signal: terminating task %s",
task_name(tp));
tp->task_terminate(tp);
trace(TR_TASK, 0, NULL);
}
} TASK_TABLEEND(tp);
trace(TR_TASK, 0, "task_receive_signal: Exiting and waiting for completion");
break;
case SIGALRM:
timer_dispatch();
break;
case SIGHUP:
task_reconfigure();
break;
case SIGINT:
trace_dump(FALSE);
break;
case SIGUSR1:
if (trace_file == NULL) {
trace(TR_ALL, LOG_ERR, "task_receive_signal: can not toggle tracing to console");
break;
}
if (trace_flags) {
trace_off();
} else {
trace_on(trace_file, TRUE);
}
break;
#ifndef NO_FORK
case SIGCHLD:
{
int pid;
WAIT_T statusp;
pid = waitpid(-1, &statusp, WNOHANG|WUNTRACED);
if (pid) {
if (pid < 0) {
trace(TR_ALL, LOG_ERR, "task_receive_signal: waitpid() error: %m");
} else {
TASK_TABLE(tp) {
if (pid == tp->task_pid) {
break;
}
} TASK_TABLEEND(tp);
if (tp) {
int done = TRUE;
if (WIFSTOPPED(statusp)) {
/* Stopped by a signal */
trace(TR_ALL, LOG_ERR, "task_receive_signal: %s stopped by SIG%s",
task_name(tp),
trace_bits(signal_names, WSTOPSIG(statusp) - 1));
done = FALSE;
} else if (WIFSIGNALED(statusp)) {
/* Terminated by a signal */
trace(TR_ALL, LOG_ERR, "task_receive_signal: %s terminated abnormally by SIG%s",
task_name(tp),
trace_bits(signal_names, WTERMSIG(statusp) - 1));
} else if (WEXITSTATUS(statusp)) {
/* Non-zero exit status */
trace(TR_ALL, LOG_ERR, "task_receive_signal: %s terminated abnormally with retcode %d",
task_name(tp),
WEXITSTATUS(statusp));
} else {
/* Normal termination */
trace(TR_TASK, 0, "task_receive_signal: %s terminated normally",
task_name(tp));
if (tp->task_child) {
tp->task_child(tp);
}
}
} else {
trace(TR_ALL, LOG_ERR, "task_receive_signal: waitpid() returned status about unknown pid: %d",
pid);
}
}
}
}
break;
#endif /* NO_FORK */
default:
trace(TR_INT, LOG_ERR,
"task_receive_signal: Ignoring unknown signal SIG%s code %d",
trace_state(signal_names, sig - 1),
code);
}
trace(TR_TASK, 0, NULL);
SIGRETURN;
}
#ifdef notdef
/*
* close a task's socket and terminate
*/
void
task_close(tp)
task *tp;
{
trace(TR_TASK, 0, "task_close: close socket %d task %s",
tp->task_socket,
task_name(tp));
if (close(tp->task_socket)) {
trace(TR_ALL, LOG_ERR, "task_close: close %s.%d: %m",
task_name(tp),
tp->task_socket);
}
task_delete(tp);
}
#endif /* notdef */
/*
* Delete a task block and free allocated storage. When the last task has been deleted, exit.
*/
void
task_delete(tp)
task *tp;
{
int i;
int socket;
trace(TR_TASK, 0, "task_delete: deleting task %s",
task_name(tp));
if (tp->task_socket != -1) {
trace(TR_TASK, 0, "task_delete: closing socket %d for task %s",
tp->task_socket,
task_name(tp));
socket = tp->task_socket;
task_reset_socket(tp);
if (close(socket)) {
trace(TR_ALL, LOG_ERR, "task_delete: close %s.%d: %m",
task_name(tp),
socket);
}
}
/* Delete any timers associated with this task */
for (i = 0; i < TASK_TIMERS; i++) {
if (tp->task_timer[i]) {
timer_delete(tp->task_timer[i]);
}
}
if (tp->task_forw) {
remque((struct qelem *) tp);
free((char *) tp);
}
if ((task_head.task_forw == task_head.task_back) & (task_head.task_forw == (task *) & task_head)) {
trace(TR_TASK, 0, "task_delete: Removed last task, exiting");
quit(0);
}
}
void
task_flash(tp)
task *tp;
{
task *tp1;
trace(TR_TASK, 0, "task_flash: flash update request from %s revision is %d",
task_name(tp),
rt_revision);
TASK_TABLE(tp1) {
if (tp1->task_flash && (tp1->task_rtrevision < rt_revision)) {
trace(TR_TASK, 0, "task_flash: calling flash routine for %s revision %d",
task_name(tp1),
tp->task_rtrevision);
tp1->task_flash(tp1);
trace(TR_TASK, 0, "task_flash: return from routine for %s revision %d",
task_name(tp1),
tp->task_rtrevision);
}
} TASK_TABLEEND(tp1);
}
/*
* Apply the function "f" to all non-passive
* interfaces. If the interface supports the
* use of broadcasting use it, otherwise address
* the output to the known router.
*/
void
task_toall(tp, func, point_to_point, if_flag, gw_list, flash_update)
task *tp;
void (*func) ();
int point_to_point;
int if_flag;
gw_entry *gw_list;
int flash_update;
{
register if_entry *ifp;
register gw_entry *gwp;
register struct sockaddr *dst;
trace(TR_TASK, 0, "task_toall: task %s revision %d rt_revision %d",
task_name(tp),
tp->task_rtrevision,
rt_revision);
if (!point_to_point) {
IF_LIST(ifp) {
if ((ifp->int_state & (if_flag | IFS_UP | IFS_LOOPBACK)) != IFS_UP) {
continue;
}
if (ifp->int_state & IFS_BROADCAST) {
dst = &ifp->int_broadaddr.a;
} else if (ifp->int_state & IFS_POINTOPOINT) {
dst = &ifp->int_dstaddr.a;
} else if (ifp->int_state & IFS_NOAGE) {
continue;
} else {
dst = &ifp->int_addr.a;
}
(*func) (tp, dst, &ifp->int_addr.a, ifp, NULL, TRUE, flash_update);
} IF_LISTEND(ifp) ;
}
GW_LIST(gw_list, gwp) {
if (gwp->gw_flags & GWF_SOURCE) {
if ((ifp = if_withdst(&gwp->gw_addr)) <= (if_entry *) 0) {
trace(TR_ALL, LOG_ERR, "task_toall: Source gateway %A not on same net",
&gwp->gw_addr);
continue;
}
if ((ifp->int_state & (if_flag | IFS_UP)) != IFS_UP) {
/* XXX -Should no rip out apply to source[rip|hello]gateways */
continue;
}
(*func) (tp, &gwp->gw_addr, &ifp->int_addr.a, ifp, gwp, TRUE, flash_update);
}
} GW_LISTEND;
tp->task_rtrevision = rt_revision;
}
/*
* Allocate a task block with the specified name
*/
task *
task_alloc(name)
const char *name;
{
task *tp;
if (!(tp = (task *) calloc(1, sizeof(task)))) {
trace(TR_ALL, LOG_ERR, "task_alloc: calloc: %m");
quit(errno);
}
tp->task_name = name;
tp->task_terminate = task_delete;
tp->task_socket = -1;
trace(TR_TASK, 0, "task_alloc: allocated task block for %s", tp->task_name);
return (tp);
}
/*
* Build a task block and add to the linked list
*/
int
task_create(tp, maxsize)
task *tp;
int maxsize;
{
if (tp->task_flash) {
tp->task_rtrevision = rt_revision;
}
if (tp->task_socket != -1) {
task_set_socket(tp, tp->task_socket);
}
/*
* Set maximum receive buffer size
*/
if (maxsize > recv_iovec[RECV_IOVEC_DATA].iov_len) {
recv_iovec[RECV_IOVEC_DATA].iov_len = maxsize;
trace(TR_TASK, 0, "task_create: receive buffer size set to %d", maxsize);
}
insque((struct qelem *) tp, (struct qelem *) & task_head); /* Insert at the top of the task queue */
tracef("task_create: %s",
task_name(tp));
if (tp->task_proto) {
tracef(" proto %d",
tp->task_proto);
}
if (tp->task_addr.in.sin_port) {
tracef(" port %d",
ntohs(tp->task_addr.in.sin_port));
}
if (tp->task_socket != -1) {
tracef(" socket %d",
tp->task_socket);
}
if (tp->task_rtproto) {
tracef(" rt_proto <%s>",
trace_bits(rt_proto_bits, tp->task_rtproto));
}
trace(TR_TASK, 0, NULL);
return (1);
}
/*
* Terminate a subprocess
*/
static void
task_kill(tp)
task *tp;
{
kill(tp->task_pid, SIGTERM);
}
/*
* Spawn a process and create a task for it.
*/
int
task_fork(tp)
task *tp;
{
int rc = 0;
if (!(tp->task_pid = fork())) {
tp->task_pid = my_pid = getpid();
trace(TR_TASK, 0, "task_fork: %s forked",
task_name(tp));
if (tp->task_process) {
tp->task_process(tp);
}
trace(TR_TASK, 0, "task_fork: %s exiting",
task_name(tp));
exit(0);
}
if (tp->task_pid < 0) {
trace(TR_ALL, LOG_ERR, "task_fork: could not fork %s: %m",
task_name(tp));
task_delete(tp);
} else {
tp->task_terminate = task_kill;
rc = task_create(tp, 0);
}
return rc;
}
/* */
int
task_ioctl(fd, cmd, data, len)
int fd;
int cmd;
caddr_t data;
int len;
{
#if !defined(SYSV)
return ioctl(fd, cmd, data);
#else /* !defined(SYSV) */
struct strioctl si;
si.ic_cmd = cmd;
si.ic_timout = 0;
si.ic_len = len;
si.ic_dp = dp;
return ioctl(fd, I_STR, &si);
#endif /* !defined(SYSV) */
}
/**/
void
task_set_socket(tp, socket)
task *tp;
int socket;
{
tp->task_socket = socket;
trace(TR_TASK, 0, "task_set_socket: task %s socket %d",
task_name(tp),
tp->task_socket);
/* Allocate space for socket to task index */
if (!task_socket_tasks) {
task_socket_tasks = (task **) calloc(1, sizeof(task *) * getdtablesize());
if (!task_socket_tasks) {
trace(TR_ALL, LOG_ERR, "task_set_socket: calloc: %m");
quit(errno);
}
}
if (tp->task_recv || tp->task_accept) {
FD_SET(tp->task_socket, &task_select_readbits);
}
if (tp->task_write || tp->task_connect) {
FD_SET(tp->task_socket, &task_select_writebits);
}
if (tp->task_except) {
FD_SET(tp->task_socket, &task_select_exceptbits);
}
if (task_socket_tasks[tp->task_socket] && (task_socket_tasks[tp->task_socket] != tp)) {
tracef("task_set_socket: attempt to assign socket %d to task %s ",
tp->task_socket,
task_name(tp));
trace(TR_ALL, LOG_ERR, "socket already assigned to task %s",
task_name(task_socket_tasks[tp->task_socket]));
quit(EBADF);
}
task_socket_tasks[tp->task_socket] = tp;
}
void
task_reset_socket(tp)
task *tp;
{
trace(TR_TASK, 0, "task_reset_socket: task %s socket %d",
task_name(tp),
tp->task_socket);
FD_CLR(tp->task_socket, &task_select_readbits);
FD_CLR(tp->task_socket, &task_select_writebits);
FD_CLR(tp->task_socket, &task_select_exceptbits);
/* Delete from socket to task table if no routines present */
if (!task_socket_tasks[tp->task_socket]) {
trace(TR_ALL, LOG_ERR, "task_reset_socket: attempt to release socket %d by task %s - socket not assigned",
tp->task_socket,
task_name(tp));
quit(EBADF);
}
task_socket_tasks[tp->task_socket] = (task *) 0;
tp->task_socket = -1;
tp->task_flags &= ~(TASKF_CONNECT | TASKF_ACCEPT);
tp->task_recv = (void (*) ()) 0;
tp->task_accept = (void (*) ()) 0;
tp->task_write = (void (*) ()) 0;
tp->task_connect = (void (*) ()) 0;
tp->task_except = (void (*) ()) 0;
}
/*
* task_socket_options - Sets socket options. Isolates protocols from system layer.
*/
int
task_set_option(tp, option, value)
task *tp;
int option;
caddr_t value;
{
int opt;
int rc = 0;
int value_int;
int len = sizeof(value_int);
caddr_t ptr = (caddr_t) & value_int;
#ifdef LINGER_PARAM
struct linger linger;
#endif /* LINGER_PARAM */
int level = SOL_SOCKET;
tracef("task_set_option: task %s socket %d option %s(%d)",
task_name(tp),
tp->task_socket,
trace_state(task_socket_options, option),
option);
switch (option) {
case TASKOPTION_RECVBUF:
#ifdef SO_RCVBUF
opt = SO_RCVBUF;
goto int_value;
#else /* SO_RCVBUF */
break;
#endif /* SO_RCVBUF */
case TASKOPTION_SENDBUF:
#ifdef SO_SNDBUF
opt = SO_SNDBUF;
goto int_value;
#else /* SO_SNDBUF */
break;
#endif /* SO_SNDBUF */
case TASKOPTION_LINGER:
opt = SO_LINGER;
#ifdef LINGER_PARAM
linger.l_linger = (int) value;
linger.l_onoff = linger.l_linger ? TRUE : FALSE;
ptr = (caddr_t) & linger;
len = sizeof(struct linger);
tracef(" value { %d, %d }",
linger.l_linger,
linger.l_onoff);
#else /* LINGER_PARAM */
ptr = 0;
len = 0;
#endif /* LINGER_PARAM */
goto setsocketopt;
case TASKOPTION_REUSEADDR:
opt = SO_REUSEADDR;
goto int_value;
case TASKOPTION_BROADCAST:
#ifdef SO_BROADCAST
opt = SO_BROADCAST;
goto int_value;
#else /* SO_BROADCAST */
break;
#endif /* SO_BROADCAST */
case TASKOPTION_DONTROUTE:
opt = SO_DONTROUTE;
goto int_value;
case TASKOPTION_KEEPALIVE:
opt = SO_KEEPALIVE;
goto int_value;
case TASKOPTION_DEBUG:
opt = SO_DEBUG;
goto int_value;
case TASKOPTION_USELOOPBACK:
opt = SO_USELOOPBACK;
goto int_value;
int_value:
value_int = (int) value;
tracef(" value %d",
value);
/* goto setsocketopt; */
setsocketopt:
if (!test_flag) {
rc = setsockopt(tp->task_socket, level, opt, ptr, len);
}
break;
case TASKOPTION_NONBLOCKING:
value_int = (int) value;
tracef(" value %d",
value);
if (!test_flag) {
#ifdef SYSV
rc = fcntl(tp->task_socket, F_SETFL, O_NDELAY);
#else /* SYSV */
rc = task_ioctl(tp->task_socket, FIONBIO, (caddr_t) & value_int, sizeof (value_int));
#endif /* SYSV */
}
break;
default:
rc = -1;
errno = EINVAL;
}
if (rc < 0) {
trace(TR_ALL, LOG_ERR, ": %m");
} else {
trace(TR_TASK, 0, NULL);
}
return (rc);
}
/*
* task_init() set up for receiving signals and other initialization
*/
void
task_init()
{
#ifndef SYSV
struct sigvec vec, ovec;
/* Set up signals to block */
SIGNAL_LIST(ip) {
task_signal_mask |= sigmask(*ip);
} SIGNAL_LIST_END(ip) ;
/* Setup signal processing */
memset((char *) &vec, (char) 0, sizeof(struct sigvec));
vec.sv_mask = task_signal_mask;
vec.sv_handler = task_receive_signal;
#endif /* SYSV */
SIGNAL_LIST(ip) {
#ifdef SYSV
sigset (*ip, task_receive_signal);
#else /* SYSV */
if (sigvec(*ip, &vec, &ovec)) {
trace(TR_ALL, LOG_ERR, "task_init: sigvec SIG%s: %m", trace_state(signal_names, *ip));
quit(errno);
}
#endif /* SYSV */
} SIGNAL_LIST_END(ip) ;
}
/*
* Dump task information to dump file
*/
void
task_dump(fd)
FILE *fd;
{
int i;
int first;
int socket;
task *tp;
timer *tip;
/* Print out task blocks */
(void) fprintf(fd, "Task and Timers:\n\n");
TASK_TABLE(tp) {
(void) fprintf(fd, "\t%s",
task_name(tp));
if (tp->task_proto) {
(void) fprintf(fd, "\tProto %3d",
tp->task_proto);
}
if (ntohs(tp->task_addr.in.sin_port)) {
(void) fprintf(fd, "\tPort %5u",
ntohs(tp->task_addr.in.sin_port));
}
if (tp->task_socket != -1) {
(void) fprintf(fd, "\tSocket %2d",
tp->task_socket);
}
if (tp->task_rtproto) {
(void) fprintf(fd, "\tRtProto %s",
trace_bits(rt_proto_bits, tp->task_rtproto));
}
if (tp->task_flags) {
(void) fprintf(fd, "\t<%s>",
trace_bits(task_flag_bits, tp->task_flags));
}
(void) fprintf(fd, "\n");
first = TRUE;
for (i = 0; i < TASK_TIMERS; i++) {
if (tp->task_timer[i]) {
if (first) {
(void) fprintf(fd, "\n");
first = FALSE;
}
timer_dump(fd, tp->task_timer[i]);
}
}
(void) fprintf(fd, "\n");
} TASK_TABLEEND(tp);
(void) fprintf(fd, "\n");
/* Print timers that are not associated with tasks */
first = TRUE;
TIMER_ACTIVE(tip) {
if (!tip->timer_task) {
if (first) {
(void) fprintf(fd, "\tTimers without tasks:\n\n");
first = FALSE;
}
timer_dump(fd, tip);
}
} TIMER_ACTIVEEND(tip);
TIMER_INACTIVE(tip) {
if (!tip->timer_task) {
if (first) {
(void) fprintf(fd, "\tTimers without tasks:\n\n");
first = FALSE;
}
timer_dump(fd, tip);
}
} TIMER_INACTIVEEND(tip);
if (!first) {
(void) fprintf(fd, "\n");
}
/* Print mapping of sockets to tasks */
(void) fprintf(fd, "Task to socket mapping:\n\n");
for (socket = 0; socket <= task_max_socket; socket++) {
if (tp = task_socket_tasks[socket]) {
(void) fprintf(fd, "\tsocket: %d\ttask: %s\n",
socket,
task_name(tp));
}
}
(void) fprintf(fd, "\n");
/* Do task-specific dumps */
TASK_TABLE(tp) {
if (tp->task_dump) {
tp->task_dump(fd);
}
} TASK_TABLEEND(tp);
}
static bits domains[AF_MAX + 1] =
{
{AF_UNSPEC, "UNSPEC"},
{AF_UNIX, "UNIX"},
{AF_INET, "INET"},
{AF_IMPLINK, "IMPLINK"},
{AF_PUP, "PUP"},
{AF_CHAOS, "CHAOS"},
{AF_NS, "NS"},
#ifdef AF_ISO
{AF_ISO, "ISO"},
#else /* AF_ISO */
{AF_NBS, "NBS"},
#endif /* AF_ISO */
{AF_ECMA, "ECMA"},
{AF_DATAKIT, "DATAKIT"},
{AF_CCITT, "CCITT"},
{AF_SNA, "SNA"},
#ifdef AF_DECnet
{AF_DECnet, "DECnet"},
{AF_DLI, "DLI"},
{AF_LAT, "LAT"},
{AF_HYLINK, "HYLINK"},
{AF_APPLETALK, "APPLETALK"},
#endif /* AF_DECnet */
#ifdef AF_ROUTE
{AF_ROUTE, "Route"},
#endif /* AF_ROUTE */
#ifdef AF_NIT
{AF_NIT, "NIT"},
#endif /* AF_NIT */
};
static bits types[5] =
{
{SOCK_STREAM, "STREAM"},
{SOCK_DGRAM, "DGRAM"},
{SOCK_RAW, "RAW"},
{SOCK_RDM, "RDM"},
{SOCK_SEQPACKET, "SEQPACKET"},
};
/*
* task_get_socket gets a socket, retries later if no buffers at present
*/
int
task_get_socket(domain, type, protocol)
int domain, type, protocol;
{
int retry, get_socket, error;
if (test_flag) {
if (task_max_socket) {
get_socket = ++task_max_socket;
} else {
/* Skip first few that may be used for logging */
get_socket = (task_max_socket = 2);
}
} else {
retry = 2; /* if no buffers a retry might work */
while ((get_socket = socket(domain, type, protocol)) < 0 && retry--) {
error = errno;
trace(TR_ALL, LOG_ERR, "task_get_socket: socket: %m");
if (error == ENOBUFS) {
sleep(5);
} else {
break;
}
}
}
trace(TR_TASK, 0, "task_get_socket: domain AF_%s type SOCK_%s protocol %d socket %d",
trace_state(domains, domain),
trace_state(types, type - 1),
protocol,
get_socket);
if (get_socket >= getdtablesize()) {
trace(TR_ALL, LOG_ERR, "task_get_socket: Too many sockets for select mask");
quit(EMFILE);
}
if (get_socket > task_max_socket) {
task_max_socket = get_socket;
}
return (get_socket);
}
/*
* Path names
*/
#ifndef vax11c
char *
task_getwd()
{
static char path_name[MAXPATHLEN];
if (!getwd(path_name)) {
trace(TR_ALL, LOG_ERR, "task_getwd: getwd: %s",
path_name);
quit(ENOENT);
}
return path_name;
}
int
task_chdir(path_name)
char *path_name;
{
int rc;
if (rc = chdir(path_name)) {
trace(TR_ALL, LOG_ERR, "task_cwd: chdir: %m");
}
return rc;
}
void
task_getpaths()
{
char *path = task_getwd();
int len = strlen(path);
/* Remember directory we were started from */
task_path_name = (caddr_t) malloc(len);
strcpy(task_path_name, path);
}
#endif /* vax11c */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.