LCOV - code coverage report
Current view: top level - apps - gdalalg_external.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 108 118 91.5 %
Date: 2026-06-03 12:46:18 Functions: 3 4 75.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "external" subcommand (always in pipeline)
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : //! @cond Doxygen_Suppress
      14             : 
      15             : #include "gdalalg_external.h"
      16             : #include "gdalalg_materialize.h"
      17             : #include "gdal_dataset.h"
      18             : 
      19             : #include "cpl_atomic_ops.h"
      20             : #include "cpl_error.h"
      21             : #include "cpl_multiproc.h"
      22             : 
      23             : #include <stdio.h>
      24             : #if !defined(_WIN32)
      25             : #include <sys/wait.h>
      26             : #endif
      27             : 
      28             : /************************************************************************/
      29             : /*                     ~GDALExternalAlgorithmBase()                     */
      30             : /************************************************************************/
      31             : 
      32         106 : GDALExternalAlgorithmBase::~GDALExternalAlgorithmBase()
      33             : {
      34         106 :     if (!m_osTempInputFilename.empty())
      35             :     {
      36          13 :         VSIUnlink(m_osTempInputFilename.c_str());
      37             :     }
      38         108 :     if (!m_osTempOutputFilename.empty() &&
      39           2 :         m_osTempOutputFilename != m_osTempInputFilename)
      40             :     {
      41           2 :         VSIUnlink(m_osTempOutputFilename.c_str());
      42             :     }
      43         106 : }
      44             : 
      45             : /************************************************************************/
      46             : /*                   GDALExternalAlgorithmBase::Run()                   */
      47             : /************************************************************************/
      48             : 
      49          24 : bool GDALExternalAlgorithmBase::Run(
      50             :     const std::vector<std::string> &inputFormats,
      51             :     std::vector<GDALArgDatasetValue> &inputDataset,
      52             :     const std::string &outputFormat, GDALArgDatasetValue &outputDataset)
      53             : {
      54          24 :     if (!CPLTestBool(CPLGetConfigOption("GDAL_ENABLE_EXTERNAL", "NO")))
      55             :     {
      56           1 :         CPLError(CE_Failure, CPLE_AppDefined,
      57             :                  "Cannot execute command '%s', because GDAL_ENABLE_EXTERNAL "
      58             :                  "configuration option is not set to YES.",
      59             :                  m_command.c_str());
      60           1 :         return false;
      61             :     }
      62             : 
      63          23 :     const bool bHasInput = m_command.find("<INPUT>") != std::string::npos;
      64             :     const bool bHasInputOutput =
      65          23 :         m_command.find("<INPUT-OUTPUT>") != std::string::npos;
      66          23 :     const bool bHasOutput = m_command.find("<OUTPUT>") != std::string::npos;
      67             : 
      68             :     const std::string osTempDirname =
      69          46 :         CPLGetDirnameSafe(CPLGenerateTempFilenameSafe(nullptr).c_str());
      70          23 :     if (bHasInput || bHasInputOutput || bHasOutput)
      71             :     {
      72             :         VSIStatBufL sStat;
      73          39 :         if (VSIStatL(osTempDirname.c_str(), &sStat) != 0 ||
      74          19 :             !VSI_ISDIR(sStat.st_mode))
      75             :         {
      76           1 :             CPLError(
      77             :                 CE_Failure, CPLE_AppDefined,
      78             :                 "'%s', used for temporary directory, is not a valid directory",
      79             :                 osTempDirname.c_str());
      80           2 :             return false;
      81             :         }
      82             :         // Check to avoid any possibility of command injection
      83          19 :         if (osTempDirname.find_first_of("'\"^&|<>%!;") != std::string::npos)
      84             :         {
      85           1 :             CPLError(CE_Failure, CPLE_AppDefined,
      86             :                      "'%s', used for temporary directory, contains a reserved "
      87             :                      "character ('\"^&|<>%%!;)",
      88             :                      osTempDirname.c_str());
      89           1 :             return false;
      90             :         }
      91             :     }
      92             : 
      93          30 :     const auto QuoteFilename = [](const std::string &s)
      94             :     {
      95             : #ifdef _WIN32
      96             :         return "\"" + s + "\"";
      97             : #else
      98          60 :         return "'" + s + "'";
      99             : #endif
     100             :     };
     101             : 
     102             :     // Process <INPUT> and <INPUT-OUTPUT> placeholder
     103          21 :     if (bHasInput || bHasInputOutput)
     104             :     {
     105          13 :         if (inputDataset.size() != 1 || !inputDataset[0].GetDatasetRef())
     106             :         {
     107           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     108             :                      "Command '%s' expects an input dataset to be provided, "
     109             :                      "but none is available.",
     110             :                      m_command.c_str());
     111           0 :             return false;
     112             :         }
     113          13 :         auto poSrcDS = inputDataset[0].GetDatasetRef();
     114             : 
     115          14 :         const std::string osDriverName = !inputFormats.empty() ? inputFormats[0]
     116          24 :                                          : poSrcDS->GetRasterCount() != 0
     117             :                                              ? "GTiff"
     118          14 :                                              : "GPKG";
     119             : 
     120             :         auto poDriver =
     121          13 :             GetGDALDriverManager()->GetDriverByName(osDriverName.c_str());
     122          13 :         if (!poDriver)
     123             :         {
     124           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Driver %s not available",
     125             :                      osDriverName.c_str());
     126           0 :             return false;
     127             :         }
     128             : 
     129             :         const CPLStringList aosExts(CSLTokenizeString2(
     130          13 :             poDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS), " ", 0));
     131          13 :         const char *pszExt = aosExts.size() >= 1 ? aosExts[0] : "bin";
     132             :         static int nTempFileCounter = 0;
     133          26 :         m_osTempInputFilename = CPLFormFilenameSafe(
     134             :             osTempDirname.c_str(),
     135             :             CPLSPrintf("input_%d_%d", CPLGetCurrentProcessID(),
     136             :                        CPLAtomicInc(&nTempFileCounter)),
     137          13 :             pszExt);
     138             : 
     139          13 :         if (bHasInputOutput)
     140             :         {
     141             :             m_command.replaceAll("<INPUT-OUTPUT>",
     142           1 :                                  QuoteFilename(m_osTempInputFilename));
     143           1 :             m_osTempOutputFilename = m_osTempInputFilename;
     144             :         }
     145             :         else
     146             :         {
     147             :             m_command.replaceAll("<INPUT>",
     148          12 :                                  QuoteFilename(m_osTempInputFilename));
     149             :         }
     150             : 
     151           0 :         std::unique_ptr<GDALPipelineStepAlgorithm> poMaterializeStep;
     152          13 :         if (poSrcDS->GetRasterCount() != 0)
     153             :         {
     154             :             poMaterializeStep =
     155           8 :                 std::make_unique<GDALMaterializeRasterAlgorithm>();
     156             :         }
     157             :         else
     158             :         {
     159             :             poMaterializeStep =
     160           5 :                 std::make_unique<GDALMaterializeVectorAlgorithm>();
     161             :         }
     162          13 :         poMaterializeStep->SetInputDataset(poSrcDS);
     163          13 :         poMaterializeStep->GetArg(GDAL_ARG_NAME_OUTPUT_FORMAT)
     164          13 :             ->Set(osDriverName.c_str());
     165          13 :         poMaterializeStep->GetArg(GDAL_ARG_NAME_OUTPUT)
     166          13 :             ->Set(m_osTempInputFilename);
     167          13 :         if (EQUAL(osDriverName.c_str(), "GTIFF"))
     168           7 :             poMaterializeStep->GetArg(GDAL_ARG_NAME_CREATION_OPTION)
     169           7 :                 ->Set("COPY_SRC_OVERVIEWS=NO");
     170          13 :         if (!poMaterializeStep->Run() || !poMaterializeStep->Finalize())
     171             :         {
     172           0 :             return false;
     173             :         }
     174             :     }
     175             : 
     176             :     // Process <OUTPUT> placeholder
     177          21 :     if (bHasOutput)
     178             :     {
     179          17 :         auto poSrcDS = inputDataset.size() == 1
     180          17 :                            ? inputDataset[0].GetDatasetRef()
     181          17 :                            : nullptr;
     182             : 
     183             :         const std::string osDriverName =
     184          17 :             !outputFormat.empty()                       ? outputFormat
     185          16 :             : !inputFormats.empty()                     ? inputFormats[0]
     186          12 :             : poSrcDS && poSrcDS->GetRasterCount() != 0 ? "GTiff"
     187          45 :                                                         : "GPKG";
     188             : 
     189             :         auto poDriver =
     190          17 :             GetGDALDriverManager()->GetDriverByName(osDriverName.c_str());
     191          17 :         if (!poDriver)
     192             :         {
     193           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Driver %s not available",
     194             :                      osDriverName.c_str());
     195           0 :             return false;
     196             :         }
     197             : 
     198             :         const CPLStringList aosExts(CSLTokenizeString2(
     199          17 :             poDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS), " ", 0));
     200          17 :         const char *pszExt = aosExts.size() >= 1 ? aosExts[0] : "bin";
     201          34 :         m_osTempOutputFilename = CPLResetExtensionSafe(
     202          51 :             CPLGenerateTempFilenameSafe("output").c_str(), pszExt);
     203             : 
     204          17 :         m_command.replaceAll("<OUTPUT>", QuoteFilename(m_osTempOutputFilename));
     205             :     }
     206             : 
     207          21 :     CPLDebug("GDAL", "Execute '%s'", m_command.c_str());
     208             : 
     209             : #ifdef _WIN32
     210             :     wchar_t *pwszCmmand =
     211             :         CPLRecodeToWChar(m_command.c_str(), CPL_ENC_UTF8, CPL_ENC_UCS2);
     212             :     FILE *fout = _wpopen(pwszCmmand, L"r");
     213             :     CPLFree(pwszCmmand);
     214             : #else
     215          21 :     FILE *fout = popen(m_command.c_str(), "r");
     216             : #endif
     217          21 :     if (!fout)
     218             :     {
     219           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     220             :                  "Cannot start external command '%s'", m_command.c_str());
     221           0 :         return false;
     222             :     }
     223             : 
     224             :     // Read child standard output
     225          42 :     std::string s;
     226          21 :     constexpr int BUFFER_SIZE = 1024;
     227          21 :     s.resize(BUFFER_SIZE);
     228          60 :     while (!feof(fout) && !ferror(fout))
     229             :     {
     230          39 :         if (fgets(s.data(), BUFFER_SIZE - 1, fout))
     231             :         {
     232          18 :             size_t nLineLen = strlen(s.c_str());
     233             :             // Remove end-of-line characters
     234          54 :             for (char chEOL : {'\n', '\r'})
     235             :             {
     236          36 :                 if (nLineLen > 0 && s[nLineLen - 1] == chEOL)
     237             :                 {
     238          18 :                     --nLineLen;
     239          18 :                     s[nLineLen] = 0;
     240             :                 }
     241             :             }
     242          18 :             if (nLineLen)
     243             :             {
     244          18 :                 bool bOnlyPrintableChars = true;
     245        1021 :                 for (size_t i = 0; bOnlyPrintableChars && i < nLineLen; ++i)
     246             :                 {
     247        1003 :                     const char ch = s[i];
     248        1003 :                     bOnlyPrintableChars = ch >= 32 || ch == '\t';
     249             :                 }
     250          18 :                 if (bOnlyPrintableChars)
     251          18 :                     CPLDebug("GDAL", "External process: %s", s.c_str());
     252             :             }
     253             :         }
     254             :     }
     255             : 
     256             : #ifdef _WIN32
     257             :     const int ret = _pclose(fout);
     258             : #else
     259          21 :     int ret = pclose(fout);
     260          21 :     if (WIFEXITED(ret))
     261          21 :         ret = WEXITSTATUS(ret);
     262             : #endif
     263             : 
     264          29 :     if (m_osTempInputFilename.empty() &&
     265           8 :         m_osTempInputFilename != m_osTempOutputFilename)
     266             :     {
     267           5 :         VSIUnlink(m_osTempInputFilename.c_str());
     268           5 :         m_osTempInputFilename.clear();
     269             :     }
     270             : 
     271          21 :     if (ret)
     272             :     {
     273           2 :         CPLError(CE_Failure, CPLE_AppDefined,
     274             :                  "External command '%s' failed with error code %d",
     275             :                  m_command.c_str(), ret);
     276           2 :         return false;
     277             :     }
     278             : 
     279          19 :     if (!m_osTempOutputFilename.empty())
     280             :     {
     281             :         auto poOutDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     282          18 :             m_osTempOutputFilename.c_str(), GDAL_OF_VERBOSE_ERROR));
     283          18 :         if (!poOutDS)
     284           2 :             return false;
     285          16 :         poOutDS->MarkSuppressOnClose();
     286          16 :         m_osTempOutputFilename.clear();
     287             : 
     288          16 :         outputDataset.Set(std::move(poOutDS));
     289             :     }
     290           1 :     else if (inputDataset.size() == 1)
     291             :     {
     292             :         // If no output dataset was expected from the external command,
     293             :         // reuse the input dataset as the output of this step.
     294           1 :         outputDataset.Set(inputDataset[0].GetDatasetRef());
     295             :     }
     296             : 
     297          17 :     return true;
     298             : }
     299             : 
     300             : GDALExternalRasterOrVectorAlgorithm::~GDALExternalRasterOrVectorAlgorithm() =
     301             :     default;
     302             : 
     303             : GDALExternalRasterAlgorithm::~GDALExternalRasterAlgorithm() = default;
     304             : 
     305             : GDALExternalVectorAlgorithm::~GDALExternalVectorAlgorithm() = default;
     306             : 
     307             : //! @endcond

Generated by: LCOV version 1.14