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
|