Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: Return the path of the "gdal" binary
5 : * Author: Even Rouault <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2025, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_spawn.h"
14 : #include "cpl_vsi_virtual.h"
15 : #include "gdal.h"
16 : #include "gdalgetgdalpath.h"
17 :
18 : #include <cassert>
19 :
20 : /************************************************************************/
21 : /* GDALGetGDALPath() */
22 : /************************************************************************/
23 :
24 : /** Return the path of the "gdal" binary, or an empty string if it cannot be
25 : * found.
26 : *
27 : * The GDAL_PATH configuration option may be set to point to the directory where
28 : * the GDAL binary is located.
29 : */
30 3 : std::string GDALGetGDALPath()
31 : {
32 3 : const char *pszGDAL_PATH = CPLGetConfigOption("GDAL_PATH", nullptr);
33 3 : if (pszGDAL_PATH)
34 : {
35 : VSIStatBufL sStat;
36 1 : for (const char *pszProgramName : {"gdal"
37 : #ifdef _WIN32
38 : ,
39 : "gdal.exe"
40 : #endif
41 2 : })
42 : {
43 : std::string osPath =
44 1 : CPLFormFilenameSafe(pszGDAL_PATH, pszProgramName, nullptr);
45 1 : if (VSIStatL(osPath.c_str(), &sStat) == 0)
46 0 : return osPath;
47 : }
48 1 : CPLError(CE_Failure, CPLE_AppDefined,
49 : "No 'gdal' binary can be found in '%s'", pszGDAL_PATH);
50 1 : return std::string();
51 : }
52 :
53 2 : constexpr int MAXPATH_SIZE = 4096;
54 4 : std::string osPath;
55 2 : osPath.resize(MAXPATH_SIZE);
56 2 : if (CPLGetExecPath(osPath.data(), MAXPATH_SIZE))
57 : {
58 2 : osPath.resize(strlen(osPath.c_str()));
59 2 : if (!cpl::ends_with(osPath, "/gdal") &&
60 4 : !cpl::ends_with(osPath, "\\gdal") &&
61 2 : !cpl::ends_with(osPath, "\\gdal.exe"))
62 : {
63 2 : osPath.clear();
64 : #if defined(__linux) && !defined(STATIC_BUILD)
65 4 : const CPLStringList aosLines(CSLLoad("/proc/self/maps"));
66 2741 : for (const char *pszLine : aosLines)
67 : {
68 2739 : const char *pszLibName = strstr(pszLine, "/libgdal.so.");
69 850 : while (pszLibName &&
70 3159 : static_cast<size_t>(pszLibName - pszLine) > 1 &&
71 430 : pszLibName[-1] != ' ')
72 : {
73 420 : --pszLibName;
74 : }
75 2739 : if (pszLibName &&
76 10 : static_cast<size_t>(pszLibName - pszLine) > 1 &&
77 10 : pszLibName[0] == '/')
78 : {
79 : const std::string osPathOfGDALLib =
80 20 : CPLGetDirnameSafe(pszLibName);
81 : std::string osBinFilename = CPLFormFilenameSafe(
82 10 : CPLGetDirnameSafe(osPathOfGDALLib.c_str()).c_str(),
83 20 : "bin/gdal", nullptr);
84 : VSIStatBufL sStat;
85 10 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
86 : {
87 : // Case if pszLibName=/usr/lib/libgdal.so.xxx
88 0 : osPath = std::move(osBinFilename);
89 : }
90 : else
91 : {
92 20 : osBinFilename = CPLFormFilenameSafe(
93 20 : CPLGetDirnameSafe(
94 20 : CPLGetDirnameSafe(osPathOfGDALLib.c_str())
95 : .c_str())
96 : .c_str(),
97 10 : "bin/gdal", nullptr);
98 10 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
99 : {
100 : // Case if pszLibName=/usr/lib/yyyyy/libgdal.so.xxx
101 0 : osPath = std::move(osBinFilename);
102 : }
103 : else
104 : {
105 20 : osBinFilename = CPLFormFilenameSafe(
106 10 : osPathOfGDALLib.c_str(), "apps/gdal", nullptr);
107 10 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
108 : {
109 : // Case if pszLibName=/path/to/build_dir/libgdal.so.xxx
110 10 : osPath = std::move(osBinFilename);
111 : }
112 : }
113 : }
114 : }
115 : }
116 : #endif
117 : }
118 2 : if (!osPath.empty())
119 : {
120 2 : CPLDebug("GDAL", "gdal binary found at '%s'", osPath.c_str());
121 : }
122 : }
123 2 : if (osPath.empty())
124 : {
125 : // Try to locate from the path
126 : #ifdef _WIN32
127 : osPath = "gdal.exe";
128 : #else
129 0 : osPath = "gdal";
130 : #endif
131 : }
132 :
133 2 : const char *const apszArgv[] = {osPath.c_str(), "--version", nullptr};
134 : const std::string osTmpFilenameVersion =
135 4 : VSIMemGenerateHiddenFilename(nullptr);
136 : auto fpOut = std::unique_ptr<VSIVirtualHandle>(
137 4 : VSIFOpenL(osTmpFilenameVersion.c_str(), "wb+"));
138 2 : VSIUnlink(osTmpFilenameVersion.c_str());
139 2 : CPLAssert(fpOut);
140 2 : CPLSpawn(apszArgv, nullptr, fpOut.get(), /* bDisplayErr = */ false);
141 2 : const auto nPos = fpOut->Tell();
142 4 : std::string osVersion;
143 2 : osVersion.resize(128);
144 2 : if (nPos > 0 && nPos < osVersion.size())
145 : {
146 2 : osVersion.resize(static_cast<size_t>(nPos));
147 2 : fpOut->Seek(0, SEEK_SET);
148 2 : fpOut->Read(osVersion.data(), 1, osVersion.size());
149 6 : for (const char ch : {'\n', '\r'})
150 : {
151 4 : if (!osVersion.empty() && osVersion.back() == ch)
152 : {
153 2 : osVersion.pop_back();
154 : }
155 : }
156 2 : if (osVersion == GDALVersionInfo(""))
157 : {
158 2 : return osPath;
159 : }
160 : else
161 : {
162 0 : CPLError(CE_Failure, CPLE_AppDefined,
163 : "'%s --version' returned '%s', whereas '%s' "
164 : "expected. Make sure the gdal binary corresponding "
165 : "to the version of the libgdal of the current "
166 : "process is in the PATH environment variable",
167 : osPath.c_str(), osVersion.c_str(), GDALVersionInfo(""));
168 : }
169 : }
170 : else
171 : {
172 0 : CPLError(CE_Failure, CPLE_AppDefined,
173 : "Could not find 'gdal' binary. Make sure it is in the "
174 : "PATH environment variable.");
175 : }
176 0 : return std::string();
177 : }
|