(root)/
libredwg-0.13/
programs/
dxfwrite.c
       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  }