Coverage Report

Created: 2026-07-25 06:50

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