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 #ifdef HAVE_UNISTD_H
26 # include <unistd.h>
27 #endif
28 #include "my_stat.h"
29 #include "my_getopt.h"
30 #ifdef HAVE_VALGRIND_VALGRIND_H
31 # include <valgrind/valgrind.h>
32 #endif
33
34 #include "dwg.h"
35 #include "common.h"
36 #include "bits.h"
37 #include "suffix.inc"
38 #include "decode.h"
39 #ifndef DISABLE_JSON
40 # include "in_json.h"
41 #endif
42 #include "out_dxf.h"
43
44 // avoid the slow fork loop, for afl-clang-fast
45 #ifdef __AFL_COMPILER
46 static volatile const char *__afl_persistent_sig = "##SIG_AFL_PERSISTENT##";
47 #endif
48
49 static int opts = 1;
50
51 static int help (void);
52
53 static int
54 usage (void)
55 {
56 printf ("\nUsage: dxfwrite [-b] [-v[0-9]] [-y] [--as rNNNN] [-I FMT] [-o "
57 "DXFFILE] "
58 "INFILE\n");
59 return 1;
60 }
61 static int
62 opt_version (void)
63 {
64 printf ("dxfwrite %s\n", PACKAGE_VERSION);
65 return 0;
66 }
67 static int
68 help (void)
69 {
70 printf ("\nUsage: dxfwrite [OPTION]... [-o DXFFILE] INFILE\n");
71 printf ("Writes a DXF file from various input formats.\n"
72 "\n");
73 #ifdef HAVE_GETOPT_LONG
74 printf (" -v[0-9], --verbose [0-9] verbosity\n");
75 printf (" --as rNNNN save as version\n");
76 printf (" Valid versions:\n");
77 printf (" r9, r10, r11, r12, r13, r14, r2000, r2004, r2007,"
78 " r2010, r2013, r2018, r2021\n");
79 # ifndef DISABLE_JSON
80 printf (" -I fmt, --format fmt DWG, DXF, DXFB, JSON\n");
81 # else
82 printf (" -I fmt, --format fmt DWG, DXF, DXFB\n");
83 # endif
84 printf (" Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
85 printf (" -o dxffile, --file \n");
86 printf (" -m, --minimal only $ACADVER, HANDSEED and "
87 "ENTITIES\n");
88 printf (" -b, --binary create a binary DXF\n");
89 printf (" -y, --overwrite overwrite existing files\n");
90 printf (" --help display this help and exit\n");
91 printf (" --version output version information and exit\n"
92 "\n");
93 #else
94 printf (" -v[0-9] verbosity\n");
95 printf (" -a rNNNN save as version\n");
96 printf (" Valid versions:\n");
97 printf (" r9, r10, r11, r12, r13, r14, r2000, r2004, r2007,"
98 " r2010, r2013, r2018, r2021\n");
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 (
105 " Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
106 printf (" -o dxffile\n");
107 printf (" -m minimal, only $ACADVER, HANDSEED and ENTITIES\n");
108 printf (" -b create a binary DXF\n");
109 printf (" -y overwrite existing files\n");
110 printf (" -h display this help and exit\n");
111 printf (" -i output version information and exit\n"
112 "\n");
113 #endif
114 printf ("GNU LibreDWG online manual: "
115 "<https://www.gnu.org/software/libredwg/>\n");
116 return 0;
117 }
118
119 int
120 main (int argc, char *argv[])
121 {
122 int i = 1;
123 int error = 0;
124 Dwg_Data dwg;
125 const char *fmt = NULL;
126 const char *infile = NULL;
127 char *outfile = NULL;
128 Bit_Chain dat = { 0 };
129 const char *version = NULL;
130 Dwg_Version_Type dwg_version = R_INVALID;
131 int c;
132 int overwrite = 0;
133 int binary = 0;
134 int minimal = 0;
135 int force_free = 0;
136 int free_outfile = 0;
137 #ifdef HAVE_GETOPT_LONG
138 int option_index = 0;
139 static struct option long_options[]
140 = { { "verbose", 1, &opts, 1 }, // optional
141 { "format", 1, 0, 'I' }, { "file", 1, 0, 'o' },
142 { "as", 1, 0, 'a' }, { "minimal", 0, 0, 'm' },
143 { "binary", 0, 0, 'b' }, { "overwrite", 0, 0, 'y' },
144 { "version", 0, 0, 0 }, { "force-free", 0, 0, 0 },
145 { "help", 0, 0, 0 }, { NULL, 0, NULL, 0 } };
146 #endif
147
148 if (argc < 2)
149 return usage ();
150
151 while
152 #ifdef HAVE_GETOPT_LONG
153 ((c = getopt_long (argc, argv, "ymba:v::I:o:h", long_options,
154 &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))
263 { // llvm_mode persistent mode (currently broken)
264 #endif
265
266 // get input format from INFILE, not outfile.
267 // With stdin, should -I be mandatory, or try to autodetect the format?
268 // With a file use the extension.
269 if (optind < argc) // have arg
270 {
271 infile = argv[i];
272 if (!fmt)
273 {
274 if (strstr (infile, ".dwg") || strstr (infile, ".DWG"))
275 fmt = (char *)"dwg";
276 #ifndef DISABLE_DXF
277 # ifndef DISABLE_JSON
278 else if (strstr (infile, ".json") || strstr (infile, ".JSON"))
279 fmt = (char *)"json";
280 # endif
281 else if (strstr (infile, ".dxfb") || strstr (infile, ".DXFB"))
282 fmt = (char *)"dxfb";
283 else if (strstr (infile, ".dxf") || strstr (infile, ".DXF"))
284 fmt = (char *)"dxf";
285 else
286 #endif
287 fprintf (stderr, "Unknown input format for '%s'\n", infile);
288 }
289 }
290
291 // allow stdin, but require -I|--format then
292 memset (&dwg, 0, sizeof (Dwg_Data));
293 dwg.opts = opts;
294 if (version) // hint the importer
295 dwg.header.version = dat.version = dwg_version;
296
297 if (infile)
298 {
299 struct stat attrib;
300 if (stat (infile, &attrib)) // not exists
301 {
302 fprintf (stderr, "Missing input file '%s'\n", infile);
303 exit (1);
304 }
305 dat.fh = fopen (infile, "r");
306 if (!dat.fh)
307 {
308 fprintf (stderr, "Could not read file '%s'\n", infile);
309 exit (1);
310 }
311 dat.size = attrib.st_size;
312 }
313 else
314 dat.fh = stdin;
315
316 if ((fmt && !strcasecmp (fmt, "dwg"))
317 || (infile && !strcasecmp (infile, ".dwg")))
318 {
319 if (opts > 1)
320 fprintf (stderr, "Reading DWG file %s\n",
321 infile ? infile : "from stdin");
322 error = dwg_read_file (infile ? infile : "-", &dwg);
323 }
324 #ifndef DISABLE_DXF
325 # ifndef DISABLE_JSON
326 else if ((fmt && !strcasecmp (fmt, "json"))
327 || (infile && !strcasecmp (infile, ".json")))
328 {
329 if (opts > 1)
330 fprintf (stderr, "Reading JSON file %s\n",
331 infile ? infile : "from stdin");
332 error = dwg_read_json (&dat, &dwg);
333 }
334 # endif
335 else if ((fmt && !strcasecmp (fmt, "dxfb"))
336 || (infile && !strcasecmp (infile, ".dxfb")))
337 {
338 if (opts > 1)
339 fprintf (stderr, "Reading Binary DXF file %s\n",
340 infile ? infile : "from stdin");
341 error = dxf_read_file (infile ? infile : "-", &dwg);
342 }
343 else if ((fmt && !strcasecmp (fmt, "dxf"))
344 || (infile && !strcasecmp (infile, ".dxf")))
345 {
346 if (opts > 1)
347 fprintf (stderr, "Reading DXF file %s\n",
348 infile ? infile : "from stdin");
349 error = dxf_read_file (infile ? infile : "-", &dwg);
350 }
351 else
352 #endif
353 {
354 if (fmt)
355 fprintf (stderr, "Invalid or unsupported input format '%s'\n",
356 fmt);
357 else if (infile)
358 fprintf (stderr, "Missing input format for '%s'\n", infile);
359 else
360 fprintf (stderr, "Missing input format\n");
361 if (infile)
362 fclose (dat.fh);
363 free (dat.chain);
364 exit (1);
365 }
366
367 free (dat.chain);
368 dat.size = 0;
369 if (infile && dat.fh)
370 {
371 fclose (dat.fh);
372 dat.fh = NULL;
373 }
374 if (error >= DWG_ERR_CRITICAL)
375 goto free;
376
377 if (!version)
378 dat.version = dwg.header.version = dwg.header.from_version;
379 if (minimal)
380 dwg.opts |= DWG_OPTS_MINIMAL;
381 dwg.opts |= opts;
382
383 if (!outfile)
384 {
385 outfile = suffix (infile, "dxf");
386 free_outfile = 1;
387 }
388
389 if (opts > 1)
390 {
391 fprintf (stderr, "Writing %s%sDXF file %s",
392 minimal ? "minimal " : "", binary ? "binary " : "",
393 outfile);
394 if (version)
395 fprintf (stderr, " (from %s to %s)\n",
396 dwg_version_type (dwg.header.from_version),
397 dwg_version_type (dwg.header.version));
398 else
399 fprintf (stderr, " (%s)\n", dwg_version_type (dwg.header.version));
400 }
401
402 {
403 struct stat attrib;
404 if (stat (outfile, &attrib))
405 dat.fh = fopen (outfile, "wb");
406 else // exists
407 {
408 if (!overwrite)
409 {
410 fprintf (stderr, "File not overwritten: %s, use -y.\n",
411 outfile);
412 error |= DWG_ERR_IOERROR;
413 }
414 else
415 {
416 if (S_ISREG (attrib.st_mode) && // refuse to remove a directory
417 (access (outfile, W_OK) == 0) // writable
418 #ifndef _WIN32
419 // refuse to remove a symlink. even with overwrite.
420 // security
421 && !S_ISLNK (attrib.st_mode)
422 #endif
423 )
424 {
425 unlink (outfile);
426 dat.fh = fopen (outfile, "wb");
427 }
428 else if ( // for fuzzing mainly
429 #ifdef _WIN32
430 strEQc (outfile, "NUL")
431 #else
432 strEQc (outfile, "/dev/null")
433 #endif
434 )
435 {
436 dat.fh = fopen (outfile, "wb");
437 }
438 else
439 {
440 fprintf (stderr, "Not writable file or symlink: %s\n",
441 outfile);
442 error |= DWG_ERR_IOERROR;
443 }
444 }
445 }
446 }
447 if (!dat.fh)
448 {
449 fprintf (stderr, "WRITE ERROR %s\n", outfile);
450 error |= DWG_ERR_IOERROR;
451 }
452 else
453 {
454 error |= binary ? dwg_write_dxfb (&dat, &dwg)
455 : dwg_write_dxf (&dat, &dwg);
456 }
457 if (dat.fh)
458 fclose (dat.fh);
459
460 free:
461 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
462 {
463 char *asanenv = getenv ("ASAN_OPTIONS");
464 if (!asanenv)
465 force_free = 1;
466 // detect_leaks is enabled by default. see if it's turned off
467 else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
468 force_free = 1;
469 }
470 #endif
471 // forget about leaks. really huge DWG's need endlessly here.
472 if ((dwg.header.version && dwg.num_objects < 1) || force_free
473 #ifdef HAVE_VALGRIND_VALGRIND_H
474 || (RUNNING_ON_VALGRIND)
475 #endif
476 )
477 {
478 dwg_free (&dwg);
479 }
480
481 #ifdef __xxAFL_HAVE_MANUAL_CONTROL
482 } // __AFL_LOOP(1000) persistent mode
483 #endif
484
485 if (error >= DWG_ERR_CRITICAL)
486 {
487 fprintf (stderr, "ERROR 0x%x\n", error);
488 if (error && opts > 2)
489 dwg_errstrings (error);
490 }
491 else
492 {
493 if (opts > 1)
494 {
495 fprintf (stderr, "SUCCESS 0x%x\n", error);
496 if (error && opts > 2)
497 dwg_errstrings (error);
498 }
499 else
500 fprintf (stderr, "SUCCESS\n");
501 }
502
503 if (free_outfile)
504 free (outfile);
505 return error >= DWG_ERR_CRITICAL ? 1 : 0;
506 }