Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: OpenGIS Simple Features Reference Implementation
4 : * Purpose: Implements FileGDB OGR driver.
5 : * Author: Ragi Yaser Burhum, ragi@burhum.com
6 : * Paul Ramsey, pramsey at cleverelephant.ca
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2010, Ragi Yaser Burhum
10 : * Copyright (c) 2011, Paul Ramsey <pramsey at cleverelephant.ca>
11 : * Copyright (c) 2011-2013, Even Rouault <even dot rouault at spatialys.com>
12 : *
13 : * SPDX-License-Identifier: MIT
14 : ****************************************************************************/
15 :
16 : #include "ogr_fgdb.h"
17 : #include "cpl_conv.h"
18 : #include "FGdbUtils.h"
19 : #include "cpl_multiproc.h"
20 : #include "ogrmutexeddatasource.h"
21 : #include "FGdbDriverCore.h"
22 :
23 : extern "C" void RegisterOGRFileGDB();
24 :
25 : static std::map<CPLString, FGdbDatabaseConnection *> *poMapConnections =
26 : nullptr;
27 : CPLMutex *FGdbDriver::hMutex = nullptr;
28 : FGdbTransactionManager *FGdbDriver::m_poTransactionManager = nullptr;
29 :
30 : /************************************************************************/
31 : /* OGRFileGDBDriverUnload() */
32 : /************************************************************************/
33 :
34 11 : static void OGRFileGDBDriverUnload(GDALDriver *)
35 : {
36 11 : if (poMapConnections && !poMapConnections->empty())
37 0 : CPLDebug("FileGDB", "Remaining %d connections. Bug?",
38 0 : (int)poMapConnections->size());
39 11 : if (FGdbDriver::hMutex != nullptr)
40 6 : CPLDestroyMutex(FGdbDriver::hMutex);
41 11 : FGdbDriver::hMutex = nullptr;
42 11 : delete FGdbDriver::m_poTransactionManager;
43 11 : FGdbDriver::m_poTransactionManager = nullptr;
44 11 : delete poMapConnections;
45 11 : poMapConnections = nullptr;
46 11 : }
47 :
48 : /************************************************************************/
49 : /* OGRFileGDBDriverOpen() */
50 : /************************************************************************/
51 :
52 55 : static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo)
53 : {
54 55 : const char *pszFilename = poOpenInfo->pszFilename;
55 : // @MAY_USE_OPENFILEGDB may be set to NO by the OpenFileGDB driver in its
56 : // Open() method when it detects that a dataset includes compressed tables
57 : // (.cdf), and thus calls the FileGDB driver to make it handle such
58 : // datasets. As the FileGDB driver would call, by default, OpenFileGDB for
59 : // assistance to get some information, OpenFileGDB needs to instruct it not
60 : // to do so to avoid a OpenFileGDB -> FileGDB -> OpenFileGDB cycle.
61 55 : const bool bUseOpenFileGDB = CPLTestBool(CSLFetchNameValueDef(
62 55 : poOpenInfo->papszOpenOptions, "@MAY_USE_OPENFILEGDB", "YES"));
63 :
64 55 : if (bUseOpenFileGDB)
65 : {
66 53 : if (OGRFileGDBDriverIdentifyInternal(poOpenInfo, pszFilename) ==
67 : GDAL_IDENTIFY_FALSE)
68 0 : return nullptr;
69 :
70 : // If this is a raster-only GDB, do not try to open it, to be consistent
71 : // with OpenFileGDB behavior.
72 53 : const char *const apszOpenFileGDBDriver[] = {"OpenFileGDB", nullptr};
73 : auto poOpenFileGDBDS = std::unique_ptr<GDALDataset>(
74 : GDALDataset::Open(pszFilename, GDAL_OF_RASTER,
75 53 : apszOpenFileGDBDriver, nullptr, nullptr));
76 53 : if (poOpenFileGDBDS)
77 : {
78 0 : poOpenFileGDBDS.reset();
79 0 : poOpenFileGDBDS = std::unique_ptr<GDALDataset>(
80 : GDALDataset::Open(pszFilename, GDAL_OF_VECTOR,
81 0 : apszOpenFileGDBDriver, nullptr, nullptr));
82 0 : if (!poOpenFileGDBDS)
83 0 : return nullptr;
84 : }
85 : }
86 :
87 55 : const bool bUpdate = poOpenInfo->eAccess == GA_Update;
88 : long hr;
89 :
90 110 : CPLMutexHolderD(&FGdbDriver::hMutex);
91 55 : if (poMapConnections == nullptr)
92 2 : poMapConnections = new std::map<CPLString, FGdbDatabaseConnection *>();
93 :
94 55 : FGdbDatabaseConnection *pConnection = (*poMapConnections)[pszFilename];
95 55 : if (pConnection != nullptr)
96 : {
97 11 : if (pConnection->IsFIDHackInProgress())
98 : {
99 0 : CPLError(CE_Failure, CPLE_AppDefined,
100 : "Cannot open geodatabase at the moment since it is in "
101 : "'FID hack mode'");
102 0 : return nullptr;
103 : }
104 :
105 11 : pConnection->m_nRefCount++;
106 11 : CPLDebug("FileGDB", "ref_count of %s = %d now", pszFilename,
107 : pConnection->m_nRefCount);
108 : }
109 : else
110 : {
111 44 : Geodatabase *pGeoDatabase = new Geodatabase;
112 44 : hr = ::OpenGeodatabase(StringToWString(pszFilename), *pGeoDatabase);
113 :
114 44 : if (FAILED(hr))
115 : {
116 2 : delete pGeoDatabase;
117 :
118 3 : if (OGRGetDriverByName("OpenFileGDB") != nullptr &&
119 1 : bUpdate == FALSE)
120 : {
121 2 : std::wstring fgdb_error_desc_w;
122 2 : std::string fgdb_error_desc("Unknown error");
123 : fgdbError er;
124 1 : er = FileGDBAPI::ErrorInfo::GetErrorDescription(
125 : static_cast<fgdbError>(hr), fgdb_error_desc_w);
126 1 : if (er == S_OK)
127 : {
128 1 : fgdb_error_desc = WStringToString(fgdb_error_desc_w);
129 : }
130 1 : CPLDebug("FileGDB",
131 : "Cannot open %s with FileGDB driver: %s. Failing "
132 : "silently so OpenFileGDB can be tried",
133 : pszFilename, fgdb_error_desc.c_str());
134 : }
135 : else
136 : {
137 1 : GDBErr(hr, "Failed to open Geodatabase");
138 : }
139 2 : poMapConnections->erase(pszFilename);
140 2 : return nullptr;
141 : }
142 :
143 42 : CPLDebug("FileGDB", "Really opening %s", pszFilename);
144 42 : pConnection = new FGdbDatabaseConnection(pszFilename, pGeoDatabase);
145 42 : (*poMapConnections)[pszFilename] = pConnection;
146 : }
147 :
148 : FGdbDataSource *pDS;
149 :
150 53 : pDS = new FGdbDataSource(true, pConnection, bUseOpenFileGDB);
151 :
152 53 : if (!pDS->Open(pszFilename, bUpdate, nullptr))
153 : {
154 0 : delete pDS;
155 0 : return nullptr;
156 : }
157 : else
158 : {
159 : auto poMutexedDS =
160 53 : new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
161 53 : if (bUpdate)
162 16 : return OGRCreateEmulatedTransactionDataSourceWrapper(
163 8 : poMutexedDS, FGdbDriver::GetTransactionManager(), TRUE, FALSE);
164 : else
165 45 : return poMutexedDS;
166 : }
167 : }
168 :
169 : /***********************************************************************/
170 : /* OGRFileGDBDriverCreate() */
171 : /***********************************************************************/
172 :
173 : static GDALDataset *
174 37 : OGRFileGDBDriverCreate(const char *pszName, CPL_UNUSED int nBands,
175 : CPL_UNUSED int nXSize, CPL_UNUSED int nYSize,
176 : CPL_UNUSED GDALDataType eDT, char **papszOptions)
177 : {
178 : long hr;
179 : Geodatabase *pGeodatabase;
180 111 : std::wstring wconn = StringToWString(pszName);
181 37 : int bUpdate = TRUE; // If we're creating, we must be writing.
182 : VSIStatBuf stat;
183 :
184 74 : CPLMutexHolderD(&FGdbDriver::hMutex);
185 :
186 : /* We don't support options yet, so warn if they send us some */
187 : if (papszOptions)
188 : {
189 : /* TODO: warning, ignoring options */
190 : }
191 :
192 : /* Only accept names of form "filename.gdb" and */
193 : /* also .gdb.zip to be able to return FGDB with MapServer OGR output (#4199)
194 : */
195 74 : const std::string osExt = CPLGetExtensionSafe(pszName);
196 37 : if (!(EQUAL(osExt.c_str(), "gdb") || EQUAL(osExt.c_str(), "zip")))
197 : {
198 1 : CPLError(CE_Failure, CPLE_AppDefined,
199 : "FGDB data source name must use 'gdb' extension.\n");
200 1 : return nullptr;
201 : }
202 :
203 : /* Don't try to create on top of something already there */
204 36 : if (CPLStat(pszName, &stat) == 0)
205 : {
206 1 : CPLError(CE_Failure, CPLE_AppDefined, "%s already exists.\n", pszName);
207 1 : return nullptr;
208 : }
209 :
210 : /* Try to create the geodatabase */
211 35 : pGeodatabase =
212 35 : new Geodatabase; // Create on heap so we can store it in the Datasource
213 35 : hr = CreateGeodatabase(wconn, *pGeodatabase);
214 :
215 : /* Handle creation errors */
216 35 : if (S_OK != hr)
217 : {
218 1 : const char *errstr = "Error creating geodatabase (%s).\n";
219 1 : if (hr == -2147220653)
220 0 : errstr = "File already exists (%s).\n";
221 1 : delete pGeodatabase;
222 1 : CPLError(CE_Failure, CPLE_AppDefined, errstr, pszName);
223 1 : return nullptr;
224 : }
225 :
226 : FGdbDatabaseConnection *pConnection =
227 34 : new FGdbDatabaseConnection(pszName, pGeodatabase);
228 :
229 34 : if (poMapConnections == nullptr)
230 5 : poMapConnections = new std::map<CPLString, FGdbDatabaseConnection *>();
231 34 : (*poMapConnections)[pszName] = pConnection;
232 :
233 : /* Ready to embed the Geodatabase in an OGR Datasource */
234 34 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
235 34 : if (!pDS->Open(pszName, bUpdate, nullptr))
236 : {
237 0 : delete pDS;
238 0 : return nullptr;
239 : }
240 : else
241 34 : return OGRCreateEmulatedTransactionDataSourceWrapper(
242 34 : new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE),
243 68 : FGdbDriver::GetTransactionManager(), TRUE, FALSE);
244 : }
245 :
246 : /************************************************************************/
247 : /* StartTransaction() */
248 : /************************************************************************/
249 :
250 0 : OGRErr FGdbTransactionManager::StartTransaction(GDALDataset *&poDSInOut,
251 : int &bOutHasReopenedDS)
252 : {
253 0 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
254 :
255 0 : bOutHasReopenedDS = FALSE;
256 :
257 0 : auto poMutexedDS = (OGRMutexedDataSource *)poDSInOut;
258 : FGdbDataSource *poDS =
259 0 : cpl::down_cast<FGdbDataSource *>(poMutexedDS->GetBaseDataSource());
260 0 : if (!poDS->GetUpdate())
261 0 : return OGRERR_FAILURE;
262 0 : FGdbDatabaseConnection *pConnection = poDS->GetConnection();
263 0 : if (pConnection->GetRefCount() != 1)
264 : {
265 0 : CPLError(CE_Failure, CPLE_AppDefined,
266 : "Cannot start transaction as database is opened in another "
267 : "connection");
268 0 : return OGRERR_FAILURE;
269 : }
270 0 : if (pConnection->IsLocked())
271 : {
272 0 : CPLError(CE_Failure, CPLE_AppDefined,
273 : "Transaction is already in progress");
274 0 : return OGRERR_FAILURE;
275 : }
276 :
277 0 : bOutHasReopenedDS = TRUE;
278 :
279 0 : CPLString osName(poMutexedDS->GetDescription());
280 0 : CPLString osNameOri(osName);
281 0 : if (osName.back() == '/' || osName.back() == '\\')
282 0 : osName.pop_back();
283 :
284 : #ifndef _WIN32
285 : int bPerLayerCopyingForTransaction =
286 0 : poDS->HasPerLayerCopyingForTransaction();
287 : #endif
288 :
289 0 : pConnection->m_nRefCount++;
290 0 : delete poDSInOut;
291 0 : poDSInOut = nullptr;
292 0 : poMutexedDS = nullptr;
293 0 : poDS = nullptr;
294 :
295 0 : pConnection->CloseGeodatabase();
296 :
297 0 : const CPLString osEditedName(CPLString(osName).append(".ogredited"));
298 :
299 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
300 0 : CPL_IGNORE_RET_VAL(CPLUnlinkTree(osEditedName));
301 0 : CPLPopErrorHandler();
302 :
303 0 : OGRErr eErr = OGRERR_NONE;
304 :
305 0 : CPLString osDatabaseToReopen;
306 : #ifndef _WIN32
307 0 : if (bPerLayerCopyingForTransaction)
308 : {
309 0 : int bError = FALSE;
310 :
311 0 : if (VSIMkdir(osEditedName, 0755) != 0)
312 : {
313 0 : CPLError(CE_Failure, CPLE_AppDefined,
314 : "Cannot create directory '%s'.", osEditedName.c_str());
315 0 : bError = TRUE;
316 : }
317 :
318 : // Only copy a0000000X.Y files with X >= 1 && X <= 8, gdb and timestamps
319 : // and symlink others
320 0 : char **papszFiles = VSIReadDir(osName);
321 0 : for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter)
322 : {
323 0 : if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0)
324 0 : continue;
325 0 : if (((*papszIter)[0] == 'a' && atoi((*papszIter) + 1) >= 1 &&
326 0 : atoi((*papszIter) + 1) <= 8) ||
327 0 : EQUAL(*papszIter, "gdb") || EQUAL(*papszIter, "timestamps"))
328 : {
329 0 : if (CPLCopyFile(
330 0 : CPLFormFilenameSafe(osEditedName, *papszIter, nullptr)
331 : .c_str(),
332 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
333 0 : .c_str()) != 0)
334 : {
335 0 : bError = TRUE;
336 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot copy %s",
337 : *papszIter);
338 : }
339 : }
340 : else
341 : {
342 0 : CPLString osSourceFile;
343 0 : if (CPLIsFilenameRelative(osName))
344 0 : osSourceFile = CPLFormFilenameSafe(
345 : CPLSPrintf("../%s", CPLGetFilename(osName.c_str())),
346 0 : *papszIter, nullptr);
347 : else
348 0 : osSourceFile = osName;
349 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
350 0 : CPLSymlink(osSourceFile,
351 0 : CPLFormFilenameSafe(osEditedName.c_str(),
352 : *papszIter, nullptr)
353 : .c_str(),
354 : nullptr) != 0)
355 : {
356 0 : bError = TRUE;
357 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot symlink %s",
358 : *papszIter);
359 : }
360 : }
361 : }
362 0 : CSLDestroy(papszFiles);
363 :
364 0 : if (bError)
365 : {
366 0 : eErr = OGRERR_FAILURE;
367 0 : osDatabaseToReopen = osName;
368 : }
369 : else
370 0 : osDatabaseToReopen = osEditedName;
371 : }
372 : else
373 : #endif
374 : {
375 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
376 0 : CPLCopyTree(osEditedName, osName) != 0)
377 : {
378 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot backup geodatabase");
379 0 : eErr = OGRERR_FAILURE;
380 0 : osDatabaseToReopen = osName;
381 : }
382 : else
383 0 : osDatabaseToReopen = osEditedName;
384 : }
385 :
386 0 : pConnection->m_pGeodatabase = new Geodatabase;
387 0 : long hr = ::OpenGeodatabase(StringToWString(osDatabaseToReopen),
388 0 : *(pConnection->m_pGeodatabase));
389 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
390 : {
391 0 : delete pConnection->m_pGeodatabase;
392 0 : pConnection->m_pGeodatabase = nullptr;
393 0 : FGdbDriver::Release(osName);
394 0 : GDBErr(hr, CPLSPrintf("Failed to open %s. Dataset should be closed",
395 : osDatabaseToReopen.c_str()));
396 :
397 0 : return OGRERR_FAILURE;
398 : }
399 :
400 0 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
401 0 : pDS->Open(osDatabaseToReopen, TRUE, osNameOri);
402 :
403 : #ifndef _WIN32
404 0 : if (eErr == OGRERR_NONE && bPerLayerCopyingForTransaction)
405 : {
406 0 : pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
407 0 : pDS->SetSymlinkFlagOnAllLayers();
408 : }
409 : #endif
410 :
411 0 : poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
412 :
413 0 : if (eErr == OGRERR_NONE)
414 0 : pConnection->SetLocked(TRUE);
415 0 : return eErr;
416 : }
417 :
418 : /************************************************************************/
419 : /* CommitTransaction() */
420 : /************************************************************************/
421 :
422 0 : OGRErr FGdbTransactionManager::CommitTransaction(GDALDataset *&poDSInOut,
423 : int &bOutHasReopenedDS)
424 : {
425 0 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
426 :
427 0 : bOutHasReopenedDS = FALSE;
428 :
429 0 : OGRMutexedDataSource *poMutexedDS = (OGRMutexedDataSource *)poDSInOut;
430 0 : FGdbDataSource *poDS = (FGdbDataSource *)poMutexedDS->GetBaseDataSource();
431 0 : FGdbDatabaseConnection *pConnection = poDS->GetConnection();
432 0 : if (!pConnection->IsLocked())
433 : {
434 0 : CPLError(CE_Failure, CPLE_NotSupported, "No transaction in progress");
435 0 : return OGRERR_FAILURE;
436 : }
437 :
438 0 : bOutHasReopenedDS = TRUE;
439 :
440 0 : CPLString osName(poMutexedDS->GetDescription());
441 0 : CPLString osNameOri(osName);
442 0 : if (osName.back() == '/' || osName.back() == '\\')
443 0 : osName.pop_back();
444 :
445 : #ifndef _WIN32
446 : int bPerLayerCopyingForTransaction =
447 0 : poDS->HasPerLayerCopyingForTransaction();
448 : #endif
449 :
450 0 : pConnection->m_nRefCount++;
451 0 : delete poDSInOut;
452 0 : poDSInOut = nullptr;
453 0 : poMutexedDS = nullptr;
454 0 : poDS = nullptr;
455 :
456 0 : pConnection->CloseGeodatabase();
457 :
458 0 : CPLString osEditedName(osName);
459 0 : osEditedName += ".ogredited";
460 :
461 : #ifndef _WIN32
462 0 : if (bPerLayerCopyingForTransaction)
463 : {
464 0 : int bError = FALSE;
465 : char **papszFiles;
466 0 : std::vector<CPLString> aosTmpFilesToClean;
467 :
468 : // Check for files present in original copy that are not in edited copy
469 : // That is to say deleted layers
470 0 : papszFiles = VSIReadDir(osName);
471 0 : for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter)
472 : {
473 0 : if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0)
474 0 : continue;
475 : VSIStatBufL sStat;
476 0 : if ((*papszIter)[0] == 'a' &&
477 0 : VSIStatL(CPLFormFilenameSafe(osEditedName, *papszIter, nullptr)
478 : .c_str(),
479 : &sStat) != 0)
480 : {
481 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
482 0 : VSIRename(CPLFormFilenameSafe(osName, *papszIter, nullptr)
483 : .c_str(),
484 0 : CPLFormFilenameSafe(osName, *papszIter, "tmp")
485 : .c_str()) != 0)
486 : {
487 0 : CPLError(
488 : CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s",
489 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
490 : .c_str(),
491 0 : CPLFormFilenameSafe(osName, *papszIter, "tmp").c_str());
492 0 : bError = TRUE;
493 : }
494 : else
495 0 : aosTmpFilesToClean.push_back(
496 0 : CPLFormFilenameSafe(osName, *papszIter, "tmp"));
497 : }
498 : }
499 0 : CSLDestroy(papszFiles);
500 :
501 : // Move modified files from edited directory to main directory
502 0 : papszFiles = VSIReadDir(osEditedName);
503 0 : for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter)
504 : {
505 0 : if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0)
506 0 : continue;
507 : struct stat sStat;
508 0 : if (lstat(CPLFormFilenameSafe(osEditedName, *papszIter, nullptr)
509 : .c_str(),
510 0 : &sStat) != 0)
511 : {
512 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot stat %s",
513 0 : CPLFormFilenameSafe(osEditedName, *papszIter, nullptr)
514 : .c_str());
515 0 : bError = TRUE;
516 : }
517 0 : else if (!S_ISLNK(sStat.st_mode))
518 : {
519 : // If there was such a file in original directory, first rename
520 : // it as a temporary file
521 0 : if (lstat(CPLFormFilenameSafe(osName, *papszIter, nullptr)
522 : .c_str(),
523 0 : &sStat) == 0)
524 : {
525 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""),
526 0 : "CASE2") ||
527 0 : VSIRename(
528 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
529 : .c_str(),
530 0 : CPLFormFilenameSafe(osName, *papszIter, "tmp")
531 : .c_str()) != 0)
532 : {
533 0 : CPLError(
534 : CE_Failure, CPLE_AppDefined,
535 : "Cannot rename %s to %s",
536 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
537 : .c_str(),
538 0 : CPLFormFilenameSafe(osName, *papszIter, "tmp")
539 : .c_str());
540 0 : bError = TRUE;
541 : }
542 : else
543 0 : aosTmpFilesToClean.push_back(
544 0 : CPLFormFilenameSafe(osName, *papszIter, "tmp"));
545 : }
546 0 : if (!bError)
547 : {
548 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""),
549 0 : "CASE3") ||
550 0 : CPLMoveFile(
551 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
552 : .c_str(),
553 0 : CPLFormFilenameSafe(osEditedName, *papszIter,
554 : nullptr)
555 : .c_str()) != 0)
556 : {
557 0 : CPLError(
558 : CE_Failure, CPLE_AppDefined, "Cannot move %s to %s",
559 0 : CPLFormFilenameSafe(osEditedName, *papszIter,
560 : nullptr)
561 : .c_str(),
562 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
563 : .c_str());
564 0 : bError = TRUE;
565 : }
566 : else
567 0 : CPLDebug(
568 : "FileGDB", "Move %s to %s",
569 0 : CPLFormFilenameSafe(osEditedName, *papszIter,
570 : nullptr)
571 : .c_str(),
572 0 : CPLFormFilenameSafe(osName, *papszIter, nullptr)
573 : .c_str());
574 : }
575 : }
576 : }
577 0 : CSLDestroy(papszFiles);
578 :
579 0 : if (!bError)
580 : {
581 0 : for (size_t i = 0; i < aosTmpFilesToClean.size(); i++)
582 : {
583 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE4") ||
584 0 : VSIUnlink(aosTmpFilesToClean[i]) != 0)
585 : {
586 0 : CPLError(CE_Warning, CPLE_AppDefined,
587 : "Cannot remove %s. Manual cleanup required",
588 0 : aosTmpFilesToClean[i].c_str());
589 : }
590 : }
591 : }
592 :
593 0 : if (bError)
594 : {
595 0 : CPLError(
596 : CE_Failure, CPLE_AppDefined,
597 : "An error occurred while moving files from %s back to %s. "
598 : "Manual cleaning must be done and dataset should be closed",
599 : osEditedName.c_str(), osName.c_str());
600 0 : pConnection->SetLocked(FALSE);
601 0 : FGdbDriver::Release(osName);
602 0 : return OGRERR_FAILURE;
603 : }
604 0 : else if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE5") ||
605 0 : CPLUnlinkTree(osEditedName) != 0)
606 : {
607 0 : CPLError(CE_Warning, CPLE_AppDefined,
608 : "Cannot remove %s. Manual cleanup required",
609 : osEditedName.c_str());
610 : }
611 : }
612 : else
613 : #endif
614 : {
615 0 : CPLString osTmpName(osName);
616 0 : osTmpName += ".ogrtmp";
617 :
618 : /* Install the backup copy as the main database in 3 steps : */
619 : /* first rename the main directory in .tmp */
620 : /* then rename the edited copy under regular name */
621 : /* and finally dispose the .tmp directory */
622 : /* That way there's no risk definitely losing data */
623 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
624 0 : VSIRename(osName, osTmpName) != 0)
625 : {
626 0 : CPLError(CE_Failure, CPLE_AppDefined,
627 : "Cannot rename %s to %s. Edited database during "
628 : "transaction is in %s"
629 : "Dataset should be closed",
630 : osName.c_str(), osTmpName.c_str(), osEditedName.c_str());
631 0 : pConnection->SetLocked(FALSE);
632 0 : FGdbDriver::Release(osName);
633 0 : return OGRERR_FAILURE;
634 : }
635 :
636 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
637 0 : VSIRename(osEditedName, osName) != 0)
638 : {
639 0 : CPLError(
640 : CE_Failure, CPLE_AppDefined,
641 : "Cannot rename %s to %s. The original geodatabase is in '%s'. "
642 : "Dataset should be closed",
643 : osEditedName.c_str(), osName.c_str(), osTmpName.c_str());
644 0 : pConnection->SetLocked(FALSE);
645 0 : FGdbDriver::Release(osName);
646 0 : return OGRERR_FAILURE;
647 : }
648 :
649 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
650 0 : CPLUnlinkTree(osTmpName) != 0)
651 : {
652 0 : CPLError(CE_Warning, CPLE_AppDefined,
653 : "Cannot remove %s. Manual cleanup required",
654 : osTmpName.c_str());
655 : }
656 : }
657 :
658 0 : pConnection->m_pGeodatabase = new Geodatabase;
659 0 : long hr = ::OpenGeodatabase(StringToWString(osName),
660 0 : *(pConnection->m_pGeodatabase));
661 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE_REOPEN") ||
662 0 : FAILED(hr))
663 : {
664 0 : delete pConnection->m_pGeodatabase;
665 0 : pConnection->m_pGeodatabase = nullptr;
666 0 : pConnection->SetLocked(FALSE);
667 0 : FGdbDriver::Release(osName);
668 0 : GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
669 0 : return OGRERR_FAILURE;
670 : }
671 :
672 0 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
673 0 : pDS->Open(osNameOri, TRUE, nullptr);
674 : // pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
675 0 : poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
676 :
677 0 : pConnection->SetLocked(FALSE);
678 :
679 0 : return OGRERR_NONE;
680 : }
681 :
682 : /************************************************************************/
683 : /* RollbackTransaction() */
684 : /************************************************************************/
685 :
686 0 : OGRErr FGdbTransactionManager::RollbackTransaction(GDALDataset *&poDSInOut,
687 : int &bOutHasReopenedDS)
688 : {
689 0 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
690 :
691 0 : bOutHasReopenedDS = FALSE;
692 :
693 0 : OGRMutexedDataSource *poMutexedDS = (OGRMutexedDataSource *)poDSInOut;
694 0 : FGdbDataSource *poDS = (FGdbDataSource *)poMutexedDS->GetBaseDataSource();
695 0 : FGdbDatabaseConnection *pConnection = poDS->GetConnection();
696 0 : if (!pConnection->IsLocked())
697 : {
698 0 : CPLError(CE_Failure, CPLE_NotSupported, "No transaction in progress");
699 0 : return OGRERR_FAILURE;
700 : }
701 :
702 0 : bOutHasReopenedDS = TRUE;
703 :
704 0 : CPLString osName(poMutexedDS->GetDescription());
705 0 : CPLString osNameOri(osName);
706 0 : if (osName.back() == '/' || osName.back() == '\\')
707 0 : osName.pop_back();
708 :
709 : // int bPerLayerCopyingForTransaction =
710 : // poDS->HasPerLayerCopyingForTransaction();
711 :
712 0 : pConnection->m_nRefCount++;
713 0 : delete poDSInOut;
714 0 : poDSInOut = nullptr;
715 0 : poMutexedDS = nullptr;
716 0 : poDS = nullptr;
717 :
718 0 : pConnection->CloseGeodatabase();
719 :
720 0 : CPLString osEditedName(osName);
721 0 : osEditedName += ".ogredited";
722 :
723 0 : OGRErr eErr = OGRERR_NONE;
724 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
725 0 : CPLUnlinkTree(osEditedName) != 0)
726 : {
727 0 : CPLError(CE_Warning, CPLE_AppDefined,
728 : "Cannot remove %s. Manual cleanup required",
729 : osEditedName.c_str());
730 0 : eErr = OGRERR_FAILURE;
731 : }
732 :
733 0 : pConnection->m_pGeodatabase = new Geodatabase;
734 0 : long hr = ::OpenGeodatabase(StringToWString(osName),
735 0 : *(pConnection->m_pGeodatabase));
736 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
737 : {
738 0 : delete pConnection->m_pGeodatabase;
739 0 : pConnection->m_pGeodatabase = nullptr;
740 0 : pConnection->SetLocked(FALSE);
741 0 : FGdbDriver::Release(osName);
742 0 : GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
743 0 : return OGRERR_FAILURE;
744 : }
745 :
746 0 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
747 0 : pDS->Open(osNameOri, TRUE, nullptr);
748 : // pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
749 0 : poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
750 :
751 0 : pConnection->SetLocked(FALSE);
752 :
753 0 : return eErr;
754 : }
755 :
756 : /***********************************************************************/
757 : /* Release() */
758 : /***********************************************************************/
759 :
760 87 : void FGdbDriver::Release(const char *pszName)
761 : {
762 174 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
763 :
764 87 : if (poMapConnections == nullptr)
765 0 : poMapConnections = new std::map<CPLString, FGdbDatabaseConnection *>();
766 :
767 87 : FGdbDatabaseConnection *pConnection = (*poMapConnections)[pszName];
768 87 : if (pConnection != nullptr)
769 : {
770 87 : pConnection->m_nRefCount--;
771 87 : CPLDebug("FileGDB", "ref_count of %s = %d now", pszName,
772 : pConnection->m_nRefCount);
773 87 : if (pConnection->m_nRefCount == 0)
774 : {
775 76 : pConnection->CloseGeodatabase();
776 76 : delete pConnection;
777 76 : poMapConnections->erase(pszName);
778 : }
779 : }
780 87 : }
781 :
782 : /***********************************************************************/
783 : /* GetTransactionManager() */
784 : /***********************************************************************/
785 :
786 42 : FGdbTransactionManager *FGdbDriver::GetTransactionManager()
787 : {
788 42 : CPLMutexHolderD(&FGdbDriver::hMutex);
789 42 : if (m_poTransactionManager == nullptr)
790 6 : m_poTransactionManager = new FGdbTransactionManager();
791 84 : return m_poTransactionManager;
792 : }
793 :
794 : /***********************************************************************/
795 : /* CloseGeodatabase() */
796 : /***********************************************************************/
797 :
798 77 : void FGdbDatabaseConnection::CloseGeodatabase()
799 : {
800 77 : if (m_pGeodatabase != nullptr)
801 : {
802 77 : CPLDebug("FileGDB", "Really closing %s now", m_osName.c_str());
803 77 : ::CloseGeodatabase(*m_pGeodatabase);
804 77 : delete m_pGeodatabase;
805 77 : m_pGeodatabase = nullptr;
806 : }
807 77 : }
808 :
809 : /***********************************************************************/
810 : /* OpenGeodatabase() */
811 : /***********************************************************************/
812 :
813 1 : int FGdbDatabaseConnection::OpenGeodatabase(const char *pszFSName)
814 : {
815 1 : m_pGeodatabase = new Geodatabase;
816 2 : long hr = ::OpenGeodatabase(StringToWString(CPLString(pszFSName)),
817 2 : *m_pGeodatabase);
818 1 : if (FAILED(hr))
819 : {
820 0 : delete m_pGeodatabase;
821 0 : m_pGeodatabase = nullptr;
822 0 : return FALSE;
823 : }
824 1 : return TRUE;
825 : }
826 :
827 : /************************************************************************/
828 : /* OGRFileGDBDeleteDataSource() */
829 : /************************************************************************/
830 :
831 2 : static CPLErr OGRFileGDBDeleteDataSource(const char *pszDataSource)
832 : {
833 4 : CPLMutexHolderD(&FGdbDriver::hMutex);
834 :
835 6 : std::wstring wstr = StringToWString(pszDataSource);
836 :
837 2 : long hr = 0;
838 :
839 2 : if (S_OK != (hr = ::DeleteGeodatabase(wstr)))
840 : {
841 1 : GDBErr(hr, "Failed to delete Geodatabase");
842 1 : return CE_Failure;
843 : }
844 :
845 1 : return CE_None;
846 : }
847 :
848 : /***********************************************************************/
849 : /* RegisterOGRFileGDB() */
850 : /***********************************************************************/
851 :
852 15 : void RegisterOGRFileGDB()
853 :
854 : {
855 15 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
856 0 : return;
857 :
858 15 : if (!GDAL_CHECK_VERSION("OGR FGDB"))
859 0 : return;
860 :
861 15 : GDALDriver *poDriver = new GDALDriver();
862 15 : OGRFileGDBDriverSetCommonMetadata(poDriver);
863 :
864 15 : poDriver->pfnOpen = OGRFileGDBDriverOpen;
865 15 : poDriver->pfnCreate = OGRFileGDBDriverCreate;
866 15 : poDriver->pfnDelete = OGRFileGDBDeleteDataSource;
867 15 : poDriver->pfnUnloadDriver = OGRFileGDBDriverUnload;
868 :
869 15 : GetGDALDriverManager()->RegisterDriver(poDriver);
870 : }
|