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 /*
14 * dxf2dwg.c: save a DXF as DWG. Detect ascii/binary. No minimal, only full.
15 * Optionally as a different version. Only r2000 encode-support so far.
16 *
17 * written by Reini Urban
18 */
19
20 #include "../src/config.h"
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #ifdef HAVE_UNISTD_H
25 # include <unistd.h>
26 #endif
27 #include "my_stat.h"
28 #include "my_getopt.h"
29 #ifdef HAVE_VALGRIND_VALGRIND_H
30 # include <valgrind/valgrind.h>
31 #endif
32
33 #include <dwg.h>
34 #include "common.h"
35 #include "bits.h"
36 #include "logging.h"
37 #include "suffix.inc"
38
39 #ifdef __AFL_COMPILER
40 # include "decode.h"
41 # include "encode.h"
42 # include "in_dxf.h"
43 #endif
44
45 static int help (void);
46
47 static int opts = 1;
48 int minimal = 0;
49 int binary = 0;
50 int overwrite = 0;
51 char buf[4096];
52 /* the current version per spec block */
53 static unsigned int cur_ver = 0;
54
55 static int
56 usage (void)
57 {
58 printf ("\nUsage: dxf2dwg [-v[N]] [-y] [--as rNNNN] [-o DWG] DXFFILES...\n");
59 return 1;
60 }
61 static int
62 opt_version (void)
63 {
64 printf ("dxf2dwg %s\n", PACKAGE_VERSION);
65 return 0;
66 }
67 static int
68 help (void)
69 {
70 printf ("\nUsage: dxf2dwg [OPTION]... DXFFILES ...\n");
71 printf ("Converts the DXF to a DWG. Accepts ascii and binary DXF.\n");
72 printf ("Default DWGFILE: DXFFILE with .dwg extension in the current "
73 "directory.\n"
74 "Existing files are not overwritten, unless -y is given.\n"
75 "Encoding currently only works for R13-R2000.\n"
76 "\n");
77 #ifdef HAVE_GETOPT_LONG
78 printf (" -v[0-9], --verbose [0-9] verbosity\n");
79 printf (" --as rNNNN save as version\n");
80 printf (" Valid versions:\n");
81 printf (" r12, r14, r2000 (default)\n");
82 printf (" Planned versions:\n");
83 printf (" r9, r10, r11, r2004, r2007, r2010, r2013, r2018\n");
84 printf (" -o outfile, --file optional, only valid with one single "
85 "DXFFILE\n");
86 printf (" --help display this help and exit\n");
87 printf (" --version output version information and exit\n"
88 "\n");
89 #else
90 printf (" -v[0-9] verbosity\n");
91 printf (" -a rNNNN save as version\n");
92 printf (" Valid versions:\n");
93 printf (" r12, r14, r2000 (default)\n");
94 printf (" Planned versions:\n");
95 printf (" r9, r10, r11, r2004, r2007, r2010, r2013, r2018\n");
96 printf (" -o dwgfile optional, only valid with one single DXFFILE\n");
97 printf (" -h display this help and exit\n");
98 printf (" -i output version information and exit\n"
99 "\n");
100 #endif
101 printf ("GNU LibreDWG online manual: "
102 "<https://www.gnu.org/software/libredwg/>\n");
103 return 0;
104 }
105
106 // lsan/valgrind leaks still TODO. GH #151
107 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
108 const char *__asan_default_options (void);
109 const char *
110 __asan_default_options (void)
111 {
112 return "detect_leaks=0";
113 }
114 #endif
115
116 #ifdef __AFL_COMPILER
117 __AFL_FUZZ_INIT ();
118 // fastest mode via shared mem
119 # define AFL_SHARED_MEM
120
121 int
122 main (int argc, char *argv[])
123 {
124 Dwg_Data dwg;
125 Bit_Chain dat = { NULL, 0, 0, 0, 0 };
126 Bit_Chain out_dat = { NULL, 0, 0, 0, 0 };
127 FILE *fp;
128 struct stat attrib;
129
130 __AFL_INIT ();
131 // dat.opts = 3;
132 # ifdef AFL_SHARED_MEM
133 dat.chain = __AFL_FUZZ_TESTCASE_BUF;
134 # endif
135
136 while (__AFL_LOOP (10000))
137 { // llvm_mode persistent, non-forking mode
138 # ifdef AFL_SHARED_MEM
139 dat.size = __AFL_FUZZ_TESTCASE_LEN;
140 printf ("Fuzzing in_dxf + encode from shmem (%lu)\n", dat.size);
141 # elif 0 // still 10x faster than the old file-forking fuzzer.
142 /* from stdin: */
143 dat.size = 0;
144 // dat.chain = NULL;
145 dat_read_stream (&dat, stdin);
146 printf ("Fuzzing in_dxf + encode from stdin (%lu)\n", dat.size);
147 # else
148 /* else from file */
149 fp = fopen (argv[1], "rb");
150 if (!fp)
151 return 0;
152 dat.size = 0;
153 dat_read_file (&dat, fp, argv[1]);
154 fclose (fp);
155 printf ("Fuzzing in_dxf + encode from file (%lu)\n", dat.size);
156 # endif
157
158 if (dat.size < 100)
159 continue; // useful minimum input length
160 if (dwg_read_dxf (&dat, &dwg) <= DWG_ERR_CRITICAL)
161 {
162 memset (&out_dat, 0, sizeof (out_dat));
163 bit_chain_set_version (&out_dat, &dat);
164 dat.codepage = dwg.header.codepage;
165 out_dat.version = R_2000;
166 dwg_encode (&dwg, &out_dat);
167 free (out_dat.chain);
168 dwg_free (&dwg);
169 }
170 }
171 dwg_free (&dwg);
172 }
173 # define main orig_main
174 int orig_main (int argc, char *argv[]);
175 #endif
176
177 int
178 main (int argc, char *argv[])
179 {
180 int i = 1;
181 int error = 0;
182 Dwg_Data dwg;
183 char *filename_in;
184 const char *version = NULL;
185 char *filename_out = NULL;
186 Dwg_Version_Type dwg_version = R_2000;
187 int do_free = 0;
188 int need_free = 0;
189 int c;
190 #ifdef HAVE_GETOPT_LONG
191 int option_index = 0;
192 static struct option long_options[]
193 = { { "verbose", 1, &opts, 1 }, // optional
194 { "file", 1, 0, 'o' }, { "as", 1, 0, 'a' },
195 { "overwrite", 0, 0, 'y' }, { "help", 0, 0, 0 },
196 { "force-free", 0, 0, 0 }, { "version", 0, 0, 0 },
197 { NULL, 0, NULL, 0 } };
198 #endif
199
200 if (argc < 2)
201 return usage ();
202
203 while
204 #ifdef HAVE_GETOPT_LONG
205 ((c = getopt_long (argc, argv, "ya:v::o:h", long_options, &option_index))
206 != -1)
207 #else
208 ((c = getopt (argc, argv, ":a:v::o:hi")) != -1)
209 #endif
210 {
211 if (c == -1)
212 break;
213 switch (c)
214 {
215 case ':': // missing arg
216 if (optarg && !strcmp (optarg, "v"))
217 {
218 opts = 1;
219 break;
220 }
221 fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
222 optopt);
223 break;
224 #ifdef HAVE_GETOPT_LONG
225 case 0:
226 /* This option sets a flag */
227 if (!strcmp (long_options[option_index].name, "verbose"))
228 {
229 if (opts < 0 || opts > 9)
230 return usage ();
231 # if defined(USE_TRACING) && defined(HAVE_SETENV)
232 {
233 char v[2];
234 *v = opts + '0';
235 *(v + 1) = 0;
236 setenv ("LIBREDWG_TRACE", v, 1);
237 }
238 # endif
239 break;
240 }
241 if (!strcmp (long_options[option_index].name, "version"))
242 return opt_version ();
243 if (!strcmp (long_options[option_index].name, "help"))
244 return help ();
245 if (!strcmp (long_options[option_index].name, "force-free"))
246 do_free = 1;
247 break;
248 #else
249 case 'i':
250 return opt_version ();
251 #endif
252 case 'o':
253 filename_out = optarg;
254 break;
255 case 'a':
256 dwg_version = dwg_version_as (optarg);
257 if (dwg_version == R_INVALID)
258 {
259 fprintf (stderr, "Invalid version '%s'\n", argv[1]);
260 return usage ();
261 }
262 version = optarg;
263 break;
264 case 'v': // support -v3 and -v
265 i = (optind > 0 && optind < argc) ? optind - 1 : 1;
266 if (!memcmp (argv[i], "-v", 2))
267 {
268 opts = argv[i][2] ? argv[i][2] - '0' : 1;
269 }
270 if (opts < 0 || opts > 9)
271 return usage ();
272 #if defined(USE_TRACING) && defined(HAVE_SETENV)
273 {
274 char v[2];
275 *v = opts + '0';
276 *(v + 1) = 0;
277 setenv ("LIBREDWG_TRACE", v, 1);
278 }
279 #endif
280 break;
281 case 'y':
282 overwrite = 1;
283 break;
284 case 'h':
285 return help ();
286 case '?':
287 fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
288 optopt);
289 break;
290 default:
291 return usage ();
292 }
293 }
294 i = optind;
295
296 if (filename_out != NULL && (i + 1) < argc)
297 {
298 fprintf (stderr, "%s: no -o with multiple input files\n", argv[0]);
299 return usage ();
300 }
301 do_free |= (i + 1) < argc; // if more than one file
302
303 while (i < argc)
304 {
305 filename_in = argv[i];
306 i++;
307 if (!filename_out)
308 {
309 need_free = 1;
310 filename_out = suffix (filename_in, "dwg");
311 }
312
313 if (strEQ (filename_in, filename_out))
314 {
315 if (filename_out != argv[i - 1])
316 free (filename_out);
317 return usage ();
318 }
319
320 dwg.opts = opts;
321 dwg.header.version = dwg_version;
322 printf ("Reading DXF file %s\n", filename_in);
323 error = dxf_read_file (filename_in, &dwg);
324 if (error >= DWG_ERR_CRITICAL)
325 {
326 fprintf (stderr, "READ ERROR 0x%x %s\n", error, filename_in);
327 if (need_free)
328 free (filename_out);
329 if (do_free
330 #ifdef HAVE_VALGRIND_VALGRIND_H
331 || (RUNNING_ON_VALGRIND)
332 #endif
333 )
334 dwg_free (&dwg);
335 continue;
336 }
337
338 dwg.opts |= opts;
339 printf ("Writing DWG file %s", filename_out);
340 if (version)
341 {
342 printf (" as %s\n", version);
343 dwg.header.version = dwg_version;
344 if (dwg_version > R_2000)
345 printf ("Warning: encode currently only works for R13-R2000.\n");
346 if (dwg.header.from_version == R_INVALID)
347 dwg.header.from_version = dwg.header.version;
348 }
349 else
350 {
351 // FIXME: for now only R_13b1 - R_2000. later remove this line.
352 if (dwg.header.from_version < R_13b1
353 || dwg.header.from_version >= R_2004)
354 dwg.header.version = dwg_version;
355 if (dwg.header.from_version == R_INVALID)
356 dwg.header.from_version = dwg.header.version;
357 if (dwg.header.version == R_INVALID)
358 dwg.header.version = dwg.header.from_version;
359 printf ("\n");
360 }
361
362 #ifdef USE_WRITE
363 {
364 struct stat attrib;
365 if (!stat (filename_out, &attrib)) // exists
366 {
367 if (!overwrite)
368 {
369 LOG_ERROR ("File not overwritten: %s, use -y.\n",
370 filename_out);
371 error |= DWG_ERR_IOERROR;
372 }
373 else
374 {
375 if (S_ISREG (attrib.st_mode) && // refuse to remove a directory
376 (access (filename_out, W_OK) == 0) // writable
377 # ifndef _WIN32
378 // refuse to remove a symlink. even with overwrite.
379 // security
380 && !S_ISLNK (attrib.st_mode)
381 # endif
382 )
383 {
384 unlink (filename_out);
385 error = dwg_write_file (filename_out, &dwg);
386 }
387 else if (
388 # ifdef _WIN32
389 strEQc (filename_out, "NUL")
390 # else
391 strEQc (filename_out, "/dev/null")
392 # endif
393 )
394 {
395 error = dwg_write_file (filename_out, &dwg);
396 }
397 else
398 {
399 LOG_ERROR ("Not writable file or symlink: %s\n",
400 filename_out);
401 error |= DWG_ERR_IOERROR;
402 }
403 }
404 }
405 else
406 error = dwg_write_file (filename_out, &dwg);
407 }
408 #else
409 error = DWG_ERR_IOERROR;
410 # error no DWG write support
411 #endif
412 if (error)
413 fprintf (stderr, "WRITE ERROR 0x%x %s\n", error, filename_out);
414
415 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
416 {
417 char *asanenv = getenv ("ASAN_OPTIONS");
418 if (!asanenv)
419 do_free = 1;
420 // detect_leaks is enabled by default. see if it's turned off
421 else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
422 do_free = 1;
423 }
424 #endif
425 // forget about leaks. really huge DWG's need endlessly here.
426 if (do_free
427 #ifdef HAVE_VALGRIND_VALGRIND_H
428 || (RUNNING_ON_VALGRIND)
429 #endif
430 )
431 {
432 dwg_free (&dwg);
433 if (need_free)
434 free (filename_out);
435 }
436 filename_out = NULL;
437 }
438
439 // but only the result of the last conversion
440 return error >= DWG_ERR_CRITICAL ? 1 : 0;
441 }