1 /*****************************************************************************/
2 /* LibreDWG - free implementation of the DWG file format */
3 /* */
4 /* Copyright (C) 2018-2020 Free Software Foundation, Inc. */
5 /* */
6 /* This library is free software, licensed under the terms of the GNU */
7 /* General Public License as published by the Free Software Foundation, */
8 /* either version 3 of the License, or (at your option) any later version. */
9 /* You should have received a copy of the GNU General Public License */
10 /* along with this program. If not, see <http://www.gnu.org/licenses/>. */
11 /*****************************************************************************/
12
13 /* dxfwrite.c: write a DXF file from various input formats.
14 * written by Reini Urban
15 */
16
17 #include "../src/config.h"
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21 // strings.h or string.h
22 #ifdef AX_STRCASECMP_HEADER
23 # include AX_STRCASECMP_HEADER
24 #endif
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <getopt.h>
28 #ifdef HAVE_VALGRIND_VALGRIND_H
29 # include <valgrind/valgrind.h>
30 #endif
31
32 #include "dwg.h"
33 #include "common.h"
34 #include "bits.h"
35 #include "suffix.inc"
36 #include "decode.h"
37 #ifndef DISABLE_JSON
38 # include "in_json.h"
39 #endif
40 #include "out_dxf.h"
41
42 // avoid the slow fork loop, for afl-clang-fast
43 #ifdef __AFL_COMPILER
44 static volatile const char *__afl_persistent_sig = "##SIG_AFL_PERSISTENT##";
45 #endif
46
47 static int opts = 1;
48
49 static int help (void);
50
51 static int
usage(void)52 usage (void)
53 {
54 printf ("\nUsage: dxfwrite [-b] [-v[0-9]] [-y] [--as rNNNN] [-I FMT] [-o DXFFILE] "
55 "INFILE\n");
56 return 1;
57 }
58 static int
opt_version(void)59 opt_version (void)
60 {
61 printf ("dxfwrite %s\n", PACKAGE_VERSION);
62 return 0;
63 }
64 static int
help(void)65 help (void)
66 {
67 printf ("\nUsage: dxfwrite [OPTION]... [-o DXFFILE] INFILE\n");
68 printf ("Writes a DXF file from various input formats.\n"
69 "\n");
70 #ifdef HAVE_GETOPT_LONG
71 printf (" -v[0-9], --verbose [0-9] verbosity\n");
72 printf (" --as rNNNN save as version\n");
73 printf (" Valid versions:\n");
74 printf (" r12, r13, r14, r2000, r2004, r2007, r2010, r2013, r2018\n");
75 # ifndef DISABLE_JSON
76 printf (" -I fmt, --format fmt DWG, DXF, DXFB, JSON\n");
77 # else
78 printf (" -I fmt, --format fmt DWG, DXF, DXFB\n");
79 # endif
80 printf (
81 " Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
82 printf (" -o dxffile, --file \n");
83 printf (" -m, --minimal only $ACADVER, HANDSEED and "
84 "ENTITIES\n");
85 printf (" -b, --binary create a binary DXF\n");
86 printf (" -y, --overwrite overwrite existing files\n");
87 printf (" --help display this help and exit\n");
88 printf (" --version output version information and exit\n"
89 "\n");
90 #else
91 printf (" -v[0-9] verbosity\n");
92 printf (" -a rNNNN save as version\n");
93 printf (" Valid versions:\n");
94 printf (" r12, r13, r14, r2000, r2004, r2007, r2010, r2013, r2018\n");
95 /*
96 printf (" Planned versions:\n");
97 printf (" r9, r10, r11, r12\n");
98 */
99 # ifndef DISABLE_JSON
100 printf (" -I fmt fmt: DWG, DXF, DXFB, JSON\n");
101 # else
102 printf (" -I fmt fmt: DWG, DXF, DXFB\n");
103 # endif
104 printf (" Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
105 printf (" -o dxffile\n");
106 printf (" -m minimal, only $ACADVER, HANDSEED and ENTITIES\n");
107 printf (" -b create a binary DXF\n");
108 printf (" -y overwrite existing files\n");
109 printf (" -h display this help and exit\n");
110 printf (" -i output version information and exit\n"
111 "\n");
112 #endif
113 printf ("GNU LibreDWG online manual: "
114 "<https://www.gnu.org/software/libredwg/>\n");
115 return 0;
116 }
117
118 int
main(int argc,char * argv[])119 main (int argc, char *argv[])
120 {
121 int i = 1;
122 int error = 0;
123 Dwg_Data dwg;
124 const char *fmt = NULL;
125 const char *infile = NULL;
126 char *outfile = NULL;
127 Bit_Chain dat = { 0 };
128 const char *version = NULL;
129 Dwg_Version_Type dwg_version = R_INVALID;
130 int c;
131 int overwrite = 0;
132 int binary = 0;
133 int minimal = 0;
134 int force_free = 0;
135 int free_outfile = 0;
136 #ifdef HAVE_GETOPT_LONG
137 int option_index = 0;
138 static struct option long_options[]
139 = { { "verbose", 1, &opts, 1 }, // optional
140 { "format", 1, 0, 'I' }, { "file", 1, 0, 'o' },
141 { "as", 1, 0, 'a' },
142 { "minimal", 0, 0, 'm' }, { "binary", 0, 0, 'b' },
143 { "overwrite", 0, 0, 'y' }, { "version", 0, 0, 0 },
144 { "force-free", 0, 0, 0 }, { "help", 0, 0, 0 },
145 { NULL, 0, NULL, 0 } };
146 #endif
147
148 if (argc < 2)
149 return usage ();
150
151 while
152 #ifdef HAVE_GETOPT_LONG
153 ((c
154 = getopt_long (argc, argv, "ymba:v::I:o:h", long_options, &option_index))
155 != -1)
156 #else
157 ((c = getopt (argc, argv, "ymba:v::I:o:hi")) != -1)
158 #endif
159 {
160 if (c == -1)
161 break;
162 switch (c)
163 {
164 case ':': // missing arg
165 if (optarg && !strcmp (optarg, "v"))
166 {
167 opts = 1;
168 break;
169 }
170 fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
171 optopt);
172 break;
173 #ifdef HAVE_GETOPT_LONG
174 case 0:
175 /* This option sets a flag */
176 if (!strcmp (long_options[option_index].name, "verbose"))
177 {
178 if (opts < 0 || opts > 9)
179 return usage ();
180 # if defined(USE_TRACING) && defined(HAVE_SETENV)
181 {
182 char v[2];
183 *v = opts + '0';
184 *(v + 1) = 0;
185 setenv ("LIBREDWG_TRACE", v, 1);
186 }
187 # endif
188 break;
189 }
190 if (!strcmp (long_options[option_index].name, "version"))
191 return opt_version ();
192 if (!strcmp (long_options[option_index].name, "help"))
193 return help ();
194 if (!strcmp (long_options[option_index].name, "force-free"))
195 force_free = 1;
196 if (!strcmp (long_options[option_index].name, "binary"))
197 binary = 1;
198 break;
199 #else
200 case 'i':
201 return opt_version ();
202 #endif
203 case 'I':
204 fmt = optarg;
205 break;
206 case 'y':
207 overwrite = 1;
208 break;
209 case 'b':
210 binary = 1;
211 break;
212 case 'm':
213 minimal = 1;
214 break;
215 case 'o':
216 outfile = optarg;
217 break;
218 case 'a': // as
219 dwg_version = dwg_version_as (optarg);
220 if (dwg_version == R_INVALID)
221 {
222 fprintf (stderr, "Invalid version '%s'\n", argv[1]);
223 return usage ();
224 }
225 version = optarg;
226 break;
227 case 'v': // support -v3 and -v
228 i = (optind > 0 && optind < argc) ? optind - 1 : 1;
229 if (!memcmp (argv[i], "-v", 2))
230 {
231 opts = argv[i][2] ? argv[i][2] - '0' : 1;
232 }
233 if (opts < 0 || opts > 9)
234 return usage ();
235 #if defined(USE_TRACING) && defined(HAVE_SETENV)
236 {
237 char v[2];
238 *v = opts + '0';
239 *(v + 1) = 0;
240 setenv ("LIBREDWG_TRACE", v, 1);
241 }
242 #endif
243 break;
244 case 'h':
245 return help ();
246 case '?':
247 fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
248 optopt);
249 break;
250 default:
251 return usage ();
252 }
253 }
254 i = optind;
255
256 #ifdef __AFL_HAVE_MANUAL_CONTROL
257 // llvm_mode deferred init
258 __AFL_INIT();
259 #endif
260
261 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
262 while (__AFL_LOOP(1000)) { // llvm_mode persistent mode (currently broken)
263 #endif
264
265 // get input format from INFILE, not outfile.
266 // With stdin, should -I be mandatory, or try to autodetect the format?
267 // With a file use the extension.
268 if (optind < argc) // have arg
269 {
270 infile = argv[i];
271 if (!fmt)
272 {
273 if (strstr (infile, ".dwg") || strstr (infile, ".DWG"))
274 fmt = (char *)"dwg";
275 #ifndef DISABLE_DXF
276 # ifndef DISABLE_JSON
277 else if (strstr (infile, ".json") || strstr (infile, ".JSON"))
278 fmt = (char *)"json";
279 # endif
280 else if (strstr (infile, ".dxfb") || strstr (infile, ".DXFB"))
281 fmt = (char *)"dxfb";
282 else if (strstr (infile, ".dxf") || strstr (infile, ".DXF"))
283 fmt = (char *)"dxf";
284 else
285 #endif
286 fprintf (stderr, "Unknown input format for '%s'\n", infile);
287 }
288 }
289
290 // allow stdin, but require -I|--format then
291 memset (&dwg, 0, sizeof (Dwg_Data));
292 dwg.opts = opts;
293 if (version) // hint the importer
294 dwg.header.version = dat.version = dwg_version;
295
296 if (infile)
297 {
298 struct stat attrib;
299 if (stat (infile, &attrib)) // not exists
300 {
301 fprintf (stderr, "Missing input file '%s'\n", infile);
302 exit (1);
303 }
304 dat.fh = fopen (infile, "r");
305 if (!dat.fh)
306 {
307 fprintf (stderr, "Could not read file '%s'\n", infile);
308 exit (1);
309 }
310 dat.size = attrib.st_size;
311 }
312 else
313 dat.fh = stdin;
314
315 if ((fmt && !strcasecmp (fmt, "dwg"))
316 || (infile && !strcasecmp (infile, ".dwg")))
317 {
318 if (opts > 1)
319 fprintf (stderr, "Reading DWG file %s\n",
320 infile ? infile : "from stdin");
321 error = dwg_read_file (infile ? infile : "-", &dwg);
322 }
323 #ifndef DISABLE_DXF
324 # ifndef DISABLE_JSON
325 else if ((fmt && !strcasecmp (fmt, "json"))
326 || (infile && !strcasecmp (infile, ".json")))
327 {
328 if (opts > 1)
329 fprintf (stderr, "Reading JSON file %s\n",
330 infile ? infile : "from stdin");
331 error = dwg_read_json (&dat, &dwg);
332 }
333 # endif
334 else if ((fmt && !strcasecmp (fmt, "dxfb"))
335 || (infile && !strcasecmp (infile, ".dxfb")))
336 {
337 if (opts > 1)
338 fprintf (stderr, "Reading Binary DXF file %s\n",
339 infile ? infile : "from stdin");
340 error = dxf_read_file (infile ? infile : "-", &dwg);
341 }
342 else if ((fmt && !strcasecmp (fmt, "dxf"))
343 || (infile && !strcasecmp (infile, ".dxf")))
344 {
345 if (opts > 1)
346 fprintf (stderr, "Reading DXF file %s\n",
347 infile ? infile : "from stdin");
348 error = dxf_read_file (infile ? infile : "-", &dwg);
349 }
350 else
351 #endif
352 {
353 if (fmt)
354 fprintf (stderr, "Invalid or unsupported input format '%s'\n", fmt);
355 else if (infile)
356 fprintf (stderr, "Missing input format for '%s'\n", infile);
357 else
358 fprintf (stderr, "Missing input format\n");
359 if (infile)
360 fclose (dat.fh);
361 free (dat.chain);
362 exit (1);
363 }
364
365 free (dat.chain);
366 dat.size = 0;
367 if (infile && dat.fh)
368 {
369 fclose (dat.fh);
370 dat.fh = NULL;
371 }
372 if (error >= DWG_ERR_CRITICAL)
373 goto free;
374
375 if (!version)
376 dat.version = dwg.header.version = dwg.header.from_version;
377 if (minimal)
378 dwg.opts |= DWG_OPTS_MINIMAL;
379 dwg.opts |= opts;
380
381 if (!outfile)
382 {
383 outfile = suffix (infile, "dxf");
384 free_outfile = 1;
385 }
386
387 if (opts > 1)
388 {
389 fprintf (stderr, "Writing %s%sDXF file %s", minimal ? "minimal " : "",
390 binary ? "binary " : "", outfile);
391 if (version)
392 fprintf (stderr, " (from %s to %s)\n",
393 dwg_version_type (dwg.header.from_version),
394 dwg_version_type (dwg.header.version));
395 else
396 fprintf (stderr, " (%s)\n", dwg_version_type (dwg.header.version));
397 }
398
399 {
400 struct stat attrib;
401 if (stat (outfile, &attrib))
402 dat.fh = fopen (outfile, "wb");
403 else // exists
404 {
405 if (!overwrite)
406 {
407 fprintf (stderr, "File not overwritten: %s, use -y.\n", outfile);
408 error |= DWG_ERR_IOERROR;
409 }
410 else
411 {
412 if (S_ISREG (attrib.st_mode) && // refuse to remove a directory
413 (access (outfile, W_OK) == 0) // writable
414 #ifndef _WIN32
415 // refuse to remove a symlink. even with overwrite. security
416 && !S_ISLNK (attrib.st_mode)
417 #endif
418 )
419 {
420 unlink (outfile);
421 dat.fh = fopen (outfile, "wb");
422 }
423 else if ( // for fuzzing mainly
424 #ifdef _WIN32
425 strEQc (outfile, "NUL")
426 #else
427 strEQc (outfile, "/dev/null")
428 #endif
429 )
430 {
431 dat.fh = fopen (outfile, "wb");
432 }
433 else
434 {
435 fprintf (stderr, "Not writable file or symlink: %s\n",
436 outfile);
437 error |= DWG_ERR_IOERROR;
438 }
439 }
440 }
441 }
442 if (!dat.fh)
443 {
444 fprintf (stderr, "WRITE ERROR %s\n", outfile);
445 error |= DWG_ERR_IOERROR;
446 }
447 else
448 {
449 error |= binary
450 ? dwg_write_dxfb (&dat, &dwg)
451 : dwg_write_dxf (&dat, &dwg);
452 }
453 if (dat.fh)
454 fclose (dat.fh);
455
456 free:
457 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
458 {
459 char *asanenv = getenv("ASAN_OPTIONS");
460 if (!asanenv)
461 force_free = 1;
462 // detect_leaks is enabled by default. see if it's turned off
463 else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
464 force_free = 1;
465 }
466 #endif
467 // forget about leaks. really huge DWG's need endlessly here.
468 if ((dwg.header.version && dwg.num_objects < 1)
469 || force_free
470 #ifdef HAVE_VALGRIND_VALGRIND_H
471 || (RUNNING_ON_VALGRIND)
472 #endif
473 )
474 {
475 dwg_free (&dwg);
476 }
477
478 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
479 } // __AFL_LOOP(1000) persistent mode
480 #endif
481
482 if (error >= DWG_ERR_CRITICAL)
483 {
484 fprintf (stderr, "ERROR 0x%x\n", error);
485 if (error && opts > 2)
486 dwg_errstrings (error);
487 }
488 else
489 {
490 if (opts > 1)
491 {
492 fprintf (stderr, "SUCCESS 0x%x\n", error);
493 if (error && opts > 2)
494 dwg_errstrings (error);
495 }
496 else
497 fprintf (stderr, "SUCCESS\n");
498 }
499
500 if (free_outfile)
501 free (outfile);
502 return error >= DWG_ERR_CRITICAL ? 1 : 0;
503 }
504