(root)/
libredwg-0.13/
programs/
dwgwrite.c
       1  /*****************************************************************************/
       2  /*  LibreDWG - free implementation of the DWG file format                    */
       3  /*                                                                           */
       4  /*  Copyright (C) 2018-2023 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  /* dwgwrite.c: write a DWG 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 "my_getopt.h"
      39  #include "decode.h"
      40  #include "encode.h"
      41  #ifndef DISABLE_JSON
      42  #  include "in_json.h"
      43  #endif
      44  #include "in_dxf.h"
      45  
      46  static int opts = 1;
      47  int overwrite = 0;
      48  
      49  static int help (void);
      50  
      51  static int
      52  usage (void)
      53  {
      54    printf (
      55        "\nUsage: dwgwrite [-v[0-9]] [-y] [--as rNNNN] [-I FMT] [-o DWGFILE] "
      56        "INFILE\n");
      57    return 1;
      58  }
      59  static int
      60  opt_version (void)
      61  {
      62    printf ("dwgwrite %s\n", PACKAGE_VERSION);
      63    return 0;
      64  }
      65  static int
      66  help (void)
      67  {
      68    printf ("\nUsage: dwgwrite [OPTION]... [-o DWGFILE] INFILE\n");
      69    printf ("Writes a DWG file from various input formats. Only r2000 for now.\n"
      70            "\n");
      71  #ifdef HAVE_GETOPT_LONG
      72    printf ("  -v[0-9], --verbose [0-9]  verbosity\n");
      73    printf ("  --as rNNNN                save as version\n");
      74    printf ("           Valid versions:\n");
      75    printf (
      76        "             r1.1, r1.2, r1.3, r1.4, r2.0, r2.10, r2.21, r2.22, r2.4,"
      77        "             r2.5, r2.6, r9, r10, r11, r13, r14, r2000 (default)\n");
      78    printf ("           Planned versions:\n");
      79    printf ("             r2004-r2021\n");
      80  #  ifndef DISABLE_JSON
      81    printf ("  -I fmt,  --format fmt     DXF, DXFB, JSON\n");
      82  #  else
      83    printf ("  -I fmt,  --format fmt     DXF, DXFB\n");
      84  #  endif
      85    printf ("           Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
      86    printf ("  -o dwgfile, --file        \n");
      87    printf ("  -y, --overwrite           overwrite existing files\n");
      88    printf ("           --help           display this help and exit\n");
      89    printf ("           --version        output version information and exit\n"
      90            "\n");
      91  #else
      92    printf ("  -v[0-9]     verbosity\n");
      93    printf ("  -a rNNNN    save as version\n");
      94    printf ("              Valid versions:\n");
      95    printf (
      96        "                r1.1, r1.2, r1.3, r1.4, r2.0, r2.10, r2.21, r2.22, "
      97        "r2.4,"
      98        "                r2.5, r2.6, r9, r10, r11, r13, r14, r2000 (default)\n");
      99    printf ("              Planned versions:\n");
     100    printf ("                r2004-r2021\n");
     101  #  ifndef DISABLE_JSON
     102    printf ("  -I fmt      fmt: DXF, DXFB, JSON\n");
     103  #  else
     104    printf ("  -I fmt      fmt: DXF, DXFB\n");
     105  #  endif
     106    printf (
     107        "              Planned input formats: GeoJSON, YAML, XML/OGR, GPX\n");
     108    printf ("  -o dwgfile\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  #ifdef __AFL_COMPILER
     120  __AFL_FUZZ_INIT ();
     121  // fastest mode via shared mem (crashes still)
     122  #  define AFL_SHARED_MEM
     123  
     124  int
     125  main (int argc, char *argv[])
     126  {
     127    Dwg_Data dwg;
     128    Bit_Chain dat = { NULL, 0, 0, 0, 0 };
     129    Bit_Chain out_dat = { NULL, 0, 0, 0, 0 };
     130    FILE *fp;
     131  
     132    __AFL_INIT ();
     133    dat.chain = NULL;
     134    dat.version = R_2000;
     135    printf ("Fuzzing in_json + encode from shared memory\n");
     136  
     137  #  ifdef AFL_SHARED_MEM
     138    dat.chain = __AFL_FUZZ_TESTCASE_BUF;
     139  #  endif
     140    while (__AFL_LOOP (10000))
     141      { // llvm_mode persistent, non-forking mode
     142  #  ifdef AFL_SHARED_MEM
     143        dat.size = __AFL_FUZZ_TESTCASE_LEN;
     144  #  elif 1 // still 1000x faster than the old file-forking fuzzer.
     145        /* from stdin: */
     146        dat.size = 0;
     147        dat_read_stream (&dat, stdin);
     148  #  else
     149        /* else from file */
     150        stat (argv[1], &attrib);
     151        fp = fopen (argv[1], "rb");
     152        if (!fp)
     153          return 0;
     154        dat.size = attrib.st_size;
     155        dat_read_file (&dat, fp, argv[1]);
     156        fclose (fp);
     157  #  endif
     158        if (dat.size < 100)
     159          continue; // useful minimum input length
     160  
     161        if (dwg_read_json (&dat, &dwg) <= DWG_ERR_CRITICAL)
     162          {
     163            memset (&out_dat, 0, sizeof (out_dat));
     164            bit_chain_set_version (&out_dat, &dat);
     165            out_dat.version = R_2000;
     166            out_dat.codepage = dwg.header.codepage;
     167            if (dwg_encode (&dwg, &out_dat) >= DWG_ERR_CRITICAL)
     168              exit (0);
     169            free (out_dat.chain);
     170          }
     171        else
     172          exit (0);
     173      }
     174    dwg_free (&dwg);
     175  }
     176  #  define main orig_main
     177  int orig_main (int argc, char *argv[]);
     178  #endif
     179  
     180  int
     181  main (int argc, char *argv[])
     182  {
     183    int i = 1;
     184    int error = 0;
     185    Dwg_Data dwg;
     186    const char *fmt = NULL;
     187    const char *infile = NULL;
     188    char *outfile = NULL;
     189    Bit_Chain dat = { 0 };
     190    const char *version = NULL;
     191    Dwg_Version_Type dwg_version = R_INVALID;
     192    int c;
     193    int force_free = 0;
     194    int free_outfile = 0;
     195  #ifdef HAVE_GETOPT_LONG
     196    int option_index = 0;
     197    static struct option long_options[]
     198        = { { "verbose", 1, &opts, 1 }, // optional
     199            { "format", 1, NULL, 'I' },    { "file", 1, NULL, 'o' },
     200            { "as", 1, NULL, 'a' },        { "help", 0, NULL, 'h' },
     201            { "overwrite", 0, NULL, 'y' }, { "version", 0, NULL, 0 },
     202            { "force-free", 0, NULL, 0 },  { NULL, 0, NULL, 0 } };
     203  #endif
     204  
     205    if (argc < 2)
     206      return usage ();
     207  
     208    while (1)
     209      {
     210  #ifdef HAVE_GETOPT_LONG
     211        c = getopt_long (argc, argv, "ya:v::I:o:h", long_options, &option_index);
     212  #else
     213        c = getopt (argc, argv, "ya:v::I:o:hi");
     214  #endif
     215        if (c == -1)
     216          break;
     217        switch (c)
     218          {
     219          case ':': // missing arg
     220            if (optarg && !strcmp (optarg, "v"))
     221              {
     222                opts = 1;
     223                break;
     224              }
     225            fprintf (stderr, "%s: option '-%c' requires an argument\n", argv[0],
     226                     optopt);
     227            break;
     228  #ifdef HAVE_GETOPT_LONG
     229          case 0:
     230            /* This option sets a flag */
     231            if (!strcmp (long_options[option_index].name, "verbose"))
     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              }
     245            if (!strcmp (long_options[option_index].name, "version"))
     246              return opt_version ();
     247            if (!strcmp (long_options[option_index].name, "force-free"))
     248              force_free = 1;
     249            break;
     250  #else
     251          case 'i':
     252            return opt_version ();
     253  #endif
     254          case 'I':
     255            fmt = optarg;
     256            break;
     257          case 'y':
     258            overwrite = 1;
     259            break;
     260          case 'o':
     261            outfile = optarg;
     262            break;
     263          case 'a':
     264            dwg_version = dwg_version_as (optarg);
     265            if (dwg_version == R_INVALID)
     266              {
     267                fprintf (stderr, "Invalid version '%s'\n", argv[1]);
     268                return usage ();
     269              }
     270            version = optarg;
     271            break;
     272          case 'v': // support -v3 and -v
     273            i = (optind > 0 && optind < argc) ? optind - 1 : 1;
     274            if (!memcmp (argv[i], "-v", 2))
     275              {
     276                opts = argv[i][2] ? argv[i][2] - '0' : 1;
     277              }
     278            if (opts < 0 || opts > 9)
     279              return usage ();
     280  #if defined(USE_TRACING) && defined(HAVE_SETENV)
     281            {
     282              char v[2];
     283              *v = opts + '0';
     284              *(v + 1) = 0;
     285              setenv ("LIBREDWG_TRACE", v, 1);
     286            }
     287  #endif
     288            break;
     289          case 'h':
     290            return help ();
     291          case '?':
     292            fprintf (stderr, "%s: invalid option '-%c' ignored\n", argv[0],
     293                     optopt);
     294            break;
     295          default:
     296            return usage ();
     297          }
     298      }
     299    i = optind;
     300  
     301    // get input format from INFILE, not outfile.
     302    // With stdin, should -I be mandatory, or try to autodetect the format?
     303    // With a file use the extension.
     304    if (optind < argc) // have arg
     305      {
     306        infile = argv[i];
     307        if (!fmt)
     308          {
     309  #ifndef DISABLE_DXF
     310  #  ifndef DISABLE_JSON
     311            if (strstr (infile, ".json") || strstr (infile, ".JSON"))
     312              fmt = (char *)"json";
     313            else
     314  #  endif
     315                if (strstr (infile, ".dxfb") || strstr (infile, ".DXFB"))
     316              fmt = (char *)"dxfb";
     317            else if (strstr (infile, ".dxf") || strstr (infile, ".DXF"))
     318              fmt = (char *)"dxf";
     319            else
     320  #endif
     321              fprintf (stderr, "Unknown input format for '%s'\n", infile);
     322          }
     323      }
     324  
     325    // allow stdin, but require -I|--format then
     326    memset (&dwg, 0, sizeof (Dwg_Data));
     327    dat.opts = dwg.opts = opts;
     328    dat.version = R_2000; // initial target for the importer
     329  
     330    if (infile)
     331      {
     332        struct stat attrib;
     333        if (stat (infile, &attrib)) // not exists
     334          {
     335            fprintf (stderr, "Missing input file '%s'\n", infile);
     336            exit (1);
     337          }
     338        dat.fh = fopen (infile, "rb");
     339        if (!dat.fh)
     340          {
     341            fprintf (stderr, "Could not read file '%s'\n", infile);
     342            exit (1);
     343          }
     344        dat.size = attrib.st_size;
     345      }
     346    else
     347      {
     348        dat.fh = stdin;
     349      }
     350  
     351  #ifndef DISABLE_DXF
     352  #  ifndef DISABLE_JSON
     353    if ((fmt && !strcasecmp (fmt, "json"))
     354        || (infile && !strcasecmp (infile, ".json")))
     355      {
     356        if (opts > 1)
     357          fprintf (stderr, "Reading JSON file %s\n",
     358                   infile ? infile : "from stdin");
     359        if (infile)
     360          dat_read_file (&dat, dat.fh, infile);
     361        error = dwg_read_json (&dat, &dwg);
     362      }
     363    else
     364  #  endif
     365        if ((fmt && !strcasecmp (fmt, "dxfb"))
     366            || (infile && !strcasecmp (infile, ".dxfb")))
     367      {
     368        if (opts > 1)
     369          {
     370            fprintf (stderr, "Reading Binary DXF file %s\n",
     371                     infile ? infile : "from stdin");
     372            fprintf (stderr,
     373                     "Warning: still highly experimental and untested.\n");
     374          }
     375        if (infile)
     376          error = dxf_read_file (infile, &dwg); // ascii or binary
     377        else
     378          error = dwg_read_dxfb (&dat, &dwg);
     379      }
     380    else if ((fmt && !strcasecmp (fmt, "dxf"))
     381             || (infile && !strcasecmp (infile, ".dxf")))
     382      {
     383        if (opts > 1)
     384          {
     385            fprintf (stderr, "Reading DXF file %s\n",
     386                     infile ? infile : "from stdin");
     387          }
     388        if (infile)
     389          error = dxf_read_file (infile, &dwg); // ascii or binary
     390        else
     391          error = dwg_read_dxf (&dat, &dwg);
     392      }
     393    else
     394  #endif
     395      {
     396        if (fmt)
     397          fprintf (stderr, "Invalid or unsupported input format '%s'\n", fmt);
     398        else if (infile)
     399          fprintf (stderr, "Missing input format for '%s'\n", infile);
     400        else
     401          fprintf (stderr, "Missing input format\n");
     402        if (infile)
     403          fclose (dat.fh);
     404        free (dat.chain);
     405        exit (1);
     406      }
     407  
     408    free (dat.chain);
     409    if (infile && dat.fh)
     410      fclose (dat.fh);
     411    if (error >= DWG_ERR_CRITICAL)
     412      goto free;
     413  
     414    if (dwg_version == R_INVALID)
     415      {
     416        dwg_version = dwg.header.from_version;
     417        if (dwg_version >= R_2004)
     418          dwg_version = R_2000;
     419        else if (dwg_version < R_1_4)
     420          dwg_version = R_1_4;
     421      }
     422    if (dwg.header.from_version == R_INVALID)
     423      fprintf (stderr, "Unknown DWG header.from_version");
     424    // FIXME: for now only r1.4 - R_2000. later remove this line.
     425    dat.version = dwg.header.version = dwg_version;
     426  
     427    if (!outfile)
     428      {
     429        outfile = suffix (infile, "dwg");
     430        free_outfile = 1;
     431      }
     432  
     433    if (opts > 1)
     434      fprintf (stderr, "Writing DWG file %s\n", outfile);
     435  
     436    {
     437      struct stat attrib;
     438      if (!stat (outfile, &attrib)) // exists
     439        {
     440          if (!overwrite)
     441            {
     442              fprintf (stderr, "File not overwritten: %s, use -y.\n", outfile);
     443              error |= DWG_ERR_IOERROR;
     444            }
     445          else
     446            {
     447              if (S_ISREG (attrib.st_mode) &&   // refuse to remove a directory
     448                  (access (outfile, W_OK) == 0) // writable
     449  #ifndef _WIN32
     450                  // refuse to remove a symlink. even with overwrite. security
     451                  && !S_ISLNK (attrib.st_mode)
     452  #endif
     453              )
     454                {
     455                  unlink (outfile);
     456                  dwg.opts |= opts;
     457                  error = dwg_write_file (outfile, &dwg);
     458                }
     459              else if ( // for fuzzing mainly
     460  #ifdef _WIN32
     461                  strEQc (outfile, "NUL")
     462  #else
     463                  strEQc (outfile, "/dev/null")
     464  #endif
     465              )
     466                {
     467                  dwg.opts |= opts;
     468                  error = dwg_write_file (outfile, &dwg);
     469                }
     470              else
     471                {
     472                  fprintf (stderr, "Not writable file or symlink: %s\n",
     473                           outfile);
     474                  error |= DWG_ERR_IOERROR;
     475                }
     476            }
     477        }
     478      else
     479        {
     480          dwg.opts |= opts;
     481          error = dwg_write_file (outfile, &dwg);
     482        }
     483    }
     484  
     485  free:
     486  #if defined __SANITIZE_ADDRESS__ || __has_feature(address_sanitizer)
     487    {
     488      char *asanenv = getenv ("ASAN_OPTIONS");
     489      if (!asanenv)
     490        force_free = 1;
     491      // detect_leaks is enabled by default. see if it's turned off
     492      else if (strstr (asanenv, "detect_leaks=0") == NULL) /* not found */
     493        force_free = 1;
     494    }
     495  #endif
     496    // forget about leaks. really huge DWG's need endlessly here.
     497    if ((dwg.header.version && dwg.num_objects < 1000) || force_free
     498  #ifdef HAVE_VALGRIND_VALGRIND_H
     499        || (RUNNING_ON_VALGRIND)
     500  #endif
     501    )
     502      {
     503        dwg_free (&dwg);
     504      }
     505  
     506    if (error >= DWG_ERR_CRITICAL)
     507      {
     508        fprintf (stderr, "ERROR 0x%x\n", error);
     509        if (error && opts > 2)
     510          dwg_errstrings (error);
     511      }
     512    else
     513      {
     514        if (opts > 1)
     515          {
     516            fprintf (stderr, "SUCCESS 0x%x\n", error);
     517            if (error && opts > 2)
     518              dwg_errstrings (error);
     519          }
     520        else
     521          fprintf (stderr, "SUCCESS\n");
     522      }
     523  
     524    if (free_outfile)
     525      free (outfile);
     526    return error >= DWG_ERR_CRITICAL ? 1 : 0;
     527  }