Graphviz  2.31.20130618.0446
lib/gvc/gvdevice.c
Go to the documentation of this file.
00001 /* $Id$ $Revision$ */
00002 /* vim:set shiftwidth=4 ts=8: */
00003 
00004 /*************************************************************************
00005  * Copyright (c) 2011 AT&T Intellectual Property 
00006  * All rights reserved. This program and the accompanying materials
00007  * are made available under the terms of the Eclipse Public License v1.0
00008  * which accompanies this distribution, and is available at
00009  * http://www.eclipse.org/legal/epl-v10.html
00010  *
00011  * Contributors: See CVS logs. Details at http://www.graphviz.org/
00012  *************************************************************************/
00013 
00014 /*
00015  *  This library forms the socket for run-time loadable device plugins.  
00016  */
00017 
00018 #ifdef HAVE_CONFIG_H
00019 #include "config.h"
00020 #endif
00021 
00022 #include <stdarg.h>
00023 #include <stdlib.h>
00024 #include <string.h>
00025 
00026 #ifdef HAVE_ERRNO_H
00027 #include <errno.h>
00028 #endif
00029 #ifdef HAVE_UNISTD_H
00030 #include <unistd.h>
00031 #endif
00032 
00033 #ifdef WIN32
00034 #include <fcntl.h>
00035 #include <io.h>
00036 #include "compat.h"
00037 #endif
00038 
00039 #ifdef HAVE_LIBZ
00040 #include <zlib.h>
00041 
00042 #ifndef OS_CODE
00043 #  define OS_CODE  0x03  /* assume Unix */
00044 #endif
00045 static char z_file_header[] =
00046    {0x1f, 0x8b, /*magic*/ Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE};
00047 
00048 static z_stream z_strm;
00049 static unsigned char *df;
00050 static unsigned int dfallocated;
00051 static unsigned long int crc;
00052 #endif /* HAVE_LIBZ */
00053 
00054 #include "const.h"
00055 #include "memory.h"
00056 #include "gvplugin_device.h"
00057 #include "gvcjob.h"
00058 #include "gvcint.h"
00059 #include "gvcproc.h"
00060 #include "logic.h"
00061 #include "gvio.h"
00062 
00063 static const int PAGE_ALIGN = 4095;             /* align to a 4K boundary (less one), typical for Linux, Mac OS X and Windows memory allocation */
00064 
00065 static size_t gvwrite_no_z (GVJ_t * job, const char *s, size_t len)
00066 {
00067     if (job->gvc->write_fn)   /* externally provided write dicipline */
00068         return (job->gvc->write_fn)(job, (char*)s, len);
00069     if (job->output_data) {
00070         if (len > job->output_data_allocated - (job->output_data_position + 1)) {
00071             /* ensure enough allocation for string = null terminator */
00072             job->output_data_allocated = (job->output_data_position + len + 1 + PAGE_ALIGN) & ~PAGE_ALIGN;
00073             job->output_data = realloc(job->output_data, job->output_data_allocated);
00074             if (!job->output_data) {
00075                 (job->common->errorfn) ("memory allocation failure\n");
00076                 exit(1);
00077             }
00078         }
00079         memcpy(job->output_data + job->output_data_position, s, len);
00080         job->output_data_position += len;
00081         job->output_data[job->output_data_position] = '\0'; /* keep null termnated */
00082         return len;
00083     }
00084     else
00085         return fwrite(s, sizeof(char), len, job->output_file);
00086     return 0;
00087 }
00088 
00089 static void auto_output_filename(GVJ_t *job)
00090 {
00091     static char *buf;
00092     static size_t bufsz;
00093     char gidx[100];  /* large enough for '.' plus any integer */
00094     char *fn, *p, *q;
00095     size_t len;
00096 
00097     if (job->graph_index)
00098         sprintf(gidx, ".%d", job->graph_index + 1);
00099     else
00100         gidx[0] = '\0';
00101     if (!(fn = job->input_filename))
00102         fn = "noname.gv";
00103     len = strlen(fn)                    /* typically "something.gv" */
00104         + strlen(gidx)                  /* "", ".2", ".3", ".4", ... */
00105         + 1                             /* "." */
00106         + strlen(job->output_langname)  /* e.g. "png" */
00107         + 1;                            /* null terminaor */
00108     if (bufsz < len) {
00109             bufsz = len + 10;
00110             buf = realloc(buf, bufsz * sizeof(char));
00111     }
00112     strcpy(buf, fn);
00113     strcat(buf, gidx);
00114     strcat(buf, ".");
00115     p = strdup(job->output_langname);
00116     while ((q = strrchr(p, ':'))) {
00117         strcat(buf, q+1);
00118         strcat(buf, ".");
00119         *q = '\0';
00120     }
00121     strcat(buf, p);
00122     free(p);
00123 
00124     job->output_filename = buf;
00125 }
00126 
00127 /* gvdevice_initialize:
00128  * Return 0 on success, non-zero on failure
00129  */
00130 int gvdevice_initialize(GVJ_t * job)
00131 {
00132     gvdevice_engine_t *gvde = job->device.engine;
00133     GVC_t *gvc = job->gvc;
00134 
00135     if (gvde && gvde->initialize) {
00136         gvde->initialize(job);
00137     }
00138     else if (job->output_data) {
00139     }
00140     /* if the device has no initialization then it uses file output */
00141     else if (!job->output_file) {        /* if not yet opened */
00142         if (gvc->common.auto_outfile_names)
00143             auto_output_filename(job);
00144         if (job->output_filename) {
00145             job->output_file = fopen(job->output_filename, "w");
00146             if (job->output_file == NULL) {
00147                 (job->common->errorfn) ("Could not open \"%s\" for writing : %s\n", 
00148                     job->output_filename, strerror(errno));
00149                 /* perror(job->output_filename); */
00150                 return(1);
00151             }
00152         }
00153         else
00154             job->output_file = stdout;
00155 
00156 #ifdef HAVE_SETMODE
00157 #ifdef O_BINARY
00158         if (job->flags & GVDEVICE_BINARY_FORMAT)
00159 #ifdef WIN32
00160                 _setmode(fileno(job->output_file), O_BINARY);
00161 #else
00162                 setmode(fileno(job->output_file), O_BINARY);
00163 #endif                  
00164 #endif
00165 #endif
00166     }
00167 
00168     if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
00169 #ifdef HAVE_LIBZ
00170         z_stream *z = &z_strm;
00171 
00172         z->zalloc = 0;
00173         z->zfree = 0;
00174         z->opaque = 0;
00175         z->next_in = NULL;
00176         z->next_out = NULL;
00177         z->avail_in = 0;
00178 
00179         crc = crc32(0L, Z_NULL, 0);
00180 
00181         if (deflateInit2(z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
00182             (job->common->errorfn) ("Error initializing for deflation\n");
00183             return(1);
00184         }
00185         gvwrite_no_z(job, z_file_header, sizeof(z_file_header));
00186 #else
00187         (job->common->errorfn) ("No libz support.\n");
00188         return(1);
00189 #endif
00190     }
00191     return 0;
00192 }
00193 
00194 size_t gvwrite (GVJ_t * job, const char *s, size_t len)
00195 {
00196     size_t ret, olen;
00197 
00198     if (!len || !s)
00199         return 0;
00200 
00201     if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
00202 #ifdef HAVE_LIBZ
00203         z_streamp z = &z_strm;
00204         size_t dflen;
00205 
00206 #ifdef HAVE_DEFLATEBOUND
00207         dflen = deflateBound(z, len);
00208 #else
00209         /* deflateBound() is not available in older libz, e.g. from centos3 */
00210         dflen = 2 * len  + dfallocated - z->avail_out;
00211 #endif
00212         if (dfallocated < dflen) {
00213             dfallocated = (dflen + 1 + PAGE_ALIGN) & ~PAGE_ALIGN;
00214             df = realloc(df, dfallocated);
00215             if (! df) {
00216                 (job->common->errorfn) ("memory allocation failure\n");
00217                 exit(1);
00218             }
00219         }
00220 
00221         crc = crc32(crc, (unsigned char*)s, len);
00222 
00223         z->next_in = (unsigned char*)s;
00224         z->avail_in = len;
00225         while (z->avail_in) {
00226             z->next_out = df;
00227             z->avail_out = dfallocated;
00228             ret=deflate (z, Z_NO_FLUSH);
00229             if (ret != Z_OK) {
00230                 (job->common->errorfn) ("deflation problem %d\n", ret);
00231                 exit(1);
00232             }
00233 
00234             if ((olen = z->next_out - df)) {
00235                 ret = gvwrite_no_z (job, (char*)df, olen);
00236                 if (ret != olen) {
00237                     (job->common->errorfn) ("gvwrite_no_z problem %d\n", ret);
00238                     exit(1);
00239                 }
00240             }
00241         }
00242 
00243 #else
00244         (job->common->errorfn) ("No libz support.\n");
00245         exit(1);
00246 #endif
00247     }
00248     else { /* uncompressed write */
00249         ret = gvwrite_no_z (job, s, len);
00250         if (ret != len) {
00251             (job->common->errorfn) ("gvwrite_no_z problem %d\n", len);
00252             exit(1);
00253         }
00254     }
00255     return len;
00256 }
00257 
00258 int gvferror (FILE* stream)
00259 {
00260     GVJ_t *job = (GVJ_t*)stream;
00261 
00262     if (!job->gvc->write_fn && !job->output_data)
00263         return ferror(job->output_file);
00264 
00265     return 0;
00266 }
00267 
00268 size_t gvfwrite (const void *ptr, size_t size, size_t nmemb, FILE *stream)
00269 {
00270     assert(size = sizeof(char));
00271     return gvwrite((GVJ_t*)stream, ptr, nmemb);
00272 }
00273 
00274 int gvputs(GVJ_t * job, const char *s)
00275 {
00276     size_t len = strlen(s);
00277 
00278     if (gvwrite (job, s, len) != len) {
00279         return EOF;
00280     }
00281     return +1;
00282 }
00283 
00284 int gvputc(GVJ_t * job, int c)
00285 {
00286     const char cc = c;
00287 
00288     if (gvwrite (job, &cc, 1) != 1) {
00289         return EOF;
00290     }
00291     return c;
00292 }
00293 
00294 int gvflush (GVJ_t * job)
00295 {
00296     if (job->output_file
00297       && ! job->external_context
00298       && ! job->gvc->write_fn) {
00299         return fflush(job->output_file);
00300     }
00301     else
00302         return 0;
00303 }
00304 
00305 static void gvdevice_close(GVJ_t * job)
00306 {
00307     if (job->output_filename
00308       && job->output_file != stdout 
00309       && ! job->external_context) {
00310         if (job->output_file) {
00311             fclose(job->output_file);
00312             job->output_file = NULL;
00313         }
00314         job->output_filename = NULL;
00315     }
00316 }
00317 
00318 void gvdevice_format(GVJ_t * job)
00319 {
00320     gvdevice_engine_t *gvde = job->device.engine;
00321 
00322     if (gvde && gvde->format)
00323         gvde->format(job);
00324     gvflush (job);
00325 }
00326 
00327 void gvdevice_finalize(GVJ_t * job)
00328 {
00329     gvdevice_engine_t *gvde = job->device.engine;
00330     boolean finalized_p = FALSE;
00331 
00332     if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
00333 #ifdef HAVE_LIBZ
00334         z_streamp z = &z_strm;
00335         unsigned char out[8] = "";
00336         int ret;
00337         int cnt = 0;
00338 
00339         z->next_in = out;
00340         z->avail_in = 0;
00341         z->next_out = df;
00342         z->avail_out = dfallocated;
00343         while ((ret = deflate (z, Z_FINISH)) == Z_OK && (cnt++ <= 100)) {
00344             gvwrite_no_z(job, (char*)df, z->next_out - df);
00345             z->next_out = df;
00346             z->avail_out = dfallocated;
00347         }
00348         if (ret != Z_STREAM_END) {
00349             (job->common->errorfn) ("deflation finish problem %d cnt=%d\n", ret, cnt);
00350             exit(1);
00351         }
00352         gvwrite_no_z(job, (char*)df, z->next_out - df);
00353 
00354         ret = deflateEnd(z);
00355         if (ret != Z_OK) {
00356             (job->common->errorfn) ("deflation end problem %d\n", ret);
00357             exit(1);
00358         }
00359         out[0] = crc;
00360         out[1] = crc >> 8;
00361         out[2] = crc >> 16;
00362         out[3] = crc >> 24;
00363         out[4] = z->total_in;
00364         out[5] = z->total_in >> 8;
00365         out[6] = z->total_in >> 16;
00366         out[7] = z->total_in >> 24;
00367         gvwrite_no_z(job, (char*)out, sizeof(out));
00368 #else
00369         (job->common->errorfn) ("No libz support\n");
00370         exit(1);
00371 #endif
00372     }
00373 
00374     if (gvde) {
00375         if (gvde->finalize) {
00376             gvde->finalize(job);
00377             finalized_p = TRUE;
00378         }
00379     }
00380 
00381     if (! finalized_p) {
00382         /* if the device has no finalization then it uses file output */
00383         gvflush (job);
00384         gvdevice_close(job);
00385     }
00386 }
00387 /* gvprintf:
00388  * Unless vsnprintf is available, this function is unsafe due to the fixed buffer size.
00389  * It should only be used when the caller is sure the input will not
00390  * overflow the buffer. In particular, it should be avoided for
00391  * input coming from users.
00392  */
00393 void gvprintf(GVJ_t * job, const char *format, ...)
00394 {
00395     char buf[BUFSIZ];
00396     size_t len;
00397     va_list argp;
00398     char* bp = buf;
00399 
00400     va_start(argp, format);
00401 #ifdef HAVE_VSNPRINTF
00402     len = vsnprintf((char *)buf, BUFSIZ, format, argp);
00403     if (len < 0) {
00404         agerr (AGERR, "gvprintf: %s\n", strerror(errno));
00405         return;
00406     }
00407     else if (len >= BUFSIZ) {
00408     /* C99 vsnprintf returns the length that would be required
00409      * to write the string without truncation. 
00410      */
00411         bp = gmalloc(len + 1);
00412         va_end(argp);
00413         va_start(argp, format);
00414         len = vsprintf(bp, format, argp);
00415     }
00416 #else
00417     len = vsprintf((char *)buf, format, argp);
00418 #endif
00419     va_end(argp);
00420 
00421     gvwrite(job, bp, len);
00422     if (bp != buf)
00423         free (bp);
00424 }
00425 
00426 
00427 /* Test with:
00428  *      cc -DGVPRINTNUM_TEST gvprintnum.c -o gvprintnum
00429  */
00430 
00431 #define DECPLACES 2
00432 #define DECPLACES_SCALE 100
00433 
00434 /* use macro so maxnegnum is stated just once for both double and string versions */
00435 #define val_str(n, x) static double n = x; static char n##str[] = #x;
00436 val_str(maxnegnum, -999999999999999.99)
00437 
00438 /* we use len and don't need the string to be terminated */
00439 /* #define TERMINATED_NUMBER_STRING */
00440 
00441 /* Note.  Returned string is only good until the next call to gvprintnum */
00442 static char * gvprintnum (size_t *len, double number)
00443 {
00444     static char tmpbuf[sizeof(maxnegnumstr)];   /* buffer big enough for worst case */
00445     char *result = tmpbuf+sizeof(maxnegnumstr); /* init result to end of tmpbuf */
00446     long int N;
00447     boolean showzeros, negative;
00448     int digit, i;
00449 
00450     /*
00451         number limited to a working range: maxnegnum >= n >= -maxnegnum
00452         N = number * DECPLACES_SCALE rounded towards zero,
00453         printing to buffer in reverse direction,
00454         printing "." after DECPLACES
00455         suppressing trailing "0" and "."
00456      */
00457 
00458     if (number < maxnegnum) {           /* -ve limit */
00459         *len = sizeof(maxnegnumstr)-1;  /* len doesn't include terminator */
00460         return maxnegnumstr;;
00461     }
00462     if (number > -maxnegnum) {          /* +ve limit */
00463         *len = sizeof(maxnegnumstr)-2;  /* len doesn't include terminator or sign */
00464         return maxnegnumstr+1;          /* +1 to skip the '-' sign */
00465     }
00466     number *= DECPLACES_SCALE;          /* scale by DECPLACES_SCALE */
00467     if (number < 0.0)                   /* round towards zero */
00468         N = number - 0.5;
00469     else
00470         N = number + 0.5;
00471     if (N == 0) {                       /* special case for exactly 0 */
00472         *len = 1;
00473         return "0";
00474     }
00475     if ((negative = (N < 0)))           /* avoid "-0" by testing rounded int */
00476         N = -N;                         /* make number +ve */
00477 #ifdef TERMINATED_NUMBER_STRING
00478     *--result = '\0';                   /* terminate the result string */
00479 #endif
00480     showzeros = FALSE;                  /* don't print trailing zeros */
00481     for (i = DECPLACES; N || i > 0; i--) {  /* non zero remainder,
00482                                                 or still in fractional part */
00483         digit = N % 10;                 /* next least-significant digit */
00484         N /= 10;
00485         if (digit || showzeros) {       /* if digit is non-zero,
00486                                                 or if we are printing zeros */
00487             *--result = digit | '0';    /* convert digit to ascii */
00488             showzeros = TRUE;           /* from now on we must print zeros */
00489         }
00490         if (i == 1) {                   /* if completed fractional part */
00491             if (showzeros)              /* if there was a non-zero fraction */
00492                 *--result = '.';        /* print decimal point */
00493             showzeros = TRUE;           /* print all digits in int part */
00494         }
00495     }
00496     if (negative)                       /* print "-" if needed */
00497         *--result = '-';
00498 #ifdef TERMINATED_NUMBER_STRING
00499     *len = tmpbuf+sizeof(maxnegnumstr)-1 - result;
00500 #else
00501     *len = tmpbuf+sizeof(maxnegnumstr) - result;
00502 #endif
00503     return result;                              
00504 }
00505 
00506 
00507 #ifdef GVPRINTNUM_TEST
00508 int main (int argc, char *argv[])
00509 {
00510     char *buf;
00511     size_t len;
00512 
00513     double test[] = {
00514         -maxnegnum*1.1, -maxnegnum*.9,
00515         1e8, 10.008, 10, 1, .1, .01,
00516         .006, .005, .004, .001, 1e-8, 
00517         0, -0,
00518         -1e-8, -.001, -.004, -.005, -.006,
00519         -.01, -.1, -1, -10, -10.008, -1e8,
00520         maxnegnum*.9, maxnegnum*1.1
00521     };
00522     int i = sizeof(test) / sizeof(test[0]);
00523 
00524     while (i--) {
00525         buf = gvprintnum(&len, test[i]);
00526         fprintf (stdout, "%g = %s %d\n", test[i], buf, len);
00527     }
00528 
00529     return 0;
00530 }
00531 #endif
00532 
00533 void gvprintdouble(GVJ_t * job, double num)
00534 {
00535     char *buf;
00536     size_t len;
00537 
00538     buf = gvprintnum(&len, num);
00539     gvwrite(job, buf, len);
00540 } 
00541 
00542 void gvprintpointf(GVJ_t * job, pointf p)
00543 {
00544     char *buf;
00545     size_t len;
00546 
00547     buf = gvprintnum(&len, p.x);
00548     gvwrite(job, buf, len);
00549     gvwrite(job, " ", 1);
00550     buf = gvprintnum(&len, p.y);
00551     gvwrite(job, buf, len);
00552 } 
00553 
00554 void gvprintpointflist(GVJ_t * job, pointf *p, int n)
00555 {
00556     int i = 0;
00557 
00558     while (TRUE) {
00559         gvprintpointf(job, p[i]);
00560         if (++i >= n) break;
00561         gvwrite(job, " ", 1);
00562     }
00563 } 
00564