/* Copyright (c) 1992,1993,1994,1995,1996,1997,2000. The Regents of the University of California (Regents). All Rights Reserved. Permission to use, copy, modify, and distribute this software and its documentation for educational, research, and not-for-profit purposes, without fee and without a signed licensing agreement, is hereby granted, provided that the above copyright notice, this paragraph and the following two paragraphs appear in all copies, modifications, and distributions. Contact The Office of Technology Licensing, UC Berkeley, 2150 Shattuck Avenue, Suite 510, Berkeley, CA 94720-1620, (510) 643-7201, for commercial licensing opportunities. Written by Matt Wright and Adrian Freed, The Center for New Music and Audio Technologies, University of California, Berkeley. IN NO EVENT SHALL REGENTS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF REGENTS HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. REGENTS SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE AND ACCOMPANYING DOCUMENTATION, IF ANY, PROVIDED HEREUNDER IS PROVIDED "AS IS". REGENTS HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. */ /* dumpOSC.c server that displays OpenSoundControl messages sent to it for debugging client udp and UNIX protocol by Matt Wright, 6/3/97 modified from dumpSC.c, by Matt Wright and Adrian Freed version 0.2: Added "-silent" option a.k.a. "-quiet" version 0.3: Incorporated patches from Nicola Bernardini to make things Linux-friendly. Also added ntohl() in the right places to support little-endian architectures. compile: cc -o dumpOSC dumpOSC.c to-do: More robustness in saying exactly what's wrong with ill-formed messages. (If they don't make sense, show exactly what was received.) Time-based features: print time-received for each packet Clean up to separate OSC parsing code from socket/select stuff pd ------------- -- tweaks for Win32 www.zeggz.com/raf 13-April-2002 */ #include "m_pd.h" #include "s_stuff.h" //#include "x_osc.h" /* declarations */ // typedef void (*t_fdpollfn)(void *ptr, int fd); void sys_addpollfn(int fd, t_fdpollfn fn, void *ptr); #if defined(__sgi) || defined(__linux) || defined(WIN32) #ifdef WIN32 #include "OSC-common.h" #include #include #include #include #include #include #include #include #else #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include //#include #ifdef NEED_SCHEDCTL_AND_LOCK #include #include #endif #endif char *htm_error_string; typedef int Boolean; typedef void *OBJ; typedef struct ClientAddressStruct { struct sockaddr_in cl_addr; int clilen; int sockfd; } *ClientAddr; typedef unsigned long long osc_time_t; Boolean ShowBytes = FALSE; Boolean Silent = FALSE; /* Declarations */ #ifndef WIN32 static int unixinitudp(int chan); #endif static int initudp(int chan); static void closeudp(int sockfd); Boolean ClientReply(int packetsize, void *packet, int socketfd, void *clientaddresspointer, int clientaddressbufferlength); void sgi_CleanExit(void); Boolean sgi_HaveToQuit(void); int RegisterPollingDevice(int fd, void (*callbackfunction)(int , void *), void *dummy); static void catch_sigint(); static int Synthmessage(char *m, int n, void *clientdesc, int clientdesclength, int fd) ; char *DataAfterAlignedString(char *string, char *boundary) ; Boolean IsNiceString(char *string, char *boundary) ; void complain(char *s, ...); #define MAXMESG 32768 static char mbuf[MAXMESG]; /* ----------------------------- dumpOSC ------------------------- */ #define MAXOUTAT 50 static t_class *dumpOSC_class; typedef struct _dumpOSC { t_object x_obj; t_outlet *x_msgout; t_outlet *x_connectout; t_atom x_outat[MAXOUTAT]; int x_outatc; t_binbuf *x_b; int x_connectsocket; int x_nconnections; int x_udp; struct sockaddr_in x_server; int x_clilen; } t_dumpOSC; void dumpOSC_ParsePacket(t_dumpOSC *x, char *buf, int n, ClientAddr returnAddr); Boolean dumpOSC_SendReply(char *buf, int n, void *clientDesc, int clientDescLenght, int fd); static void dumpOSC_Smessage(t_dumpOSC *x, char *address, void *v, int n, ClientAddr returnAddr); static void dumpOSC_PrintTypeTaggedArgs(t_dumpOSC *x, void *v, int n); static void dumpOSC_PrintHeuristicallyTypeGuessedArgs(t_dumpOSC *x, void *v, int n, int skipComma); static void dumpOSC_read(t_dumpOSC *x, int sockfd) { int clilen = x->x_clilen; int n; struct ClientAddressStruct ras; ClientAddr ra = &ras; //catchupflag= FALSE; /* if (ShowBytes) { */ /* int i; */ /* printf("%d byte message:\n", n); */ /* for (i = 0; i < n; ++i) { */ /* printf(" %x (%c)\t", m[i], m[i]); */ /* if (i%4 == 3) printf("\n"); */ /* } */ /* printf("\n"); */ /* } */ // return catchupflag; //struct sockaddr_in x->x_server; //while( (n = recvfrom(sockfd, mbuf, MAXMESG, 0, &cl_addr, &clilen)) >0) // while(( #ifdef WIN32 if ((n = recvfrom(sockfd, mbuf, MAXMESG, 0, (SOCKADDR*)&x->x_server, &clilen)) >0) #else if ((n = recvfrom(sockfd, mbuf, MAXMESG, 0, (struct sockaddr *)&x->x_server, &clilen)) >0) #endif { //int r; ras.cl_addr = *((struct sockaddr_in *) &x->x_server); ras.clilen = x->x_clilen; ras.sockfd = x->x_connectsocket; #ifdef DEBUG printf("dumpOSC_read: received UDP packet of length %d\n", n); #endif if(!dumpOSC_SendReply(mbuf, n, &x->x_server, clilen, sockfd)) { dumpOSC_ParsePacket(x, mbuf, n, ra); } //r = Synthmessage(mbuf, n, &x->x_server, clilen, sockfd); //post ("%d", r); //outlet_anything(x->x_msgout, at[msg].a_w.w_symbol, // emsg-msg-1, at + msg + 1); // outlet_list(x->x_msgout, 0, n, mbuf); //if( sgi_HaveToQuit()) goto out; //if(r>0) goto back; //clilen = maxclilen; } } static void *dumpOSC_new(t_symbol *compatflag, t_floatarg fportno) { t_dumpOSC *x; struct sockaddr_in server; int clilen=sizeof(server); int sockfd; int portno=fportno; int udp = 1; //x->x_b = binbuf_new(); //x->x_outat = binbuf_getvec(x->x_b); //{{raf}} pointer not valid yet...moving this down //x->x_outatc = 0; {{raf}} /* create a socket */ if ((sockfd = socket(AF_INET, (udp ? SOCK_DGRAM : SOCK_STREAM), 0)) == -1) { sys_sockerror("socket"); return (0); } server.sin_family = AF_INET; server.sin_addr.s_addr = INADDR_ANY; /* assign server port number */ server.sin_port = htons((u_short)portno); /* name the socket */ if (bind(sockfd, (struct sockaddr *)&server, sizeof(server)) < 0) { sys_sockerror("bind"); sys_closesocket(sockfd); return (0); } x = (t_dumpOSC *)pd_new(dumpOSC_class); x->x_outatc = 0; // {{raf}} now pointer is valid (less invalid) x->x_msgout = outlet_new(&x->x_obj, &s_anything); // if (udp) /* datagram protocol */ { sys_addpollfn(sockfd, (t_fdpollfn)dumpOSC_read, x); x->x_connectout = 0; } // else /* streaming protocol */ /* { */ /* if (listen(sockfd, 5) < 0) */ /* { */ /* sys_sockerror("listen"); */ /* sys_closesocket(sockfd); */ /* sockfd = -1; */ /* } */ /* else */ /* { */ /* sys_addpollfn(sockfd, (t_fdpollfn)dumpOSC_connectpoll, x); */ /* x->x_connectout = outlet_new(&x->x_obj, &s_float); */ /* } */ /* } */ x->x_connectsocket = sockfd; x->x_server = server; x->x_clilen = clilen; x->x_nconnections = 0; x->x_udp = udp; return (x); } static void dumpOSC_free(t_dumpOSC *x) { /* LATER make me clean up open connections */ if (x->x_connectsocket >= 0) { sys_rmpollfn(x->x_connectsocket); sys_closesocket(x->x_connectsocket); } } #ifdef WIN32 OSC_API void dumpOSC_setup(void) #else void dumpOSC_setup(void) #endif { dumpOSC_class = class_new(gensym("dumpOSC"), (t_newmethod)dumpOSC_new, (t_method)dumpOSC_free, sizeof(t_dumpOSC), CLASS_NOINLET, A_DEFFLOAT, A_DEFFLOAT, A_DEFSYM, 0); class_sethelpsymbol(dumpOSC_class, gensym("OSC/dumpOSC-help.pd")); } #ifndef WIN32 #define UNIXDG_PATH "/tmp/htm" #define UNIXDG_TMP "/tmp/htm.XXXXXX" static int unixinitudp(int chan) { struct sockaddr_un serv_addr; int sockfd; if((sockfd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) return sockfd; bzero((char *)&serv_addr, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; strcpy(serv_addr.sun_path, UNIXDG_PATH); sprintf(serv_addr.sun_path+strlen(serv_addr.sun_path), "%d", chan); unlink(serv_addr.sun_path); if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr.sun_family)+strlen(serv_addr.sun_path)) < 0) { perror("unable to bind\n"); return -1; } fcntl(sockfd, F_SETFL, FNDELAY); return sockfd; } #endif // #ifndef WIN32 static int initudp(int chan) { #ifdef WIN32 struct sockaddr_in serv_addr; unsigned int sockfd; ULONG nonBlocking = (ULONG) TRUE; if( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) != INVALID_SOCKET ) { ZeroMemory((char *)&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(chan); if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) >= 0) { // set for non-blocking mode if(ioctlsocket(sockfd, FIONBIO, &nonBlocking) == SOCKET_ERROR) { perror("unable to set non-blocking\n"); return -1; } } else { perror("unable to bind\n"); return -1; } } return (sockfd == INVALID_SOCKET ? -1 : (int)sockfd); #else struct sockaddr_in serv_addr; int sockfd; if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) return sockfd; bzero((char *)&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(chan); if(bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { perror("unable to bind\n"); return -1; } fcntl(sockfd, F_SETFL, FNDELAY); return sockfd; #endif } static void closeudp(int sockfd) { #ifdef WIN32 closesocket(sockfd); #else close(sockfd); #endif } static Boolean catchupflag=FALSE; Boolean ClientReply(int packetsize, void *packet, int socketfd, void *clientaddresspointer, int clientaddressbufferlength) { if(!clientaddresspointer) return FALSE; catchupflag= TRUE; return packetsize==sendto(socketfd, packet, packetsize, 0, clientaddresspointer, clientaddressbufferlength); } static Boolean exitflag= FALSE; void sgi_CleanExit(void) { exitflag = TRUE; } Boolean sgi_HaveToQuit(void) { return exitflag; } /* file descriptor poll table */ static int npolldevs =0; typedef struct polldev { int fd; void (*callbackfunction)(int , void *); void *dummy; } polldev; #define TABMAX 8 static polldev polldevs[TABMAX]; /* Register a device (referred to by a file descriptor that the caller should have already successfully obtained from a system call) to be polled as real-time constraints allowed. When a select(2) call indicates activity on the file descriptor, the callback function is called with the file descripter as first argument and the given dummy argument (presumably a pointer to the instance variables associated with the device). */ int RegisterPollingDevice(int fd, void (*callbackfunction)(int , void *), void *dummy) { if(npolldevscl_addr.sin_addr.s_addr; printf("Client address %p:\n", CA); printf(" clilen %d, sockfd %d\n", CA->clilen, CA->sockfd); printf(" sin_family %d, sin_port %d\n", CA->cl_addr.sin_family, CA->cl_addr.sin_port); printf(" address: (%x) %s\n", addr, inet_ntoa(CA->cl_addr.sin_addr)); printf(" sin_zero = \"%c%c%c%c%c%c%c%c\"\n", CA->cl_addr.sin_zero[0], CA->cl_addr.sin_zero[1], CA->cl_addr.sin_zero[2], CA->cl_addr.sin_zero[3], CA->cl_addr.sin_zero[4], CA->cl_addr.sin_zero[5], CA->cl_addr.sin_zero[6], CA->cl_addr.sin_zero[7]); printf("\n"); } //******************* void WriteTime(char* dst, osc_time_t osctime) { *(int32_t*)dst = htonl((int32_t)(osctime >> 32)); *(int32_t*)(dst+4) = htonl((int32_t)osctime); } void WriteMode(char* dst) { *(int32_t*)dst = htonl(0); } osc_time_t ReadTime(const char* src) { osc_time_t osctime = ntohl(*(int32_t*)src); return (osctime << 32) + ntohl(*(int32_t*)(src+4)); } double TimeToSeconds(osc_time_t osctime) { return (double)osctime * 2.3283064365386962890625e-10 /* 1/2^32 */; } int timeRound(double x) { return x >= 0.0 ? x+0.5 : x-0.5; } void WriteLogicalTime(char* dst) { double sTime = clock_gettimesince(19230720) / 1000.0; double tau = sTime - timeRound(sTime); //fprintf(stderr, "sSec = %f tau = %f\n", sTime, tau); *(int32_t*)dst = htonl((int32_t)(sTime)); *(int32_t*)(dst+4) = htonl((int32_t)(4294967296 * tau)); } Boolean dumpOSC_SendReply(char *buf, int n, void *clientDesc, int clientDescLenght, int fd) { if((n == 24) && (strcmp(buf, "#time") == 0)) { osc_time_t t0, t1, t2; double dt0, dt1, dt2; WriteMode(buf+6); t0 = ReadTime(buf+8); WriteLogicalTime(buf+16); t1 = ReadTime(buf+16); // reverse dt0 = TimeToSeconds(t0); // client time dt1 = TimeToSeconds(t1); // server time fprintf(stderr, "%f\t%f\t%f\n", dt0, dt1, dt0 - dt1); sendto(fd, buf, n, 0, (struct sockaddr *)clientDesc, clientDescLenght); return TRUE; } else { return FALSE; } } //********************** void dumpOSC_ParsePacket(t_dumpOSC *x, char *buf, int n, ClientAddr returnAddr) { // t_dumpOSC *x; int size, messageLen, i; char *messageName; char *args; //#ifdef PRINTADDRS #ifdef DEBUG //PrintClientAddr(returnAddr); #endif if ((n%4) != 0) { complain("SynthControl packet size (%d) not a multiple of 4 bytes: dropping", n); return; } if ((n >= 8) && (strncmp(buf, "#bundle", 8) == 0)) { /* This is a bundle message. */ #ifdef DEBUG printf("dumpOSC_ParsePacket: bundle msg: bundles not yet supported\n"); #endif if (n < 16) { complain("Bundle message too small (%d bytes) for time tag", n); return; } /* Print the time tag */ #ifdef DEBUG printf("[ %lx%08lx\n", ntohl(*((unsigned long *)(buf+8))), ntohl(*((unsigned long *)(buf+12)))); #endif /* Note: if we wanted to actually use the time tag as a little-endian 64-bit int, we'd have to word-swap the two 32-bit halves of it */ i = 16; /* Skip "#group\0" and time tag */ while(i n) { complain("Bad size count %d in bundle (only %d bytes left in entire bundle)", size, n-i-4); return; } /* Recursively handle element of bundle */ dumpOSC_ParsePacket(x, buf+i+4, size, returnAddr); i += 4 + size; } if (i != n) { complain("This can't happen"); } #ifdef DEBUG printf("]\n"); #endif } else if ((n == 24) && (strcmp(buf, "#time") == 0)) { complain("Time message: %s\n :).\n", htm_error_string); return; } else { /* This is not a bundle message */ messageName = buf; args = DataAfterAlignedString(messageName, buf+n); if (args == 0) { complain("Bad message name string: %s\nDropping entire message.\n", htm_error_string); return; } messageLen = args-messageName; dumpOSC_Smessage(x, messageName, (void *)args, n-messageLen, returnAddr); } } #define SMALLEST_POSITIVE_FLOAT 0.000001f static void dumpOSC_Smessage(t_dumpOSC *x, char *address, void *v, int n, ClientAddr returnAddr) { char *chars = v; t_atom at; //t_atom myargv[50]; int myargc = x->x_outatc; t_atom* mya = x->x_outat; int myi; #ifdef DEBUG printf("%s ", address); #endif // ztoln+cvt from envgen.c, ggee-0.18 .. // outlet_anything's 'symbol' gets set to address // so we dont need to append address to the atomlist /* SETSYMBOL(mya,gensym(address));myargc++; x->x_outatc = myargc; */ if (n != 0) { if (chars[0] == ',') { if (chars[1] != ',') { /* This message begins with a type-tag string */ dumpOSC_PrintTypeTaggedArgs(x, v, n); } else { /* Double comma means an escaped real comma, not a type string */ dumpOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 1); } } else { dumpOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 0); } } outlet_anything(x->x_msgout,gensym(address),x->x_outatc,(t_atom*)&x->x_outat); x->x_outatc = 0; #ifdef DEBUG printf("\n"); #endif fflush(stdout); /* Added for Sami 5/21/98 */ } static void dumpOSC_PrintTypeTaggedArgs(t_dumpOSC *x, void *v, int n) { char *typeTags, *thisType; char *p; int myargc = x->x_outatc; t_atom* mya = x->x_outat; int myi; typeTags = v; if (!IsNiceString(typeTags, typeTags+n)) { /* No null-termination, so maybe it wasn't a type tag string after all */ dumpOSC_PrintHeuristicallyTypeGuessedArgs(x, v, n, 0); return; } p = DataAfterAlignedString(typeTags, typeTags+n); for (thisType = typeTags + 1; *thisType != 0; ++thisType) { switch (*thisType) { case 'i': case 'r': case 'm': case 'c': #ifdef DEBUG //post("integer: %d", ntohl(*((int *) p))); #endif SETFLOAT(mya+myargc,ntohl(*((int *) p))); myargc++; p += 4; break; case 'f': { int i = ntohl(*((int *) p)); float *floatp = ((float *) (&i)); #ifdef DEBUG post("float: %f", *floatp); #endif SETFLOAT(mya+myargc,*floatp); myargc++; p += 4; } break; case 'h': case 't': #ifdef DEBUG printf("[A 64-bit int] "); #endif post("[A 64-bit int] not implemented"); p += 8; break; case 'd': #ifdef DEBUG printf("[A 64-bit float] "); #endif post("[A 64-bit float] not implemented"); p += 8; break; case 's': case 'S': if (!IsNiceString(p, typeTags+n)) { post("Type tag said this arg is a string but it's not!\n"); return; } else { #ifdef DEBUG post("string: \"%s\"", p); #endif SETSYMBOL(mya+myargc,gensym(p)); myargc++; //outlet_list(x->x_msgout, 0,sizeof(p), p); //outlet_anything(x->x_msgout, 0, sizeof(p), p); p = DataAfterAlignedString(p, typeTags+n); // append to output vector .. } break; case 'T': #ifdef DEBUG printf("[True] "); #endif SETFLOAT(mya+myargc,1.); myargc++; break; case 'F': #ifdef DEBUG printf("[False] "); #endif SETFLOAT(mya+myargc,0.); myargc++; break; case 'N': #ifdef DEBUG printf("[Nil]"); #endif post("sendOSC: [Nil] not implemented"); break; case 'I': #ifdef DEBUG printf("[Infinitum]"); #endif post("sendOSC: [Infinitum] not implemented"); break; default: post("sendOSC: [Unrecognized type tag %c]", *thisType); // return; } } x->x_outatc = myargc; } static void dumpOSC_PrintHeuristicallyTypeGuessedArgs(t_dumpOSC *x, void *v, int n, int skipComma) { int i, thisi; float thisf; int *ints; char *chars; char *string, *nextString; int myargc= x->x_outatc; t_atom* mya = x->x_outat; int myi; /* Go through the arguments 32 bits at a time */ ints = v; chars = v; for (i = 0; i= -1000 && thisi <= 1000000) { #ifdef DEBUG printf("%d ", thisi); #endif // append to output vector .. SETFLOAT(mya+myargc,(t_float) (thisi)); myargc++; // outlet_float(x->x_msgout, thisi); i++; } else if (thisf >= -1000.f && thisf <= 1000000.f && (thisf <=0.0f || thisf >= SMALLEST_POSITIVE_FLOAT)) { #ifdef DEBUG printf("%f ", thisf); #endif // append to output vector .. SETFLOAT(mya+myargc,thisf); myargc++; //outlet_float(x->x_msgout, thisf); i++; } else if (IsNiceString(string, chars+n)) { nextString = DataAfterAlignedString(string, chars+n); #ifdef DEBUG printf("\"%s\" ", (i == 0 && skipComma) ? string +1 : string); #endif // append to output vector .. SETSYMBOL(mya+myargc,gensym(string)); myargc++; //outlet_symbol(x->x_msgout, gensym((i == 0 && skipComma) ? string +1 : string)); i += (nextString-string) / 4; } else { // unhandled .. ;) #ifdef DEBUG printf("0x%x xx", ints[i]); #endif i++; } x->x_outatc = myargc; } } #define STRING_ALIGN_PAD 4 char *DataAfterAlignedString(char *string, char *boundary) { /* The argument is a block of data beginning with a string. The string has (presumably) been padded with extra null characters so that the overall length is a multiple of STRING_ALIGN_PAD bytes. Return a pointer to the next byte after the null byte(s). The boundary argument points to the character after the last valid character in the buffer---if the string hasn't ended by there, something's wrong. If the data looks wrong, return 0, and set htm_error_string */ int i; if ((boundary - string) %4 != 0) { fprintf(stderr, "Internal error: DataAfterAlignedString: bad boundary\n"); return 0; } for (i = 0; string[i] != '\0'; i++) { if (string + i >= boundary) { htm_error_string = "DataAfterAlignedString: Unreasonably long string"; return 0; } } /* Now string[i] is the first null character */ i++; for (; (i % STRING_ALIGN_PAD) != 0; i++) { if (string + i >= boundary) { htm_error_string = "DataAfterAlignedString: Unreasonably long string"; return 0; } if (string[i] != '\0') { htm_error_string = "DataAfterAlignedString: Incorrectly padded string."; return 0; } } return string+i; } Boolean IsNiceString(char *string, char *boundary) { /* Arguments same as DataAfterAlignedString(). Is the given "string" really a string? I.e., is it a sequence of isprint() characters terminated with 1-4 null characters to align on a 4-byte boundary? */ int i; if ((boundary - string) %4 != 0) { fprintf(stderr, "Internal error: IsNiceString: bad boundary\n"); return 0; } for (i = 0; string[i] != '\0'; i++) { if (!isprint(string[i])) return FALSE; if (string + i >= boundary) return FALSE; } /* If we made it this far, it's a null-terminated sequence of printing characters in the given boundary. Now we just make sure it's null padded... */ /* Now string[i] is the first null character */ i++; for (; (i % STRING_ALIGN_PAD) != 0; i++) { if (string[i] != '\0') return FALSE; } return TRUE; } #include void complain(char *s, ...) { va_list ap; va_start(ap, s); fprintf(stderr, "*** ERROR: "); vfprintf(stderr, s, ap); fprintf(stderr, "\n"); va_end(ap); } #endif /* __sgi or LINUX or WIN32 */