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

Generated by: LCOV version 1.14