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