1 /* $Id$ $Revision$ */
2 /* vim:set shiftwidth=4 ts=8: */
3 
4 /*************************************************************************
5  * Copyright (c) 2011 AT&T Intellectual Property
6  * All rights reserved. This program and the accompanying materials
7  * are made available under the terms of the Eclipse Public License v1.0
8  * which accompanies this distribution, and is available at
9  * http://www.eclipse.org/legal/epl-v10.html
10  *
11  * Contributors: See CVS logs. Details at http://www.graphviz.org/
12  *************************************************************************/
13 
14 /*
15  *  This library forms the socket for run-time loadable device plugins.
16  */
17 
18 #include "config.h"
19 
20 #include <stdarg.h>
21 #include <stdlib.h>
22 #include <string.h>
23 #include <inttypes.h>
24 #include <errno.h>
25 #ifdef HAVE_UNISTD_H
26 #include <unistd.h>
27 #endif
28 
29 #ifdef _WIN32
30 #include <fcntl.h>
31 #include <io.h>
32 #include "compat.h"
33 #endif
34 
35 #ifdef HAVE_LIBZ
36 #include <zlib.h>
37 
38 #ifndef OS_CODE
39 #  define OS_CODE  0x03  /* assume Unix */
40 #endif
41 static char z_file_header[] =
42    {0x1f, 0x8b, /*magic*/ Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE};
43 
44 static z_stream z_strm;
45 static unsigned char *df;
46 static unsigned int dfallocated;
47 static uint64_t crc;
48 #endif /* HAVE_LIBZ */
49 
50 #include "const.h"
51 #include "memory.h"
52 #include "gvplugin_device.h"
53 #include "gvcjob.h"
54 #include "gvcint.h"
55 #include "gvcproc.h"
56 #include "logic.h"
57 #include "gvio.h"
58 
59 static const int PAGE_ALIGN = 4095;		/* align to a 4K boundary (less one), typical for Linux, Mac OS X and Windows memory allocation */
60 
gvwrite_no_z(GVJ_t * job,const char * s,size_t len)61 static size_t gvwrite_no_z (GVJ_t * job, const char *s, size_t len)
62 {
63     if (job->gvc->write_fn)   /* externally provided write dicipline */
64 	return (job->gvc->write_fn)(job, (char*)s, len);
65     if (job->output_data) {
66 	if (len > job->output_data_allocated - (job->output_data_position + 1)) {
67 	    /* ensure enough allocation for string = null terminator */
68 	    job->output_data_allocated = (job->output_data_position + len + 1 + PAGE_ALIGN) & ~PAGE_ALIGN;
69 	    job->output_data = realloc(job->output_data, job->output_data_allocated);
70 	    if (!job->output_data) {
71                 (job->common->errorfn) ("memory allocation failure\n");
72 		exit(1);
73 	    }
74 	}
75 	memcpy(job->output_data + job->output_data_position, s, len);
76         job->output_data_position += len;
77 	job->output_data[job->output_data_position] = '\0'; /* keep null termnated */
78 	return len;
79     }
80     else
81 	return fwrite(s, sizeof(char), len, job->output_file);
82     return 0;
83 }
84 
auto_output_filename(GVJ_t * job)85 static void auto_output_filename(GVJ_t *job)
86 {
87     static char *buf;
88     static size_t bufsz;
89     char gidx[100];  /* large enough for '.' plus any integer */
90     char *fn, *p, *q;
91     size_t len;
92 
93     if (job->graph_index)
94         sprintf(gidx, ".%d", job->graph_index + 1);
95     else
96         gidx[0] = '\0';
97     if (!(fn = job->input_filename))
98         fn = "noname.gv";
99     len = strlen(fn)                    /* typically "something.gv" */
100         + strlen(gidx)                  /* "", ".2", ".3", ".4", ... */
101         + 1                             /* "." */
102         + strlen(job->output_langname)  /* e.g. "png" */
103         + 1;                            /* null terminaor */
104     if (bufsz < len) {
105             bufsz = len + 10;
106             buf = realloc(buf, bufsz * sizeof(char));
107     }
108     strcpy(buf, fn);
109     strcat(buf, gidx);
110     strcat(buf, ".");
111     p = strdup(job->output_langname);
112     while ((q = strrchr(p, ':'))) {
113         strcat(buf, q+1);
114         strcat(buf, ".");
115 	*q = '\0';
116     }
117     strcat(buf, p);
118     free(p);
119 
120     job->output_filename = buf;
121 }
122 
123 /* gvdevice_initialize:
124  * Return 0 on success, non-zero on failure
125  */
gvdevice_initialize(GVJ_t * job)126 int gvdevice_initialize(GVJ_t * job)
127 {
128     gvdevice_engine_t *gvde = job->device.engine;
129     GVC_t *gvc = job->gvc;
130 
131     if (gvde && gvde->initialize) {
132 	gvde->initialize(job);
133     }
134     else if (job->output_data) {
135     }
136     /* if the device has no initialization then it uses file output */
137     else if (!job->output_file) {        /* if not yet opened */
138         if (gvc->common.auto_outfile_names)
139             auto_output_filename(job);
140         if (job->output_filename) {
141             job->output_file = fopen(job->output_filename, "w");
142             if (job->output_file == NULL) {
143 		(job->common->errorfn) ("Could not open \"%s\" for writing : %s\n",
144 		    job->output_filename, strerror(errno));
145                 /* perror(job->output_filename); */
146                 return(1);
147             }
148         }
149         else
150             job->output_file = stdout;
151 
152 #ifdef HAVE_SETMODE
153 #ifdef O_BINARY
154         if (job->flags & GVDEVICE_BINARY_FORMAT)
155 #ifdef _WIN32
156 		_setmode(fileno(job->output_file), O_BINARY);
157 #else
158 		setmode(fileno(job->output_file), O_BINARY);
159 #endif
160 #endif
161 #endif
162     }
163 
164     if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
165 #ifdef HAVE_LIBZ
166 	z_stream *z = &z_strm;
167 
168 	z->zalloc = 0;
169 	z->zfree = 0;
170 	z->opaque = 0;
171 	z->next_in = NULL;
172 	z->next_out = NULL;
173 	z->avail_in = 0;
174 
175 	crc = crc32(0L, Z_NULL, 0);
176 
177 	if (deflateInit2(z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
178 	    (job->common->errorfn) ("Error initializing for deflation\n");
179 	    return(1);
180 	}
181 	gvwrite_no_z(job, z_file_header, sizeof(z_file_header));
182 #else
183 	(job->common->errorfn) ("No libz support.\n");
184 	return(1);
185 #endif
186     }
187     return 0;
188 }
189 
gvwrite(GVJ_t * job,const char * s,size_t len)190 size_t gvwrite (GVJ_t * job, const char *s, size_t len)
191 {
192     size_t ret, olen;
193 
194     if (!len || !s)
195 	return 0;
196 
197     if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
198 #ifdef HAVE_LIBZ
199 	z_streamp z = &z_strm;
200 	size_t dflen;
201 
202 #ifdef HAVE_DEFLATEBOUND
203 	dflen = deflateBound(z, len);
204 #else
205 	/* deflateBound() is not available in older libz, e.g. from centos3 */
206 	dflen = 2 * len  + dfallocated - z->avail_out;
207 #endif
208 	if (dfallocated < dflen) {
209 	    dfallocated = (dflen + 1 + PAGE_ALIGN) & ~PAGE_ALIGN;
210 	    df = realloc(df, dfallocated);
211 	    if (! df) {
212                 (job->common->errorfn) ("memory allocation failure\n");
213 		exit(1);
214 	    }
215 	}
216 
217 	crc = crc32(crc, (unsigned char*)s, len);
218 
219 	z->next_in = (unsigned char*)s;
220 	z->avail_in = len;
221 	while (z->avail_in) {
222 	    z->next_out = df;
223 	    z->avail_out = dfallocated;
224 	    ret=deflate (z, Z_NO_FLUSH);
225 	    if (ret != Z_OK) {
226                 (job->common->errorfn) ("deflation problem %d\n", ret);
227 	        exit(1);
228 	    }
229 
230 	    if ((olen = z->next_out - df)) {
231 		ret = gvwrite_no_z (job, (char*)df, olen);
232 	        if (ret != olen) {
233                     (job->common->errorfn) ("gvwrite_no_z problem %d\n", ret);
234 	            exit(1);
235 	        }
236 	    }
237 	}
238 
239 #else
240 	(job->common->errorfn) ("No libz support.\n");
241 	exit(1);
242 #endif
243     }
244     else { /* uncompressed write */
245 	ret = gvwrite_no_z (job, s, len);
246 	if (ret != len) {
247 	    (job->common->errorfn) ("gvwrite_no_z problem %d\n", len);
248 	    exit(1);
249 	}
250     }
251     return len;
252 }
253 
gvferror(FILE * stream)254 int gvferror (FILE* stream)
255 {
256     GVJ_t *job = (GVJ_t*)stream;
257 
258     if (!job->gvc->write_fn && !job->output_data)
259 	return ferror(job->output_file);
260 
261     return 0;
262 }
263 
gvfwrite(const void * ptr,size_t size,size_t nmemb,FILE * stream)264 size_t gvfwrite (const void *ptr, size_t size, size_t nmemb, FILE *stream)
265 {
266     size = sizeof(char);
267     assert(size);
268     return gvwrite((GVJ_t*)stream, ptr, nmemb);
269 }
270 
gvputs(GVJ_t * job,const char * s)271 int gvputs(GVJ_t * job, const char *s)
272 {
273     size_t len = strlen(s);
274 
275     if (gvwrite (job, s, len) != len) {
276 	return EOF;
277     }
278     return +1;
279 }
280 
gvputc(GVJ_t * job,int c)281 int gvputc(GVJ_t * job, int c)
282 {
283     const char cc = c;
284 
285     if (gvwrite (job, &cc, 1) != 1) {
286 	return EOF;
287     }
288     return c;
289 }
290 
gvflush(GVJ_t * job)291 int gvflush (GVJ_t * job)
292 {
293     if (job->output_file
294       && ! job->external_context
295       && ! job->gvc->write_fn) {
296 	return fflush(job->output_file);
297     }
298     else
299 	return 0;
300 }
301 
gvdevice_close(GVJ_t * job)302 static void gvdevice_close(GVJ_t * job)
303 {
304     if (job->output_filename
305       && job->output_file != stdout
306       && ! job->external_context) {
307         if (job->output_file) {
308             fclose(job->output_file);
309             job->output_file = NULL;
310         }
311 	job->output_filename = NULL;
312     }
313 }
314 
gvdevice_format(GVJ_t * job)315 void gvdevice_format(GVJ_t * job)
316 {
317     gvdevice_engine_t *gvde = job->device.engine;
318 
319     if (gvde && gvde->format)
320 	gvde->format(job);
321     gvflush (job);
322 }
323 
gvdevice_finalize(GVJ_t * job)324 void gvdevice_finalize(GVJ_t * job)
325 {
326     gvdevice_engine_t *gvde = job->device.engine;
327     boolean finalized_p = FALSE;
328 
329     if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
330 #ifdef HAVE_LIBZ
331 	z_streamp z = &z_strm;
332 	unsigned char out[8] = "";
333 	int ret;
334 	int cnt = 0;
335 
336 	z->next_in = out;
337 	z->avail_in = 0;
338 	z->next_out = df;
339 	z->avail_out = dfallocated;
340 	while ((ret = deflate (z, Z_FINISH)) == Z_OK && (cnt++ <= 100)) {
341 	    gvwrite_no_z(job, (char*)df, z->next_out - df);
342 	    z->next_out = df;
343 	    z->avail_out = dfallocated;
344 	}
345 	if (ret != Z_STREAM_END) {
346             (job->common->errorfn) ("deflation finish problem %d cnt=%d\n", ret, cnt);
347 	    exit(1);
348 	}
349 	gvwrite_no_z(job, (char*)df, z->next_out - df);
350 
351 	ret = deflateEnd(z);
352 	if (ret != Z_OK) {
353 	    (job->common->errorfn) ("deflation end problem %d\n", ret);
354 	    exit(1);
355 	}
356 	out[0] = crc;
357 	out[1] = crc >> 8;
358 	out[2] = crc >> 16;
359 	out[3] = crc >> 24;
360 	out[4] = z->total_in;
361 	out[5] = z->total_in >> 8;
362 	out[6] = z->total_in >> 16;
363 	out[7] = z->total_in >> 24;
364 	gvwrite_no_z(job, (char*)out, sizeof(out));
365 #else
366 	(job->common->errorfn) ("No libz support\n");
367 	exit(1);
368 #endif
369     }
370 
371     if (gvde) {
372 	if (gvde->finalize) {
373 	    gvde->finalize(job);
374 	    finalized_p = TRUE;
375 	}
376     }
377 
378     if (! finalized_p) {
379         /* if the device has no finalization then it uses file output */
380 	gvflush (job);
381 	gvdevice_close(job);
382     }
383 }
384 /* gvprintf:
385  * Unless vsnprintf is available, this function is unsafe due to the fixed buffer size.
386  * It should only be used when the caller is sure the input will not
387  * overflow the buffer. In particular, it should be avoided for
388  * input coming from users.
389  */
gvprintf(GVJ_t * job,const char * format,...)390 void gvprintf(GVJ_t * job, const char *format, ...)
391 {
392     char buf[BUFSIZ];
393     int len;
394     va_list argp;
395     char* bp = buf;
396 
397     va_start(argp, format);
398 #ifdef HAVE_VSNPRINTF
399     len = vsnprintf((char *)buf, BUFSIZ, format, argp);
400     if (len < 0) {
401 	agerr (AGERR, "gvprintf: %s\n", strerror(errno));
402 	return;
403     }
404     else if (len >= BUFSIZ) {
405     /* C99 vsnprintf returns the length that would be required
406      * to write the string without truncation.
407      */
408 	bp = gmalloc(len + 1);
409 	va_end(argp);
410 	va_start(argp, format);
411 	len = vsprintf(bp, format, argp);
412     }
413 #else
414     len = vsprintf((char *)buf, format, argp);
415 #endif
416     va_end(argp);
417 
418     gvwrite(job, bp, len);
419     if (bp != buf)
420 	free (bp);
421 }
422 
423 
424 /* Test with:
425  *	cc -DGVPRINTNUM_TEST gvprintnum.c -o gvprintnum
426  */
427 
428 #define DECPLACES 4
429 #define DECPLACES_SCALE 10000
430 
431 /* use macro so maxnegnum is stated just once for both double and string versions */
432 #define val_str(n, x) static double n = x; static char n##str[] = #x;
433 val_str(maxnegnum, -999999999999999.99)
434 
435 /* we use len and don't need the string to be terminated */
436 /* #define TERMINATED_NUMBER_STRING */
437 
438 /* Note.  Returned string is only good until the next call to gvprintnum */
gvprintnum(size_t * len,double number)439 static char * gvprintnum (size_t *len, double number)
440 {
441     static char tmpbuf[sizeof(maxnegnumstr)];   /* buffer big enough for worst case */
442     char *result = tmpbuf+sizeof(maxnegnumstr); /* init result to end of tmpbuf */
443     long int N;
444     boolean showzeros, negative;
445     int digit, i;
446 
447     /*
448         number limited to a working range: maxnegnum >= n >= -maxnegnum
449 	N = number * DECPLACES_SCALE rounded towards zero,
450 	printing to buffer in reverse direction,
451 	printing "." after DECPLACES
452 	suppressing trailing "0" and "."
453      */
454 
455     if (number < maxnegnum) {		/* -ve limit */
456 	*len = sizeof(maxnegnumstr)-1;  /* len doesn't include terminator */
457 	return maxnegnumstr;;
458     }
459     if (number > -maxnegnum) {		/* +ve limit */
460 	*len = sizeof(maxnegnumstr)-2;  /* len doesn't include terminator or sign */
461 	return maxnegnumstr+1;		/* +1 to skip the '-' sign */
462     }
463     number *= DECPLACES_SCALE;		/* scale by DECPLACES_SCALE */
464     if (number < 0.0)			/* round towards zero */
465         N = number - 0.5;
466     else
467         N = number + 0.5;
468     if (N == 0) {			/* special case for exactly 0 */
469 	*len = 1;
470 	return "0";
471     }
472     if ((negative = (N < 0)))		/* avoid "-0" by testing rounded int */
473         N = -N;				/* make number +ve */
474 #ifdef TERMINATED_NUMBER_STRING
475     *--result = '\0';			/* terminate the result string */
476 #endif
477     showzeros = FALSE;			/* don't print trailing zeros */
478     for (i = DECPLACES; N || i > 0; i--) {  /* non zero remainder,
479 						or still in fractional part */
480         digit = N % 10;			/* next least-significant digit */
481         N /= 10;
482         if (digit || showzeros) {	/* if digit is non-zero,
483 						or if we are printing zeros */
484             *--result = digit | '0';	/* convert digit to ascii */
485             showzeros = TRUE;		/* from now on we must print zeros */
486         }
487         if (i == 1) {			/* if completed fractional part */
488             if (showzeros)		/* if there was a non-zero fraction */
489                 *--result = '.';	/* print decimal point */
490             showzeros = TRUE;		/* print all digits in int part */
491         }
492     }
493     if (negative)			/* print "-" if needed */
494         *--result = '-';
495 #ifdef TERMINATED_NUMBER_STRING
496     *len = tmpbuf+sizeof(maxnegnumstr)-1 - result;
497 #else
498     *len = tmpbuf+sizeof(maxnegnumstr) - result;
499 #endif
500     return result;
501 }
502 
503 
504 #ifdef GVPRINTNUM_TEST
main(int argc,char * argv[])505 int main (int argc, char *argv[])
506 {
507     char *buf;
508     size_t len;
509 
510     double test[] = {
511 	-maxnegnum*1.1, -maxnegnum*.9,
512 	1e8, 10.008, 10, 1, .1, .01,
513 	.006, .005, .004, .001, 1e-8,
514 	0, -0,
515 	-1e-8, -.001, -.004, -.005, -.006,
516 	-.01, -.1, -1, -10, -10.008, -1e8,
517 	maxnegnum*.9, maxnegnum*1.1
518     };
519     int i = sizeof(test) / sizeof(test[0]);
520 
521     while (i--) {
522 	buf = gvprintnum(&len, test[i]);
523         fprintf (stdout, "%g = %s %d\n", test[i], buf, len);
524     }
525 
526     return 0;
527 }
528 #endif
529 
530 /* gv_trim_zeros
531 * Trailing zeros are removed and decimal point, if possible.
532 * Add trailing space if addSpace is non-zero.
533 */
gv_trim_zeros(char * buf,int addSpace)534 static void gv_trim_zeros(char* buf, int addSpace)
535 {
536     char* dotp;
537     char* p;
538 
539     if ((dotp = strchr(buf, '.'))) {
540         p = dotp + 1;
541         while (*p) p++;  // find end of string
542         p--;
543         while (*p == '0') *p-- = '\0';
544         if (*p == '.')        // If all decimals were zeros, remove ".".
545             *p = '\0';
546         else
547             p++;
548     }
549     else if (addSpace)
550         p = buf + strlen(buf);
551 
552     if (addSpace) { /* p points to null byte */
553         *p++ = ' ';
554         *p = '\0';
555     }
556 }
557 
gvprintdouble(GVJ_t * job,double num)558 void gvprintdouble(GVJ_t * job, double num)
559 {
560     // Prevents values like -0
561     if (num > -0.00000001 && num < 0.00000001)
562     {
563         num = 0;
564     }
565 
566     char buf[50];
567 
568     snprintf(buf, 50, "%.02f", num);
569     gv_trim_zeros(buf, 0);
570 
571     gvwrite(job, buf, strlen(buf));
572 }
573 
gvprintpointf(GVJ_t * job,pointf p)574 void gvprintpointf(GVJ_t * job, pointf p)
575 {
576     char *buf;
577     size_t len;
578 
579     buf = gvprintnum(&len, p.x);
580     gvwrite(job, buf, len);
581     gvwrite(job, " ", 1);
582     buf = gvprintnum(&len, p.y);
583     gvwrite(job, buf, len);
584 }
585 
gvprintpointflist(GVJ_t * job,pointf * p,int n)586 void gvprintpointflist(GVJ_t * job, pointf *p, int n)
587 {
588     int i = 0;
589 
590     while (TRUE) {
591 	gvprintpointf(job, p[i]);
592         if (++i >= n) break;
593         gvwrite(job, " ", 1);
594     }
595 }
596 
597