gvdevice.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545
  1. /*************************************************************************
  2. * Copyright (c) 2011 AT&T Intellectual Property
  3. * All rights reserved. This program and the accompanying materials
  4. * are made available under the terms of the Eclipse Public License v1.0
  5. * which accompanies this distribution, and is available at
  6. * https://www.eclipse.org/legal/epl-v10.html
  7. *
  8. * Contributors: Details at https://graphviz.org
  9. *************************************************************************/
  10. /*
  11. * This library forms the socket for run-time loadable device plugins.
  12. */
  13. #include "config.h"
  14. #include <ctype.h>
  15. #include <limits.h>
  16. #include <stdarg.h>
  17. #include <stdbool.h>
  18. #include <stdio.h>
  19. #include <stdlib.h>
  20. #include <string.h>
  21. #include <inttypes.h>
  22. #include <errno.h>
  23. #include <unistd.h>
  24. #include <util/gv_fopen.h>
  25. #ifdef _WIN32
  26. #include <fcntl.h>
  27. #include <io.h>
  28. #endif
  29. #ifdef HAVE_LIBZ
  30. #include <zlib.h>
  31. #ifndef OS_CODE
  32. # define OS_CODE 0x03 /* assume Unix */
  33. #endif
  34. static const unsigned char z_file_header[] =
  35. {0x1f, 0x8b, /*magic*/ Z_DEFLATED, 0 /*flags*/, 0,0,0,0 /*time*/, 0 /*xflags*/, OS_CODE};
  36. static z_stream z_strm;
  37. static unsigned char *df;
  38. static unsigned int dfallocated;
  39. static uint64_t crc;
  40. #endif /* HAVE_LIBZ */
  41. #include <assert.h>
  42. #include <common/const.h>
  43. #include <gvc/gvplugin_device.h>
  44. #include <gvc/gvcjob.h>
  45. #include <gvc/gvcint.h>
  46. #include <gvc/gvcproc.h>
  47. #include <common/utils.h>
  48. #include <gvc/gvio.h>
  49. #include <util/agxbuf.h>
  50. #include <util/exit.h>
  51. #include <util/startswith.h>
  52. static size_t gvwrite_no_z(GVJ_t * job, const void *s, size_t len) {
  53. if (job->gvc->write_fn) /* externally provided write discipline */
  54. return job->gvc->write_fn(job, s, len);
  55. if (job->output_data) {
  56. if (len > job->output_data_allocated - (job->output_data_position + 1)) {
  57. /* ensure enough allocation for string = null terminator */
  58. job->output_data_allocated = job->output_data_position + len + 1;
  59. job->output_data = realloc(job->output_data, job->output_data_allocated);
  60. if (!job->output_data) {
  61. job->common->errorfn("memory allocation failure\n");
  62. graphviz_exit(1);
  63. }
  64. }
  65. memcpy(job->output_data + job->output_data_position, s, len);
  66. job->output_data_position += len;
  67. job->output_data[job->output_data_position] = '\0'; /* keep null terminated */
  68. return len;
  69. }
  70. assert(job->output_file != NULL);
  71. return fwrite(s, sizeof(char), len, job->output_file);
  72. }
  73. static void auto_output_filename(GVJ_t *job)
  74. {
  75. static agxbuf buf;
  76. char *fn;
  77. if (!(fn = job->input_filename))
  78. fn = "noname.gv";
  79. agxbput(&buf, fn);
  80. if (job->graph_index)
  81. agxbprint(&buf, ".%d", job->graph_index + 1);
  82. agxbputc(&buf, '.');
  83. {
  84. const char *src = job->output_langname;
  85. const char *src_end = src + strlen(src);
  86. for (const char *q = src_end; ; --q) {
  87. if (*q == ':') {
  88. agxbprint(&buf, "%.*s.", (int)(src_end - q - 1), q + 1);
  89. src_end = q;
  90. }
  91. if (q == src) {
  92. agxbprint(&buf, "%.*s", (int)(src_end - src), src);
  93. break;
  94. }
  95. }
  96. }
  97. job->output_filename = agxbuse(&buf);
  98. }
  99. /* gvdevice_initialize:
  100. * Return 0 on success, non-zero on failure
  101. */
  102. int gvdevice_initialize(GVJ_t * job)
  103. {
  104. gvdevice_engine_t *gvde = job->device.engine;
  105. GVC_t *gvc = job->gvc;
  106. if (gvde && gvde->initialize) {
  107. gvde->initialize(job);
  108. }
  109. else if (job->output_data) {
  110. }
  111. /* if the device has no initialization then it uses file output */
  112. else if (!job->output_file) { /* if not yet opened */
  113. if (gvc->common.auto_outfile_names)
  114. auto_output_filename(job);
  115. if (job->output_filename) {
  116. job->output_file = gv_fopen(job->output_filename, "w");
  117. if (job->output_file == NULL) {
  118. job->common->errorfn("Could not open \"%s\" for writing : %s\n",
  119. job->output_filename, strerror(errno));
  120. /* perror(job->output_filename); */
  121. return 1;
  122. }
  123. }
  124. else
  125. job->output_file = stdout;
  126. #ifdef HAVE_SETMODE
  127. #ifdef O_BINARY
  128. if (job->flags & GVDEVICE_BINARY_FORMAT)
  129. #ifdef _WIN32
  130. _setmode(fileno(job->output_file), O_BINARY);
  131. #else
  132. setmode(fileno(job->output_file), O_BINARY);
  133. #endif
  134. #endif
  135. #endif
  136. }
  137. if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
  138. #ifdef HAVE_LIBZ
  139. z_stream *z = &z_strm;
  140. z->zalloc = 0;
  141. z->zfree = 0;
  142. z->opaque = 0;
  143. z->next_in = NULL;
  144. z->next_out = NULL;
  145. z->avail_in = 0;
  146. crc = crc32(0L, Z_NULL, 0);
  147. if (deflateInit2(z, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY) != Z_OK) {
  148. job->common->errorfn("Error initializing for deflation\n");
  149. return 1;
  150. }
  151. gvwrite_no_z(job, z_file_header, sizeof(z_file_header));
  152. #else
  153. job->common->errorfn("No libz support.\n");
  154. return 1;
  155. #endif
  156. }
  157. return 0;
  158. }
  159. size_t gvwrite (GVJ_t * job, const char *s, size_t len)
  160. {
  161. size_t ret, olen;
  162. if (!len || !s)
  163. return 0;
  164. if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
  165. #ifdef HAVE_LIBZ
  166. z_streamp z = &z_strm;
  167. size_t dflen = deflateBound(z, len);
  168. if (dfallocated < dflen) {
  169. dfallocated = dflen > UINT_MAX - 1 ? UINT_MAX : (unsigned)dflen + 1;
  170. df = realloc(df, dfallocated);
  171. if (! df) {
  172. job->common->errorfn("memory allocation failure\n");
  173. graphviz_exit(1);
  174. }
  175. }
  176. #if ZLIB_VERNUM >= 0x1290
  177. crc = crc32_z(crc, (const unsigned char*)s, len);
  178. #else
  179. crc = crc32(crc, (const unsigned char*)s, len);
  180. #endif
  181. for (size_t offset = 0; offset < len; ) {
  182. // Suppress Clang/GCC -Wcast-qual warnings. `next_in` is morally const.
  183. #ifdef __GNUC__
  184. #pragma GCC diagnostic push
  185. #pragma GCC diagnostic ignored "-Wcast-qual"
  186. #endif
  187. z->next_in = (unsigned char *)s + offset;
  188. #ifdef __GNUC__
  189. #pragma GCC diagnostic pop
  190. #endif
  191. const unsigned chunk = len - offset > UINT_MAX
  192. ? UINT_MAX : (unsigned)(len - offset);
  193. z->avail_in = chunk;
  194. z->next_out = df;
  195. z->avail_out = dfallocated;
  196. int r = deflate(z, Z_NO_FLUSH);
  197. if (r != Z_OK) {
  198. job->common->errorfn("deflation problem %d\n", r);
  199. graphviz_exit(1);
  200. }
  201. if ((olen = (size_t)(z->next_out - df))) {
  202. ret = gvwrite_no_z(job, df, olen);
  203. if (ret != olen) {
  204. job->common->errorfn("gvwrite_no_z problem %d\n", ret);
  205. graphviz_exit(1);
  206. }
  207. }
  208. offset += chunk - z->avail_in;
  209. }
  210. #else
  211. (void)olen;
  212. job->common->errorfn("No libz support.\n");
  213. graphviz_exit(1);
  214. #endif
  215. }
  216. else { /* uncompressed write */
  217. ret = gvwrite_no_z (job, s, len);
  218. if (ret != len) {
  219. job->common->errorfn("gvwrite_no_z problem %d\n", len);
  220. graphviz_exit(1);
  221. }
  222. }
  223. return len;
  224. }
  225. int gvferror (FILE* stream)
  226. {
  227. GVJ_t *job = (GVJ_t*)stream;
  228. if (!job->gvc->write_fn && !job->output_data)
  229. return ferror(job->output_file);
  230. return 0;
  231. }
  232. int gvputs(GVJ_t * job, const char *s)
  233. {
  234. size_t len = strlen(s);
  235. if (gvwrite (job, s, len) != len) {
  236. return EOF;
  237. }
  238. return 1;
  239. }
  240. int gvputs_xml(GVJ_t *job, const char *s) {
  241. const xml_flags_t flags = {.dash = 1, .nbsp = 1};
  242. return xml_escape(s, flags, (int (*)(void *, const char *))gvputs, job);
  243. }
  244. void gvputs_nonascii(GVJ_t *job, const char *s) {
  245. for (; *s != '\0'; ++s) {
  246. if (*s == '\\') {
  247. gvputs(job, "\\\\");
  248. } else if (isascii((int)*s)) {
  249. gvputc(job, *s);
  250. } else {
  251. gvprintf(job, "%03o", (unsigned)*s);
  252. }
  253. }
  254. }
  255. int gvputc(GVJ_t * job, int c)
  256. {
  257. const char cc = (char)c;
  258. if (gvwrite (job, &cc, 1) != 1) {
  259. return EOF;
  260. }
  261. return c;
  262. }
  263. int gvflush (GVJ_t * job)
  264. {
  265. if (job->output_file
  266. && ! job->external_context
  267. && ! job->gvc->write_fn) {
  268. return fflush(job->output_file);
  269. }
  270. else
  271. return 0;
  272. }
  273. static void gvdevice_close(GVJ_t * job)
  274. {
  275. if (job->output_filename
  276. && job->output_file != stdout
  277. && ! job->external_context) {
  278. if (job->output_file) {
  279. fclose(job->output_file);
  280. job->output_file = NULL;
  281. }
  282. job->output_filename = NULL;
  283. }
  284. }
  285. void gvdevice_format(GVJ_t * job)
  286. {
  287. gvdevice_engine_t *gvde = job->device.engine;
  288. if (gvde && gvde->format)
  289. gvde->format(job);
  290. gvflush (job);
  291. }
  292. void gvdevice_finalize(GVJ_t * job)
  293. {
  294. gvdevice_engine_t *gvde = job->device.engine;
  295. bool finalized_p = false;
  296. if (job->flags & GVDEVICE_COMPRESSED_FORMAT) {
  297. #ifdef HAVE_LIBZ
  298. z_streamp z = &z_strm;
  299. unsigned char out[8] = "";
  300. int ret;
  301. int cnt = 0;
  302. z->next_in = out;
  303. z->avail_in = 0;
  304. z->next_out = df;
  305. z->avail_out = dfallocated;
  306. while ((ret = deflate (z, Z_FINISH)) == Z_OK && (cnt++ <= 100)) {
  307. gvwrite_no_z(job, df, (size_t)(z->next_out - df));
  308. z->next_out = df;
  309. z->avail_out = dfallocated;
  310. }
  311. if (ret != Z_STREAM_END) {
  312. job->common->errorfn("deflation finish problem %d cnt=%d\n", ret, cnt);
  313. graphviz_exit(1);
  314. }
  315. gvwrite_no_z(job, df, (size_t)(z->next_out - df));
  316. ret = deflateEnd(z);
  317. if (ret != Z_OK) {
  318. job->common->errorfn("deflation end problem %d\n", ret);
  319. graphviz_exit(1);
  320. }
  321. out[0] = (unsigned char)crc;
  322. out[1] = (unsigned char)(crc >> 8);
  323. out[2] = (unsigned char)(crc >> 16);
  324. out[3] = (unsigned char)(crc >> 24);
  325. out[4] = (unsigned char)z->total_in;
  326. out[5] = (unsigned char)(z->total_in >> 8);
  327. out[6] = (unsigned char)(z->total_in >> 16);
  328. out[7] = (unsigned char)(z->total_in >> 24);
  329. gvwrite_no_z(job, out, sizeof(out));
  330. #else
  331. job->common->errorfn("No libz support\n");
  332. graphviz_exit(1);
  333. #endif
  334. }
  335. if (gvde) {
  336. if (gvde->finalize) {
  337. gvde->finalize(job);
  338. finalized_p = true;
  339. }
  340. }
  341. if (! finalized_p) {
  342. /* if the device has no finalization then it uses file output */
  343. gvflush (job);
  344. gvdevice_close(job);
  345. }
  346. }
  347. void gvprintf(GVJ_t * job, const char *format, ...)
  348. {
  349. agxbuf buf = {0};
  350. va_list argp;
  351. va_start(argp, format);
  352. int len = vagxbprint(&buf, format, argp);
  353. if (len < 0) {
  354. va_end(argp);
  355. agerrorf("gvprintf: %s\n", strerror(errno));
  356. return;
  357. }
  358. va_end(argp);
  359. gvwrite(job, agxbuse(&buf), (size_t)len);
  360. agxbfree(&buf);
  361. }
  362. /* Test with:
  363. * cc -DGVPRINTNUM_TEST gvprintnum.c -o gvprintnum
  364. */
  365. /* use macro so maxnegnum is stated just once for both double and string versions */
  366. #define val_str(n, x) static double n = x; static char n##str[] = #x;
  367. val_str(maxnegnum, -999999999999999.99)
  368. static void gvprintnum(agxbuf *xb, double number) {
  369. /*
  370. number limited to a working range: maxnegnum >= n >= -maxnegnum
  371. suppressing trailing "0" and "."
  372. */
  373. if (number < maxnegnum) { /* -ve limit */
  374. agxbput(xb, maxnegnumstr);
  375. return;
  376. }
  377. if (number > -maxnegnum) { /* +ve limit */
  378. agxbput(xb, maxnegnumstr + 1); // +1 to skip the '-' sign
  379. return;
  380. }
  381. agxbprint(xb, "%.03f", number);
  382. agxbuf_trim_zeros(xb);
  383. // strip off unnecessary leading '0'
  384. {
  385. char *staging = agxbdisown(xb);
  386. if (startswith(staging, "0.")) {
  387. memmove(staging, &staging[1], strlen(staging));
  388. } else if (startswith(staging, "-0.")) {
  389. memmove(&staging[1], &staging[2], strlen(&staging[1]));
  390. }
  391. agxbput(xb, staging);
  392. free(staging);
  393. }
  394. }
  395. #ifdef GVPRINTNUM_TEST
  396. int main (int argc, char *argv[])
  397. {
  398. agxbuf xb = {0};
  399. char *buf;
  400. size_t len;
  401. double test[] = {
  402. -maxnegnum*1.1, -maxnegnum*.9,
  403. 1e8, 10.008, 10, 1, .1, .01,
  404. .006, .005, .004, .001, 1e-8,
  405. 0, -0,
  406. -1e-8, -.001, -.004, -.005, -.006,
  407. -.01, -.1, -1, -10, -10.008, -1e8,
  408. maxnegnum*.9, maxnegnum*1.1
  409. };
  410. int i = sizeof(test) / sizeof(test[0]);
  411. while (i--) {
  412. gvprintnum(&xb, test[i]);
  413. buf = agxbuse(&xb);;
  414. fprintf (stdout, "%g = %s %d\n", test[i], buf, len);
  415. }
  416. agxbfree(&xb);
  417. graphviz_exit(0);
  418. }
  419. #endif
  420. /* gv_trim_zeros
  421. * Identify Trailing zeros and decimal point, if possible.
  422. * Assumes the input is the result of %.02f printing.
  423. */
  424. static size_t gv_trim_zeros(const char *buf) {
  425. char *dotp = strchr(buf, '.');
  426. if (dotp == NULL) {
  427. return strlen(buf);
  428. }
  429. // check this really is the result of %.02f printing
  430. assert(isdigit((int)dotp[1]) && isdigit((int)dotp[2]) && dotp[3] == '\0');
  431. if (dotp[2] == '0') {
  432. if (dotp[1] == '0') {
  433. return (size_t)(dotp - buf);
  434. } else {
  435. return (size_t)(dotp - buf) + 2;
  436. }
  437. }
  438. return strlen(buf);
  439. }
  440. void gvprintdouble(GVJ_t * job, double num)
  441. {
  442. // Prevents values like -0
  443. if (num > -0.005 && num < 0.005)
  444. {
  445. gvwrite(job, "0", 1);
  446. return;
  447. }
  448. char buf[50];
  449. snprintf(buf, 50, "%.02f", num);
  450. size_t len = gv_trim_zeros(buf);
  451. gvwrite(job, buf, len);
  452. }
  453. void gvprintpointf(GVJ_t * job, pointf p)
  454. {
  455. agxbuf xb = {0};
  456. gvprintnum(&xb, p.x);
  457. const char *buf = agxbuse(&xb);
  458. gvwrite(job, buf, strlen(buf));
  459. gvwrite(job, " ", 1);
  460. gvprintnum(&xb, p.y);
  461. buf = agxbuse(&xb);
  462. gvwrite(job, buf, strlen(buf));
  463. agxbfree(&xb);
  464. }
  465. void gvprintpointflist(GVJ_t *job, pointf *p, size_t n) {
  466. const char *separator = "";
  467. for (size_t i = 0; i < n; ++i) {
  468. gvputs(job, separator);
  469. gvprintpointf(job, p[i]);
  470. separator = " ";
  471. }
  472. }