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
|