Coverage Report

Created: 2026-04-01 06:20

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
        VSIUnlink(m_osTempInputFilename.c_str());
36
0
    if (!m_osTempOutputFilename.empty() &&
37
0
        m_osTempOutputFilename != m_osTempInputFilename)
38
0
        VSIUnlink(m_osTempOutputFilename.c_str());
39
0
}
40
41
/************************************************************************/
42
/*                   GDALExternalAlgorithmBase::Run()                   */
43
/************************************************************************/
44
45
bool GDALExternalAlgorithmBase::Run(
46
    const std::vector<std::string> &inputFormats,
47
    std::vector<GDALArgDatasetValue> &inputDataset,
48
    const std::string &outputFormat, GDALArgDatasetValue &outputDataset)
49
0
{
50
0
    if (!CPLTestBool(CPLGetConfigOption("GDAL_ENABLE_EXTERNAL", "NO")))
51
0
    {
52
0
        CPLError(CE_Failure, CPLE_AppDefined,
53
0
                 "Cannot execute command '%s', because GDAL_ENABLE_EXTERNAL "
54
0
                 "configuration option is not set to YES.",
55
0
                 m_command.c_str());
56
0
        return false;
57
0
    }
58
59
0
    const bool bHasInput = m_command.find("<INPUT>") != std::string::npos;
60
0
    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
0
    const std::string osTempDirname =
65
0
        CPLGetDirnameSafe(CPLGenerateTempFilenameSafe(nullptr).c_str());
66
0
    if (bHasInput || bHasInputOutput || bHasOutput)
67
0
    {
68
0
        VSIStatBufL sStat;
69
0
        if (VSIStatL(osTempDirname.c_str(), &sStat) != 0 ||
70
0
            !VSI_ISDIR(sStat.st_mode))
71
0
        {
72
0
            CPLError(
73
0
                CE_Failure, CPLE_AppDefined,
74
0
                "'%s', used for temporary directory, is not a valid directory",
75
0
                osTempDirname.c_str());
76
0
            return false;
77
0
        }
78
        // Check to avoid any possibility of command injection
79
0
        if (osTempDirname.find_first_of("'\"^&|<>%!;") != std::string::npos)
80
0
        {
81
0
            CPLError(CE_Failure, CPLE_AppDefined,
82
0
                     "'%s', used for temporary directory, contains a reserved "
83
0
                     "character ('\"^&|<>%%!;)",
84
0
                     osTempDirname.c_str());
85
0
            return false;
86
0
        }
87
0
    }
88
89
0
    const auto QuoteFilename = [](const std::string &s)
90
0
    {
91
#ifdef _WIN32
92
        return "\"" + s + "\"";
93
#else
94
0
        return "'" + s + "'";
95
0
#endif
96
0
    };
97
98
    // Process <INPUT> and <INPUT-OUTPUT> placeholder
99
0
    if (bHasInput || bHasInputOutput)
100
0
    {
101
0
        if (inputDataset.size() != 1 || !inputDataset[0].GetDatasetRef())
102
0
        {
103
0
            CPLError(CE_Failure, CPLE_AppDefined,
104
0
                     "Command '%s' expects an input dataset to be provided, "
105
0
                     "but none is available.",
106
0
                     m_command.c_str());
107
0
            return false;
108
0
        }
109
0
        auto poSrcDS = inputDataset[0].GetDatasetRef();
110
111
0
        const std::string osDriverName = !inputFormats.empty() ? inputFormats[0]
112
0
                                         : poSrcDS->GetRasterCount() != 0
113
0
                                             ? "GTiff"
114
0
                                             : "GPKG";
115
116
0
        auto poDriver =
117
0
            GetGDALDriverManager()->GetDriverByName(osDriverName.c_str());
118
0
        if (!poDriver)
119
0
        {
120
0
            CPLError(CE_Failure, CPLE_AppDefined, "Driver %s not available",
121
0
                     osDriverName.c_str());
122
0
            return false;
123
0
        }
124
125
0
        const CPLStringList aosExts(CSLTokenizeString2(
126
0
            poDriver->GetMetadataItem(GDAL_DMD_EXTENSIONS), " ", 0));
127
0
        const char *pszExt = aosExts.size() >= 1 ? aosExts[0] : "bin";
128
0
        static int nTempFileCounter = 0;
129
0
        m_osTempInputFilename = CPLFormFilenameSafe(
130
0
            osTempDirname.c_str(),
131
0
            CPLSPrintf("input_%d_%d", CPLGetCurrentProcessID(),
132
0
                       CPLAtomicInc(&nTempFileCounter)),
133
0
            pszExt);
134
135
0
        if (bHasInputOutput)
136
0
        {
137
0
            m_command.replaceAll("<INPUT-OUTPUT>",
138
0
                                 QuoteFilename(m_osTempInputFilename));
139
0
            m_osTempOutputFilename = m_osTempInputFilename;
140
0
        }
141
0
        else
142
0
        {
143
0
            m_command.replaceAll("<INPUT>",
144
0
                                 QuoteFilename(m_osTempInputFilename));
145
0
        }
146
147
0
        std::unique_ptr<GDALPipelineStepAlgorithm> poMaterializeStep;
148
0
        if (poSrcDS->GetRasterCount() != 0)
149
0
        {
150
0
            poMaterializeStep =
151
0
                std::make_unique<GDALMaterializeRasterAlgorithm>();
152
0
        }
153
0
        else
154
0
        {
155
0
            poMaterializeStep =
156
0
                std::make_unique<GDALMaterializeVectorAlgorithm>();
157
0
        }
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
0
        {
168
0
            return false;
169
0
        }
170
0
    }
171
172
    // Process <OUTPUT> placeholder
173
0
    if (bHasOutput)
174
0
    {
175
0
        auto poSrcDS = inputDataset.size() == 1
176
0
                           ? inputDataset[0].GetDatasetRef()
177
0
                           : nullptr;
178
179
0
        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
0
        auto poDriver =
186
0
            GetGDALDriverManager()->GetDriverByName(osDriverName.c_str());
187
0
        if (!poDriver)
188
0
        {
189
0
            CPLError(CE_Failure, CPLE_AppDefined, "Driver %s not available",
190
0
                     osDriverName.c_str());
191
0
            return false;
192
0
        }
193
194
0
        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
0
    }
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
0
#endif
213
0
    if (!fout)
214
0
    {
215
0
        CPLError(CE_Failure, CPLE_AppDefined,
216
0
                 "Cannot start external command '%s'", m_command.c_str());
217
0
        return false;
218
0
    }
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
0
    {
226
0
        if (fgets(s.data(), BUFFER_SIZE - 1, fout))
227
0
        {
228
0
            size_t nLineLen = strlen(s.c_str());
229
            // Remove end-of-line characters
230
0
            for (char chEOL : {'\n', '\r'})
231
0
            {
232
0
                if (nLineLen > 0 && s[nLineLen - 1] == chEOL)
233
0
                {
234
0
                    --nLineLen;
235
0
                    s[nLineLen] = 0;
236
0
                }
237
0
            }
238
0
            if (nLineLen)
239
0
            {
240
0
                bool bOnlyPrintableChars = true;
241
0
                for (size_t i = 0; bOnlyPrintableChars && i < nLineLen; ++i)
242
0
                {
243
0
                    const char ch = s[i];
244
0
                    bOnlyPrintableChars = ch >= 32 || ch == '\t';
245
0
                }
246
0
                if (bOnlyPrintableChars)
247
0
                    CPLDebug("GDAL", "External process: %s", s.c_str());
248
0
            }
249
0
        }
250
0
    }
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
0
#endif
259
0
    if (ret)
260
0
    {
261
0
        CPLError(CE_Failure, CPLE_AppDefined,
262
0
                 "External command '%s' failed with error code %d",
263
0
                 m_command.c_str(), ret);
264
0
        return false;
265
0
    }
266
267
0
    if (!m_osTempOutputFilename.empty())
268
0
    {
269
0
        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
0
    }
276
0
    else if (inputDataset.size() == 1)
277
0
    {
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
0
    }
282
283
0
    return true;
284
0
}
285
286
0
GDALExternalRasterOrVectorAlgorithm::~GDALExternalRasterOrVectorAlgorithm() =
287
    default;
288
289
0
GDALExternalRasterAlgorithm::~GDALExternalRasterAlgorithm() = default;
290
291
0
GDALExternalVectorAlgorithm::~GDALExternalVectorAlgorithm() = default;
292
293
//! @endcond