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  * dwg2dxf.c: save a DWG as DXF.
15  * optionally as a different version.
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 <unistd.h>
25 #include <sys/stat.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 #include "out_dxf.h"
37 
38 static int opts = 1;
39 int minimal = 0;
40 int binary = 0;
41 int overwrite = 0;
42 char buf[4096];
43 /* the current version per spec block */
44 static unsigned int cur_ver = 0;
45 
46 static int
usage(void)47 usage (void)
48 {
49   printf ("\nUsage: dwg2dxf [-v[N]] [--as rNNNN] [-m|--minimal] [-b|--binary] "
50           "DWGFILES...\n");
51   return 1;
52 }
53 static int
opt_version(void)54 opt_version (void)
55 {
56   printf ("dwg2dxf %s\n", PACKAGE_VERSION);
57   return 0;
58 }
59 static int
help(void)60 help (void)
61 {
62   printf ("\nUsage: dwg2dxf [OPTION]... DWGFILES...\n");
63   printf ("Converts DWG files to DXF.\n");
64   printf ("Default DXFFILE: DWGFILE with .dxf extension in the current "
65           "directory.\n"
66           "Existing files are not overwritten, unless -y is given.\n"
67           "\n");
68 #ifdef HAVE_GETOPT_LONG
69   printf ("  -v[0-9], --verbose [0-9]  verbosity\n");
70   printf ("  --as rNNNN                save as version\n");
71   printf ("           Valid versions:\n");
72   printf ("             r12, r14, r2000, r2004, r2007, r2010, r2013\n");
73   printf ("           Planned versions:\n");
74   printf ("             r9, r10, r11, r2018\n");
75   printf ("  -m, --minimal             only $ACADVER, HANDSEED and "
76           "ENTITIES\n");
77   printf ("  -b, --binary              save as binary DXF\n");
78   printf ("  -y, --overwrite           overwrite existing files\n");
79   printf ("  -o outfile, --file        optional, only valid with one single "
80           "DWGFILE\n");
81   printf ("      --help                display this help and exit\n");
82   printf ("      --version             output version information and exit\n"
83           "\n");
84 #else
85   printf ("  -v[0-9]     verbosity\n");
86   printf ("  -a rNNNN    save as version\n");
87   printf ("              Valid versions:\n");
88   printf ("                r12, r14, r2000 (default)\n");
89   printf ("              Planned versions:\n");
90   printf ("                r9, r10, r11, r2004, r2007, r2010, r2013, r2018\n");
91   printf ("  -m          minimal, only $ACADVER, HANDSEED and ENTITIES\n");
92   printf ("  -b          save as binary DXF\n");
93   printf ("  -y          overwrite existing files\n");
94   printf ("  -o dwgfile  optional, only valid with one single DWGFILE\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 int
main(int argc,char * argv[])105 main (int argc, char *argv[])
106 {
107   int i = 1;
108   int error = 0;
109   Dwg_Data dwg;
110   char *filename_in;
111   const char *version = NULL;
112   char *filename_out = NULL;
113   Dwg_Version_Type dwg_version = R_2000;
114   Bit_Chain dat = { 0 };
115   int do_free = 0;
116   int need_free = 0;
117   int c;
118 #ifdef HAVE_GETOPT_LONG
119   int option_index = 0;
120   static struct option long_options[]
121       = { { "verbose", 1, &opts, 1 }, // optional
122           { "file", 1, 0, 'o' },      { "as", 1, 0, 'a' },
123           { "minimal", 0, 0, 'm' },   { "binary", 0, 0, 'b' },
124           { "overwrite", 0, 0, 'y' }, { "help", 0, 0, 0 },
125           { "force-free", 0, 0, 0 },
126           { "version", 0, 0, 0 },     { NULL, 0, NULL, 0 } };
127 #endif
128 
129   if (argc < 2)
130     return usage ();
131 
132   while
133 #ifdef HAVE_GETOPT_LONG
134       ((c
135         = getopt_long (argc, argv, "mbya:v::o:h", long_options, &option_index))
136        != -1)
137 #else
138       ((c = getopt (argc, argv, ":mba:v::o:hi")) != -1)
139 #endif
140     {
141       if (c == -1)
142         break;
143       switch (c)
144         {
145         case ':': // missing arg
146           if (optarg && !strcmp (optarg, "v"))
147             {
148               opts = 1;
149               break;
150             }
151           fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
152                    optopt);
153           break;
154 #ifdef HAVE_GETOPT_LONG
155         case 0:
156           /* This option sets a flag */
157           if (!strcmp (long_options[option_index].name, "verbose"))
158             {
159               if (opts < 0 || opts > 9)
160                 return usage ();
161 #  if defined(USE_TRACING) && defined(HAVE_SETENV)
162               {
163                 char v[2];
164                 *v = opts + '0';
165                 *(v + 1) = 0;
166                 setenv ("LIBREDWG_TRACE", v, 1);
167               }
168 #  endif
169               break;
170             }
171           if (!strcmp (long_options[option_index].name, "version"))
172             return opt_version ();
173           if (!strcmp (long_options[option_index].name, "help"))
174             return help ();
175           if (!strcmp (long_options[option_index].name, "force-free"))
176             do_free = 1;
177           break;
178 #else
179         case 'i':
180           return opt_version ();
181 #endif
182         case 'm':
183           minimal = 1;
184           break;
185         case 'b':
186           binary = 1;
187           break;
188         case 'y':
189           overwrite = 1;
190           break;
191         case 'o':
192           filename_out = optarg;
193           break;
194         case 'a':
195           dwg_version = dwg_version_as (optarg);
196           if (dwg_version == R_INVALID)
197             {
198               fprintf (stderr, "Invalid version '%s'\n", argv[1]);
199               return usage ();
200             }
201           version = optarg;
202           break;
203         case 'v': // support -v3 and -v
204           i = (optind > 0 && optind < argc) ? optind - 1 : 1;
205           if (!memcmp (argv[i], "-v", 2))
206             {
207               opts = argv[i][2] ? argv[i][2] - '0' : 1;
208             }
209           if (opts < 0 || opts > 9)
210             return usage ();
211 #if defined(USE_TRACING) && defined(HAVE_SETENV)
212           {
213             char v[2];
214             *v = opts + '0';
215             *(v + 1) = 0;
216             setenv ("LIBREDWG_TRACE", v, 1);
217           }
218 #endif
219           break;
220         case 'h':
221           return help ();
222         case '?':
223           fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
224                    optopt);
225           break;
226         default:
227           return usage ();
228         }
229     }
230   i = optind;
231 
232   if (filename_out && i + 1 < argc)
233     {
234       fprintf (stderr, "%s: no -o with multiple input files\n", argv[0]);
235       return usage ();
236     }
237   do_free |= (i + 1) < argc;
238 
239   while (i < argc)
240     {
241       filename_in = argv[i];
242       i++;
243       if (!filename_out)
244         {
245           need_free = 1;
246           filename_out = suffix (filename_in, "dxf");
247         }
248       if (strEQ (filename_in, filename_out))
249         {
250           if (need_free)
251             free (filename_out);
252           return usage ();
253         }
254 
255       memset (&dwg, 0, sizeof (Dwg_Data));
256       dwg.opts = opts;
257       printf ("Reading DWG file %s\n", filename_in);
258       error = dwg_read_file (filename_in, &dwg);
259       if (error >= DWG_ERR_CRITICAL)
260         {
261           fprintf (stderr, "READ ERROR 0x%x\n", error);
262           goto final;
263         }
264 
265       printf ("Writing DXF file %s", filename_out);
266       if (version)
267         {
268           printf (" as %s\n", version);
269           if (dwg.header.from_version != dwg.header.version)
270             dwg.header.from_version = dwg.header.version;
271           // else keep from_version = 0
272           dwg.header.version = dwg_version;
273         }
274       else
275         {
276           printf ("\n");
277         }
278       dat.version = dwg.header.version;
279       dat.from_version = dwg.header.from_version;
280 
281       if (minimal)
282         dwg.opts |= DWG_OPTS_MINIMAL;
283       {
284         struct stat attrib;
285         if (!stat (filename_out, &attrib)) // exists
286           {
287             if (!overwrite)
288               {
289                 LOG_ERROR ("File not overwritten: %s, use -y.\n",
290                            filename_out);
291                 error |= DWG_ERR_IOERROR;
292               }
293             else
294               {
295                 if (S_ISREG (attrib.st_mode) && // refuse to remove a directory
296                     (access (filename_out, W_OK) == 0) // writable
297 #ifndef _WIN32
298                     // refuse to remove a symlink. even with overwrite.
299                     // security
300                     && !S_ISLNK (attrib.st_mode)
301 #endif
302                 )
303                   {
304                     unlink (filename_out);
305                     dat.fh = fopen (filename_out, "wb");
306                   }
307                 else if (
308 #ifdef _WIN32
309       strEQc (filename_out, "NUL")
310 #else
311       strEQc (filename_out, "/dev/null")
312 #endif
313                          )
314                   {
315                     dat.fh = fopen (filename_out, "wb");
316                   }
317                 else
318                   {
319                     LOG_ERROR ("Not writable file or symlink: %s\n",
320                                filename_out);
321                     error |= DWG_ERR_IOERROR;
322                   }
323               }
324           }
325         else
326           dat.fh = fopen (filename_out, "wb");
327       }
328       if (!dat.fh)
329         {
330           fprintf (stderr, "WRITE ERROR %s\n", filename_out);
331           error = DWG_ERR_IOERROR;
332         }
333       else
334         {
335           error = binary ? dwg_write_dxfb (&dat, &dwg)
336                          : dwg_write_dxf (&dat, &dwg);
337         }
338 
339       if (error >= DWG_ERR_CRITICAL)
340         fprintf (stderr, "WRITE ERROR %s\n", filename_out);
341 
342       if (dat.fh)
343         fclose (dat.fh);
344 
345     final:
346 #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
347   {
348     char *asanenv = getenv("ASAN_OPTIONS");
349     if (!asanenv)
350       do_free = 1;
351     // detect_leaks is enabled by default. see if it's turned off
352     else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
353       do_free = 1;
354   }
355 #endif
356       // forget about leaks. really huge DWG's need endlessly here.
357       if (do_free
358 #ifdef HAVE_VALGRIND_VALGRIND_H
359           || (RUNNING_ON_VALGRIND)
360 #endif
361       )
362         {
363           dwg_free (&dwg);
364           if (need_free)
365             free (filename_out);
366         }
367       filename_out = NULL;
368     }
369 
370   // but only the result of the last conversion
371   return error >= DWG_ERR_CRITICAL ? 1 : 0;
372 }
373