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 | |
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 | |
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 | */ |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | |
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 | */ |
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 */ |
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 |
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 | */ |
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 | |
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 | |
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 | |
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 | |