/* Copyright (c) 1997-1999 Miller Puckette. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ /* Pd side of the Pd/Pd-gui interface. Also, some system interface routines that didn't really belong anywhere. */ #include "m_pd.h" #include "s_stuff.h" #include "m_imp.h" #ifdef UNIX #include #include #include #include #include #include #include #include #endif #ifdef HAVE_BSTRING_H #include #endif #ifdef MSW #include #include #include #include typedef int pid_t; #define EADDRINUSE WSAEADDRINUSE #endif #include #include #include #include #include #include #ifdef MACOSX #include #include #include #else #include #endif #define DEBUG_MESSUP 1 /* messages up from pd to pd-gui */ #define DEBUG_MESSDOWN 2 /* messages down from pd-gui to pd */ /* T.Grill - make it a _little_ more adaptable... */ #ifndef PDBINDIR #define PDBINDIR "bin/" #endif #ifndef WISHAPP #define WISHAPP "wish83.exe" #endif extern char pd_version[]; typedef struct _fdpoll { int fdp_fd; t_fdpollfn fdp_fn; void *fdp_ptr; } t_fdpoll; #define INBUFSIZE 4096 struct _socketreceiver { char *sr_inbuf; int sr_inhead; int sr_intail; void *sr_owner; int sr_udp; t_socketnotifier sr_notifier; t_socketreceivefn sr_socketreceivefn; }; static int sys_nfdpoll; static t_fdpoll *sys_fdpoll; static int sys_maxfd; static int sys_guisock; static t_binbuf *inbinbuf; static t_socketreceiver *sys_socketreceiver; extern int sys_addhist(int phase); #ifdef MSW static LARGE_INTEGER nt_inittime; static double nt_freq = 0; static void sys_initntclock(void) { LARGE_INTEGER f1; LARGE_INTEGER now; QueryPerformanceCounter(&now); if (!QueryPerformanceFrequency(&f1)) { fprintf(stderr, "pd: QueryPerformanceFrequency failed\n"); f1.QuadPart = 1; } nt_freq = f1.QuadPart; nt_inittime = now; } #if 0 /* this is a version you can call if you did the QueryPerformanceCounter call yourself. Necessary for time tagging incoming MIDI at interrupt level, for instance; but we're not doing that just now. */ double nt_tixtotime(LARGE_INTEGER *dumbass) { if (nt_freq == 0) sys_initntclock(); return (((double)(dumbass->QuadPart - nt_inittime.QuadPart)) / nt_freq); } #endif #endif /* MSW */ /* get "real time" in seconds; take the first time we get called as a reference time of zero. */ double sys_getrealtime(void) { #ifdef UNIX static struct timeval then; struct timeval now; gettimeofday(&now, 0); if (then.tv_sec == 0 && then.tv_usec == 0) then = now; return ((now.tv_sec - then.tv_sec) + (1./1000000.) * (now.tv_usec - then.tv_usec)); #endif #ifdef MSW LARGE_INTEGER now; QueryPerformanceCounter(&now); if (nt_freq == 0) sys_initntclock(); return (((double)(now.QuadPart - nt_inittime.QuadPart)) / nt_freq); #endif } void sys_sockerror(char *s) { #ifdef MSW int err = WSAGetLastError(); if (err == 10054) return; else if (err == 10044) { fprintf(stderr, "Warning: you might not have TCP/IP \"networking\" turned on\n"); fprintf(stderr, "which is needed for Pd to talk to its GUI layer.\n"); } #endif #ifdef UNIX int err = errno; #endif fprintf(stderr, "%s: %s (%d)\n", s, strerror(err), err); } void sys_addpollfn(int fd, t_fdpollfn fn, void *ptr) { int nfd = sys_nfdpoll; int size = nfd * sizeof(t_fdpoll); t_fdpoll *fp; sys_fdpoll = (t_fdpoll *)t_resizebytes(sys_fdpoll, size, size + sizeof(t_fdpoll)); fp = sys_fdpoll + nfd; fp->fdp_fd = fd; fp->fdp_fn = fn; fp->fdp_ptr = ptr; sys_nfdpoll = nfd + 1; if (fd >= sys_maxfd) sys_maxfd = fd + 1; } void sys_rmpollfn(int fd) { int nfd = sys_nfdpoll; int i, size = nfd * sizeof(t_fdpoll); t_fdpoll *fp; for (i = nfd, fp = sys_fdpoll; i--; fp++) { if (fp->fdp_fd == fd) { while (i--) { fp[0] = fp[1]; fp++; } sys_fdpoll = (t_fdpoll *)t_resizebytes(sys_fdpoll, size, size - sizeof(t_fdpoll)); sys_nfdpoll = nfd - 1; return; } } post("warning: %d removed from poll list but not found", fd); } static int sys_domicrosleep(int microsec, int pollem) { struct timeval timout; int i, didsomething = 0; t_fdpoll *fp; timout.tv_sec = 0; timout.tv_usec = microsec; if (pollem) { fd_set readset, writeset, exceptset; FD_ZERO(&writeset); FD_ZERO(&readset); FD_ZERO(&exceptset); for (fp = sys_fdpoll, i = sys_nfdpoll; i--; fp++) FD_SET(fp->fdp_fd, &readset); select(sys_maxfd+1, &readset, &writeset, &exceptset, &timout); for (i = 0; i < sys_nfdpoll; i++) if (FD_ISSET(sys_fdpoll[i].fdp_fd, &readset)) { (*sys_fdpoll[i].fdp_fn)(sys_fdpoll[i].fdp_ptr, sys_fdpoll[i].fdp_fd); didsomething = 1; } return (didsomething); } else { select(0, 0, 0, 0, &timout); return (0); } } void sys_microsleep(int microsec) { sys_domicrosleep(microsec, 1); } t_socketreceiver *socketreceiver_new(void *owner, t_socketnotifier notifier, t_socketreceivefn socketreceivefn, int udp) { t_socketreceiver *x = (t_socketreceiver *)getbytes(sizeof(*x)); x->sr_inhead = x->sr_intail = 0; x->sr_owner = owner; x->sr_notifier = notifier; x->sr_socketreceivefn = socketreceivefn; x->sr_udp = udp; if (!(x->sr_inbuf = malloc(INBUFSIZE))) bug("t_socketreceiver");; return (x); } void socketreceiver_free(t_socketreceiver *x) { free(x->sr_inbuf); freebytes(x, sizeof(*x)); } /* this is in a separately called subroutine so that the buffer isn't sitting on the stack while the messages are getting passed. */ static int socketreceiver_doread(t_socketreceiver *x) { char messbuf[INBUFSIZE], *bp = messbuf; int indx; int inhead = x->sr_inhead; int intail = x->sr_intail; char *inbuf = x->sr_inbuf; if (intail == inhead) return (0); for (indx = intail; indx != inhead; indx = (indx+1)&(INBUFSIZE-1)) { /* if we hit a semi that isn't preceeded by a \, it's a message boundary. LATER we should deal with the possibility that the preceeding \ might itself be escaped! */ char c = *bp++ = inbuf[indx]; if (c == ';' && (!indx || inbuf[indx-1] != '\\')) { intail = (indx+1)&(INBUFSIZE-1); binbuf_text(inbinbuf, messbuf, bp - messbuf); if (sys_debuglevel & DEBUG_MESSDOWN) { write(2, messbuf, bp - messbuf); write(2, "\n", 1); } x->sr_inhead = inhead; x->sr_intail = intail; return (1); } } return (0); } static void socketreceiver_getudp(t_socketreceiver *x, int fd) { char buf[INBUFSIZE+1]; int ret = recv(fd, buf, INBUFSIZE, 0); if (ret < 0) { sys_sockerror("recv"); sys_rmpollfn(fd); sys_closesocket(fd); } else if (ret > 0) { buf[ret] = 0; #if 0 post("%s", buf); #endif if (buf[ret-1] != '\n') { #if 0 buf[ret] = 0; error("dropped bad buffer %s\n", buf); #endif } else { char *semi = strchr(buf, ';'); if (semi) *semi = 0; binbuf_text(inbinbuf, buf, strlen(buf)); outlet_setstacklim(); if (x->sr_socketreceivefn) (*x->sr_socketreceivefn)(x->sr_owner, inbinbuf); else bug("socketreceiver_getudp"); } } } void socketreceiver_read(t_socketreceiver *x, int fd) { if (x->sr_udp) /* UDP ("datagram") socket protocol */ socketreceiver_getudp(x, fd); else /* TCP ("streaming") socket protocol */ { char *semi; int readto = (x->sr_inhead >= x->sr_intail ? INBUFSIZE : x->sr_intail-1); int ret; /* the input buffer might be full. If so, drop the whole thing */ if (readto == x->sr_inhead) { fprintf(stderr, "pd: dropped message from gui\n"); x->sr_inhead = x->sr_intail = 0; readto = INBUFSIZE; } else { ret = recv(fd, x->sr_inbuf + x->sr_inhead, readto - x->sr_inhead, 0); if (ret < 0) { sys_sockerror("recv"); if (x == sys_socketreceiver) sys_bail(1); else { if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); sys_rmpollfn(fd); sys_closesocket(fd); } } else if (ret == 0) { if (x == sys_socketreceiver) { fprintf(stderr, "pd: exiting\n"); sys_bail(0); } else { post("EOF on socket %d\n", fd); if (x->sr_notifier) (*x->sr_notifier)(x->sr_owner); sys_rmpollfn(fd); sys_closesocket(fd); } } else { x->sr_inhead += ret; if (x->sr_inhead >= INBUFSIZE) x->sr_inhead = 0; while (socketreceiver_doread(x)) { outlet_setstacklim(); if (x->sr_socketreceivefn) (*x->sr_socketreceivefn)(x->sr_owner, inbinbuf); else binbuf_eval(inbinbuf, 0, 0, 0); } } } } } void sys_closesocket(int fd) { #ifdef UNIX close(fd); #endif #ifdef MSW closesocket(fd); #endif } void sys_gui(char *s) { int length = strlen(s), written = 0, res, histwas = sys_addhist(4); if (sys_debuglevel & DEBUG_MESSUP) fprintf(stderr, "%s", s); if (sys_nogui) return; while (1) { res = send(sys_guisock, s + written, length, 0); if (res < 0) { perror("pd output pipe"); sys_bail(1); } else { written += res; if (written >= length) break; } } sys_addhist(histwas); } /* LATER should do a bounds check -- but how do you get printf to do that? See also rtext_senditup() in this regard */ void sys_vgui(char *fmt, ...) { int result, i; char buf[2048]; va_list ap; va_start(ap, fmt); vsprintf(buf, fmt, ap); sys_gui(buf); va_end(ap); } #define FIRSTPORTNUM 5400 /* -------------- signal handling for UNIX -------------- */ #ifdef UNIX typedef void (*sighandler_t)(int); static void sys_signal(int signo, sighandler_t sigfun) { struct sigaction action; action.sa_flags = 0; action.sa_handler = sigfun; memset(&action.sa_mask, 0, sizeof(action.sa_mask)); #if 0 /* GG says: don't use that */ action.sa_restorer = 0; #endif if (sigaction(signo, &action, 0) < 0) perror("sigaction"); } static void sys_exithandler(int n) { static int trouble = 0; if (!trouble) { trouble = 1; fprintf(stderr, "Pd: signal %d\n", n); sys_bail(1); } else _exit(1); } static void sys_alarmhandler(int n) { fprintf(stderr, "Pd: system call timed out\n"); } static void sys_huphandler(int n) { struct timeval timout; timout.tv_sec = 0; timout.tv_usec = 30000; select(1, 0, 0, 0, &timout); } void sys_setalarm(int microsec) { struct itimerval gonzo; #if 0 fprintf(stderr, "timer %d\n", microsec); #endif gonzo.it_interval.tv_sec = 0; gonzo.it_interval.tv_usec = 0; gonzo.it_value.tv_sec = 0; gonzo.it_value.tv_usec = microsec; if (microsec) sys_signal(SIGALRM, sys_alarmhandler); else sys_signal(SIGALRM, SIG_IGN); setitimer(ITIMER_REAL, &gonzo, 0); } #endif #ifdef __linux #if defined(_POSIX_PRIORITY_SCHEDULING) || defined(_POSIX_MEMLOCK) #include #endif void sys_set_priority(int higher) { #ifdef _POSIX_PRIORITY_SCHEDULING struct sched_param par; int p1 ,p2, p3; p1 = sched_get_priority_min(SCHED_FIFO); p2 = sched_get_priority_max(SCHED_FIFO); p3 = (higher ? p2 - 1 : p2 - 3); par.sched_priority = p3; if (sched_setscheduler(0,SCHED_FIFO,&par) != -1) fprintf(stderr, "priority %d scheduling enabled.\n", p3); #endif #ifdef _POSIX_MEMLOCK if (mlockall(MCL_FUTURE) != -1) fprintf(stderr, "memory locking enabled.\n"); #endif } #endif /* __linux */ static int sys_watchfd; #ifdef __linux__ void glob_ping(t_pd *dummy) { if (write(sys_watchfd, "\n", 1) < 1) { fprintf(stderr, "pd: watchdog process died\n"); sys_bail(1); } } #endif static int defaultfontshit[] = { 8, 5, 9, 10, 6, 10, 12, 7, 13, 14, 9, 17, 16, 10, 19, 24, 15, 28, 24, 15, 28}; int sys_startgui(const char *guidir) { pid_t childpid; char cmdbuf[4*MAXPDSTRING]; struct sockaddr_in server; int msgsock; char buf[15]; int len = sizeof(server); int ntry = 0, portno = FIRSTPORTNUM; int xsock = -1; #ifdef MSW short version = MAKEWORD(2, 0); WSADATA nobby; #endif #ifdef UNIX int stdinpipe[2]; #endif /* create an empty FD poll list */ sys_fdpoll = (t_fdpoll *)t_getbytes(0); sys_nfdpoll = 0; inbinbuf = binbuf_new(); #ifdef UNIX signal(SIGHUP, sys_huphandler); signal(SIGINT, sys_exithandler); signal(SIGQUIT, sys_exithandler); signal(SIGILL, sys_exithandler); signal(SIGIOT, sys_exithandler); signal(SIGFPE, SIG_IGN); /* signal(SIGILL, sys_exithandler); signal(SIGBUS, sys_exithandler); signal(SIGSEGV, sys_exithandler); */ signal(SIGPIPE, sys_exithandler); signal(SIGALRM, SIG_IGN); signal(SIGTERM, SIG_IGN); #if 0 /* GG says: don't use that */ signal(SIGSTKFLT, sys_exithandler); #endif #endif #ifdef MSW if (WSAStartup(version, &nobby)) sys_sockerror("WSAstartup"); #endif if (sys_nogui) { /* fake the GUI's message giving cwd and font sizes; then skip starting the GUI up. */ t_atom zz[19]; int i; #ifdef MSW if (GetCurrentDirectory(MAXPDSTRING, cmdbuf) == 0) strcpy(cmdbuf, "."); #endif #ifdef UNIX if (!getcwd(cmdbuf, MAXPDSTRING)) strcpy(cmdbuf, "."); #endif SETSYMBOL(zz, gensym(cmdbuf)); for (i = 1; i < 22; i++) SETFLOAT(zz + i, defaultfontshit[i-1]); SETFLOAT(zz+22,0); glob_initfromgui(0, 0, 23, zz); } else { #ifdef MSW char scriptbuf[MAXPDSTRING+30], wishbuf[MAXPDSTRING+30], portbuf[80]; int spawnret; #endif #ifdef MSW char intarg; #else int intarg; #endif /* create a socket */ xsock = socket(AF_INET, SOCK_STREAM, 0); if (xsock < 0) sys_sockerror("socket"); #if 0 intarg = 0; if (setsockopt(xsock, SOL_SOCKET, SO_SNDBUF, &intarg, sizeof(intarg)) < 0) post("setsockopt (SO_RCVBUF) failed\n"); intarg = 0; if (setsockopt(xsock, SOL_SOCKET, SO_RCVBUF, &intarg, sizeof(intarg)) < 0) post("setsockopt (SO_RCVBUF) failed\n"); #endif intarg = 1; if (setsockopt(xsock, IPPROTO_TCP, TCP_NODELAY, &intarg, sizeof(intarg)) < 0) #ifndef MSW post("setsockopt (TCP_NODELAY) failed\n") #endif ; server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; /* assign server port number */ server.sin_port = htons((unsigned short)portno); /* name the socket */ while (bind(xsock, (struct sockaddr *)&server, sizeof(server)) < 0) { #ifdef MSW int err = WSAGetLastError(); #endif #ifdef UNIX int err = errno; #endif if ((ntry++ > 20) || (err != EADDRINUSE)) { perror("bind"); fprintf(stderr, "Pd needs your machine to be configured with\n"); fprintf(stderr, "'networking' turned on (see Pd's html doc for details.)\n"); exit(1); } portno++; server.sin_port = htons((unsigned short)(portno)); } if (sys_verbose) fprintf(stderr, "port %d\n", portno); sys_socketreceiver = socketreceiver_new(0, 0, 0, 0); #ifdef UNIX childpid = fork(); if (childpid < 0) { if (errno) perror("sys_startgui"); else fprintf(stderr, "sys_startgui failed\n"); return (1); } else if (!childpid) /* we're the child */ { seteuid(getuid()); /* lose setuid priveliges */ #ifndef MACOSX /* the wish process in Unix will make a wish shell and read/write standard in and out unless we close the file descriptors. Somehow this doesn't make the MAC OSX version of Wish happy...*/ if (pipe(stdinpipe) < 0) sys_sockerror("pipe"); else { if (stdinpipe[0] != 0) { close (0); dup2(stdinpipe[0], 0); close(stdinpipe[0]); } } #endif if (!sys_guicmd) { #ifdef MACOSX char *homedir = getenv("HOME"), filename[250]; struct stat statbuf; if (!homedir || strlen(homedir) > 150) goto nohomedir; sprintf(filename, "%s/Applications/Utilities/Wish shell.app/Contents/MacOS/Wish Shell", homedir); if (stat(filename, &statbuf) >= 0) goto foundit; sprintf(filename, "%s/Applications/Wish shell.app/Contents/MacOS/Wish Shell", homedir); if (stat(filename, &statbuf) >= 0) goto foundit; nohomedir: strcpy(filename, "/Applications/Utilities/Wish Shell.app/Contents/MacOS/Wish Shell"); if (stat(filename, &statbuf) >= 0) goto foundit; strcpy(filename, "/Applications/Wish Shell.app/Contents/MacOS/Wish Shell"); foundit: sprintf(cmdbuf, "\"%s\" %s/pd.tk %d\n", filename, guidir, portno); #else sprintf(cmdbuf, "TCL_LIBRARY=\"%s/tcl/library\" TK_LIBRARY=\"%s/tk/library\" \ \"%s/pd-gui\" %d\n", sys_libdir->s_name, sys_libdir->s_name, guidir, portno); #endif sys_guicmd = cmdbuf; } if (sys_verbose) fprintf(stderr, "%s", sys_guicmd); execl("/bin/sh", "sh", "-c", sys_guicmd, 0); perror("pd: exec"); _exit(1); } #endif /* UNIX */ #ifdef MSW /* in MSW land "guipath" is unused; we just do everything from the libdir. */ /* fprintf(stderr, "%s\n", sys_libdir->s_name); */ strcpy(scriptbuf, "\""); strcat(scriptbuf, sys_libdir->s_name); strcat(scriptbuf, "/" PDBINDIR "pd.tk\""); sys_bashfilename(scriptbuf, scriptbuf); sprintf(portbuf, "%d", portno); strcpy(wishbuf, sys_libdir->s_name); strcat(wishbuf, "/" PDBINDIR WISHAPP); sys_bashfilename(wishbuf, wishbuf); spawnret = _spawnl(P_NOWAIT, wishbuf, WISHAPP, scriptbuf, portbuf, 0); if (spawnret < 0) { perror("spawnl"); fprintf(stderr, "%s: couldn't load TCL\n", wishbuf); exit(1); } #endif /* MSW */ } #ifdef __linux__ /* now that we've spun off the child process we can promote our process's priority, if we happen to be root. */ if (sys_hipriority) { if (!getuid() || !geteuid()) { /* To prevent lockup, we fork off a watchdog process with higher real-time priority than ours. The GUI has to send a stream of ping messages to the watchdog THROUGH the Pd process which has to pick them up from the GUI and forward them. If any of these things aren't happening the watchdog starts sending "stop" and "cont" signals to the Pd process to make it timeshare with the rest of the system. (Version 0.33P2 : if there's no GUI, the watchdog pinging is done from the scheduler idle routine in this process instead.) */ int pipe9[2], watchpid; if (pipe(pipe9) < 0) { seteuid(getuid()); /* lose setuid priveliges */ sys_sockerror("pipe"); return (1); } watchpid = fork(); if (watchpid < 0) { seteuid(getuid()); /* lose setuid priveliges */ if (errno) perror("sys_startgui"); else fprintf(stderr, "sys_startgui failed\n"); return (1); } else if (!watchpid) /* we're the child */ { sys_set_priority(1); seteuid(getuid()); /* lose setuid priveliges */ if (pipe9[1] != 0) { dup2(pipe9[0], 0); close(pipe9[0]); } close(pipe9[1]); sprintf(cmdbuf, "%s/pd-watchdog\n", guidir); if (sys_verbose) fprintf(stderr, "%s", cmdbuf); execl("/bin/sh", "sh", "-c", cmdbuf, 0); perror("pd: exec"); _exit(1); } else /* we're the parent */ { sys_set_priority(0); seteuid(getuid()); /* lose setuid priveliges */ close(pipe9[0]); sys_watchfd = pipe9[1]; /* We also have to start the ping loop in the GUI; this is done later when the socket is open. */ } } else { post("realtime setting failed because not root\n"); sys_hipriority = 0; } } seteuid(getuid()); /* lose setuid priveliges */ #endif /* __linux__ */ #ifdef MSW if (!SetPriorityClass(GetCurrentProcess(), HIGH_PRIORITY_CLASS)) fprintf(stderr, "pd: couldn't set high priority class\n"); #endif #ifdef MACOSX if (sys_hipriority) { struct sched_param param; int policy = SCHED_RR; int err; param.sched_priority = 80; // adjust 0 : 100 err = pthread_setschedparam(pthread_self(), policy, ¶m); if (err) post("warning: high priority scheduling failed\n"); } #endif /* MACOSX */ if (!sys_nogui) { char buf[256]; if (sys_verbose) fprintf(stderr, "Waiting for connection request... \n"); if (listen(xsock, 5) < 0) sys_sockerror("listen"); sys_guisock = accept(xsock, (struct sockaddr *) &server, &len); #ifdef OOPS close(xsock); #endif if (sys_guisock < 0) sys_sockerror("accept"); sys_addpollfn(sys_guisock, (t_fdpollfn)socketreceiver_read, sys_socketreceiver); if (sys_verbose) fprintf(stderr, "... connected\n"); /* here is where we start the pinging. */ #ifdef __linux__ if (sys_hipriority) sys_gui("pdtk_watchdog\n"); #endif sys_get_audio_apis(buf); sys_vgui("pdtk_pd_startup {%s} %s\n", pd_version, buf); } return (0); } static int sys_poll_togui(void) { /* LATER use this to flush output buffer to gui */ return (0); } int sys_pollgui(void) { return (sys_domicrosleep(0, 1) || sys_poll_togui()); } /* T.Grill - import clean quit function */ extern void sys_exit(void); /* This is called when something bad has happened, like a segfault. Call glob_quit() below to exit cleanly. LATER try to save dirty documents even in the bad case. */ void sys_bail(int n) { static int reentered = 0; if (!reentered) { reentered = 1; #ifndef __linux /* sys_close_audio() hangs if you're in a signal? */ fprintf(stderr, "closing audio...\n"); sys_close_audio(); fprintf(stderr, "closing MIDI...\n"); sys_close_midi(); fprintf(stderr, "... done.\n"); #endif exit(1); } else _exit(n); } void glob_quit(void *dummy) { sys_vgui("exit\n"); if (!sys_nogui) { close(sys_guisock); sys_rmpollfn(sys_guisock); } sys_bail(0); }