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_config.h"
14 :
15 : #if HAVE_DL_ITERATE_PHDR
16 : #if !defined(_GNU_SOURCE)
17 : #define _GNU_SOURCE
18 : #endif
19 : #include <link.h>
20 :
21 : #elif defined(__MACH__) && defined(__APPLE__)
22 : #include <mach-o/dyld.h>
23 :
24 : #endif
25 :
26 : #include "cpl_spawn.h"
27 : #include "cpl_vsi_virtual.h"
28 : #include "gdal.h"
29 : #include "gdalgetgdalpath.h"
30 :
31 : #include <cassert>
32 :
33 : /************************************************************************/
34 : /* GDALGetGDALPathDLIterateCbk() */
35 : /************************************************************************/
36 :
37 : #if HAVE_DL_ITERATE_PHDR && !defined(STATIC_BUILD)
38 :
39 93 : static int GDALGetGDALPathDLIterateCbk(struct dl_phdr_info *info,
40 : size_t /*size*/, void *data)
41 : {
42 93 : if (info->dlpi_name && strstr(info->dlpi_name, "/libgdal.so."))
43 : {
44 3 : *static_cast<std::string *>(data) = info->dlpi_name;
45 3 : return 1;
46 : }
47 90 : return 0; // continue iteration
48 : }
49 :
50 : #endif
51 :
52 : /************************************************************************/
53 : /* GDALGetGDALPath() */
54 : /************************************************************************/
55 :
56 : /** Return the path of the "gdal" binary, or an empty string if it cannot be
57 : * found.
58 : *
59 : * The GDAL_PATH configuration option may be set to point to the directory where
60 : * the GDAL binary is located.
61 : */
62 8 : std::string GDALGetGDALPath()
63 : {
64 8 : const char *pszGDAL_PATH = CPLGetConfigOption("GDAL_PATH", nullptr);
65 8 : if (pszGDAL_PATH)
66 : {
67 : VSIStatBufL sStat;
68 5 : for (const char *pszProgramName : {"gdal"
69 : #ifdef _WIN32
70 : ,
71 : "gdal.exe"
72 : #endif
73 10 : })
74 : {
75 : std::string osPath =
76 5 : CPLFormFilenameSafe(pszGDAL_PATH, pszProgramName, nullptr);
77 5 : if (VSIStatL(osPath.c_str(), &sStat) == 0)
78 0 : return osPath;
79 : }
80 5 : CPLError(CE_Failure, CPLE_AppDefined,
81 : "No 'gdal' binary can be found in '%s'", pszGDAL_PATH);
82 5 : return std::string();
83 : }
84 :
85 3 : constexpr int MAXPATH_SIZE = 4096;
86 6 : std::string osPath;
87 3 : osPath.resize(MAXPATH_SIZE);
88 3 : if (CPLGetExecPath(osPath.data(), MAXPATH_SIZE))
89 : {
90 3 : osPath.resize(strlen(osPath.c_str()));
91 3 : if (!cpl::ends_with(osPath, "/gdal") &&
92 6 : !cpl::ends_with(osPath, "\\gdal") &&
93 3 : !cpl::ends_with(osPath, "\\gdal.exe"))
94 : {
95 3 : osPath.clear();
96 : #if (HAVE_DL_ITERATE_PHDR || (defined(__MACH__) && defined(__APPLE__))) && \
97 : !defined(STATIC_BUILD)
98 6 : std::string osGDALLib;
99 : #if HAVE_DL_ITERATE_PHDR
100 3 : dl_iterate_phdr(GDALGetGDALPathDLIterateCbk, &osGDALLib);
101 : #else
102 : const uint32_t imageCount = _dyld_image_count();
103 : for (uint32_t i = 0; i < imageCount; ++i)
104 : {
105 : const char *imageName = _dyld_get_image_name(i);
106 : if (imageName && strstr(imageName, "/libgdal."))
107 : {
108 : osGDALLib = imageName;
109 : break;
110 : }
111 : }
112 : #endif
113 3 : if (!osGDALLib.empty() && osGDALLib[0] == '/')
114 : {
115 : const std::string osPathOfGDALLib =
116 6 : CPLGetDirnameSafe(osGDALLib.c_str());
117 : std::string osBinFilename = CPLFormFilenameSafe(
118 3 : CPLGetDirnameSafe(osPathOfGDALLib.c_str()).c_str(),
119 6 : "bin/gdal", nullptr);
120 : VSIStatBufL sStat;
121 3 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
122 : {
123 : // Case if osGDALLib=/usr/lib/libgdal.so.xxx
124 0 : osPath = std::move(osBinFilename);
125 : }
126 : else
127 : {
128 6 : osBinFilename = CPLFormFilenameSafe(
129 6 : CPLGetDirnameSafe(
130 6 : CPLGetDirnameSafe(osPathOfGDALLib.c_str()).c_str())
131 : .c_str(),
132 3 : "bin/gdal", nullptr);
133 3 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
134 : {
135 : // Case if pszLibName=/usr/lib/libgdal.so.xxx
136 0 : osPath = std::move(osBinFilename);
137 : }
138 : else
139 : {
140 6 : osBinFilename = CPLFormFilenameSafe(
141 3 : osPathOfGDALLib.c_str(), "apps/gdal", nullptr);
142 3 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
143 : {
144 : // Case if pszLibName=/usr/lib/yyyyy/libgdal.so.xxx
145 3 : osPath = std::move(osBinFilename);
146 : }
147 : else
148 : {
149 0 : osBinFilename = CPLFormFilenameSafe(
150 0 : osPathOfGDALLib.c_str(), "apps/gdal", nullptr);
151 0 : if (VSIStatL(osBinFilename.c_str(), &sStat) == 0)
152 : {
153 : // Case if pszLibName=/path/to/build_dir/libgdal.so.xxx
154 0 : osPath = std::move(osBinFilename);
155 : }
156 : }
157 : }
158 : }
159 : }
160 : #endif
161 : }
162 3 : if (!osPath.empty())
163 : {
164 3 : CPLDebug("GDAL", "gdal binary found at '%s'", osPath.c_str());
165 : }
166 : }
167 : else
168 : {
169 0 : osPath.clear();
170 : }
171 3 : if (osPath.empty())
172 : {
173 : // Try to locate from the path
174 : #ifdef _WIN32
175 : osPath = "gdal.exe";
176 : #else
177 0 : osPath = "gdal";
178 : #endif
179 : }
180 :
181 3 : const char *const apszArgv[] = {osPath.c_str(), "--version", nullptr};
182 : const std::string osTmpFilenameVersion =
183 6 : VSIMemGenerateHiddenFilename(nullptr);
184 : auto fpOut = std::unique_ptr<VSIVirtualHandle>(
185 6 : VSIFOpenL(osTmpFilenameVersion.c_str(), "wb+"));
186 3 : VSIUnlink(osTmpFilenameVersion.c_str());
187 3 : CPLAssert(fpOut);
188 3 : CPLSpawn(apszArgv, nullptr, fpOut.get(), /* bDisplayErr = */ false);
189 3 : const auto nPos = fpOut->Tell();
190 6 : std::string osVersion;
191 3 : osVersion.resize(128);
192 3 : if (nPos > 0 && nPos < osVersion.size())
193 : {
194 3 : osVersion.resize(static_cast<size_t>(nPos));
195 3 : fpOut->Seek(0, SEEK_SET);
196 3 : fpOut->Read(osVersion.data(), 1, osVersion.size());
197 9 : for (const char ch : {'\n', '\r'})
198 : {
199 6 : if (!osVersion.empty() && osVersion.back() == ch)
200 : {
201 3 : osVersion.pop_back();
202 : }
203 : }
204 3 : if (osVersion == GDALVersionInfo(""))
205 : {
206 3 : return osPath;
207 : }
208 : else
209 : {
210 0 : CPLError(CE_Failure, CPLE_AppDefined,
211 : "'%s --version' returned '%s', whereas '%s' "
212 : "expected. Make sure the gdal binary corresponding "
213 : "to the version of the libgdal of the current "
214 : "process is in the PATH environment variable",
215 : osPath.c_str(), osVersion.c_str(), GDALVersionInfo(""));
216 : }
217 : }
218 : else
219 : {
220 0 : CPLError(CE_Failure, CPLE_AppDefined,
221 : "Could not find 'gdal' binary. Make sure it is in the "
222 : "PATH environment variable.");
223 : }
224 0 : return std::string();
225 : }
|