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
41static char z_file_header[] =
42 {0x1f, 0x8b, /*magic*/ Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE};
43
44static z_stream z_strm;
45static unsigned char *df;
46static unsigned int dfallocated;
47static 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
59static const int PAGE_ALIGN = 4095; /* align to a 4K boundary (less one), typical for Linux, Mac OS X and Windows memory allocation */
60
61static 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
85static 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 */
126int 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
190size_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
254int 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
264size_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
271int 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
281int 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
291int 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
302static 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
315void 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
324void 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 */
390void 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;
433val_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 */
439static 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
505int 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*/
534static 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
558void 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
574void 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
586void 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