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 37 : const char *pszExt = CPLGetExtension(pszName);
196 37 : if (!(EQUAL(pszExt, "gdb") || EQUAL(pszExt, "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 : CPLFormFilename(osEditedName, *papszIter, nullptr),
331 0 : CPLFormFilename(osName, *papszIter, nullptr)) != 0)
332 : {
333 0 : bError = TRUE;
334 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot copy %s",
335 : *papszIter);
336 : }
337 : }
338 : else
339 : {
340 0 : CPLString osSourceFile;
341 0 : if (CPLIsFilenameRelative(osName))
342 : osSourceFile = CPLFormFilename(
343 : CPLSPrintf("../%s", CPLGetFilename(osName.c_str())),
344 0 : *papszIter, nullptr);
345 : else
346 0 : osSourceFile = osName;
347 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
348 0 : CPLSymlink(osSourceFile,
349 : CPLFormFilename(osEditedName.c_str(), *papszIter,
350 : nullptr),
351 : nullptr) != 0)
352 : {
353 0 : bError = TRUE;
354 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot symlink %s",
355 : *papszIter);
356 : }
357 : }
358 : }
359 0 : CSLDestroy(papszFiles);
360 :
361 0 : if (bError)
362 : {
363 0 : eErr = OGRERR_FAILURE;
364 0 : osDatabaseToReopen = osName;
365 : }
366 : else
367 0 : osDatabaseToReopen = osEditedName;
368 : }
369 : else
370 : #endif
371 : {
372 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
373 0 : CPLCopyTree(osEditedName, osName) != 0)
374 : {
375 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot backup geodatabase");
376 0 : eErr = OGRERR_FAILURE;
377 0 : osDatabaseToReopen = osName;
378 : }
379 : else
380 0 : osDatabaseToReopen = osEditedName;
381 : }
382 :
383 0 : pConnection->m_pGeodatabase = new Geodatabase;
384 0 : long hr = ::OpenGeodatabase(StringToWString(osDatabaseToReopen),
385 0 : *(pConnection->m_pGeodatabase));
386 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
387 : {
388 0 : delete pConnection->m_pGeodatabase;
389 0 : pConnection->m_pGeodatabase = nullptr;
390 0 : FGdbDriver::Release(osName);
391 0 : GDBErr(hr, CPLSPrintf("Failed to open %s. Dataset should be closed",
392 : osDatabaseToReopen.c_str()));
393 :
394 0 : return OGRERR_FAILURE;
395 : }
396 :
397 0 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
398 0 : pDS->Open(osDatabaseToReopen, TRUE, osNameOri);
399 :
400 : #ifndef _WIN32
401 0 : if (eErr == OGRERR_NONE && bPerLayerCopyingForTransaction)
402 : {
403 0 : pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
404 0 : pDS->SetSymlinkFlagOnAllLayers();
405 : }
406 : #endif
407 :
408 0 : poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
409 :
410 0 : if (eErr == OGRERR_NONE)
411 0 : pConnection->SetLocked(TRUE);
412 0 : return eErr;
413 : }
414 :
415 : /************************************************************************/
416 : /* CommitTransaction() */
417 : /************************************************************************/
418 :
419 0 : OGRErr FGdbTransactionManager::CommitTransaction(GDALDataset *&poDSInOut,
420 : int &bOutHasReopenedDS)
421 : {
422 0 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
423 :
424 0 : bOutHasReopenedDS = FALSE;
425 :
426 0 : OGRMutexedDataSource *poMutexedDS = (OGRMutexedDataSource *)poDSInOut;
427 0 : FGdbDataSource *poDS = (FGdbDataSource *)poMutexedDS->GetBaseDataSource();
428 0 : FGdbDatabaseConnection *pConnection = poDS->GetConnection();
429 0 : if (!pConnection->IsLocked())
430 : {
431 0 : CPLError(CE_Failure, CPLE_NotSupported, "No transaction in progress");
432 0 : return OGRERR_FAILURE;
433 : }
434 :
435 0 : bOutHasReopenedDS = TRUE;
436 :
437 0 : CPLString osName(poMutexedDS->GetDescription());
438 0 : CPLString osNameOri(osName);
439 0 : if (osName.back() == '/' || osName.back() == '\\')
440 0 : osName.pop_back();
441 :
442 : #ifndef _WIN32
443 : int bPerLayerCopyingForTransaction =
444 0 : poDS->HasPerLayerCopyingForTransaction();
445 : #endif
446 :
447 0 : pConnection->m_nRefCount++;
448 0 : delete poDSInOut;
449 0 : poDSInOut = nullptr;
450 0 : poMutexedDS = nullptr;
451 0 : poDS = nullptr;
452 :
453 0 : pConnection->CloseGeodatabase();
454 :
455 0 : CPLString osEditedName(osName);
456 0 : osEditedName += ".ogredited";
457 :
458 : #ifndef _WIN32
459 0 : if (bPerLayerCopyingForTransaction)
460 : {
461 0 : int bError = FALSE;
462 : char **papszFiles;
463 0 : std::vector<CPLString> aosTmpFilesToClean;
464 :
465 : // Check for files present in original copy that are not in edited copy
466 : // That is to say deleted layers
467 0 : papszFiles = VSIReadDir(osName);
468 0 : for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter)
469 : {
470 0 : if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0)
471 0 : continue;
472 : VSIStatBufL sStat;
473 0 : if ((*papszIter)[0] == 'a' &&
474 0 : VSIStatL(CPLFormFilename(osEditedName, *papszIter, nullptr),
475 : &sStat) != 0)
476 : {
477 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
478 0 : VSIRename(CPLFormFilename(osName, *papszIter, nullptr),
479 : CPLFormFilename(osName, *papszIter, "tmp")) != 0)
480 : {
481 0 : CPLError(CE_Failure, CPLE_AppDefined,
482 : "Cannot rename %s to %s",
483 : CPLFormFilename(osName, *papszIter, nullptr),
484 : CPLFormFilename(osName, *papszIter, "tmp"));
485 0 : bError = TRUE;
486 : }
487 : else
488 0 : aosTmpFilesToClean.push_back(
489 : CPLFormFilename(osName, *papszIter, "tmp"));
490 : }
491 : }
492 0 : CSLDestroy(papszFiles);
493 :
494 : // Move modified files from edited directory to main directory
495 0 : papszFiles = VSIReadDir(osEditedName);
496 0 : for (char **papszIter = papszFiles; !bError && *papszIter; ++papszIter)
497 : {
498 0 : if (strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0)
499 0 : continue;
500 : struct stat sStat;
501 0 : if (lstat(CPLFormFilename(osEditedName, *papszIter, nullptr),
502 0 : &sStat) != 0)
503 : {
504 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot stat %s",
505 : CPLFormFilename(osEditedName, *papszIter, nullptr));
506 0 : bError = TRUE;
507 : }
508 0 : else if (!S_ISLNK(sStat.st_mode))
509 : {
510 : // If there was such a file in original directory, first rename
511 : // it as a temporary file
512 0 : if (lstat(CPLFormFilename(osName, *papszIter, nullptr),
513 0 : &sStat) == 0)
514 : {
515 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""),
516 0 : "CASE2") ||
517 0 : VSIRename(CPLFormFilename(osName, *papszIter, nullptr),
518 : CPLFormFilename(osName, *papszIter, "tmp")) !=
519 : 0)
520 : {
521 0 : CPLError(CE_Failure, CPLE_AppDefined,
522 : "Cannot rename %s to %s",
523 : CPLFormFilename(osName, *papszIter, nullptr),
524 : CPLFormFilename(osName, *papszIter, "tmp"));
525 0 : bError = TRUE;
526 : }
527 : else
528 0 : aosTmpFilesToClean.push_back(
529 : CPLFormFilename(osName, *papszIter, "tmp"));
530 : }
531 0 : if (!bError)
532 : {
533 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""),
534 0 : "CASE3") ||
535 0 : CPLMoveFile(
536 : CPLFormFilename(osName, *papszIter, nullptr),
537 : CPLFormFilename(osEditedName, *papszIter,
538 : nullptr)) != 0)
539 : {
540 0 : CPLError(
541 : CE_Failure, CPLE_AppDefined, "Cannot move %s to %s",
542 : CPLFormFilename(osEditedName, *papszIter, nullptr),
543 : CPLFormFilename(osName, *papszIter, nullptr));
544 0 : bError = TRUE;
545 : }
546 : else
547 0 : CPLDebug(
548 : "FileGDB", "Move %s to %s",
549 : CPLFormFilename(osEditedName, *papszIter, nullptr),
550 : CPLFormFilename(osName, *papszIter, nullptr));
551 : }
552 : }
553 : }
554 0 : CSLDestroy(papszFiles);
555 :
556 0 : if (!bError)
557 : {
558 0 : for (size_t i = 0; i < aosTmpFilesToClean.size(); i++)
559 : {
560 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE4") ||
561 0 : VSIUnlink(aosTmpFilesToClean[i]) != 0)
562 : {
563 0 : CPLError(CE_Warning, CPLE_AppDefined,
564 : "Cannot remove %s. Manual cleanup required",
565 0 : aosTmpFilesToClean[i].c_str());
566 : }
567 : }
568 : }
569 :
570 0 : if (bError)
571 : {
572 0 : CPLError(
573 : CE_Failure, CPLE_AppDefined,
574 : "An error occurred while moving files from %s back to %s. "
575 : "Manual cleaning must be done and dataset should be closed",
576 : osEditedName.c_str(), osName.c_str());
577 0 : pConnection->SetLocked(FALSE);
578 0 : FGdbDriver::Release(osName);
579 0 : return OGRERR_FAILURE;
580 : }
581 0 : else if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE5") ||
582 0 : CPLUnlinkTree(osEditedName) != 0)
583 : {
584 0 : CPLError(CE_Warning, CPLE_AppDefined,
585 : "Cannot remove %s. Manual cleanup required",
586 : osEditedName.c_str());
587 : }
588 : }
589 : else
590 : #endif
591 : {
592 0 : CPLString osTmpName(osName);
593 0 : osTmpName += ".ogrtmp";
594 :
595 : /* Install the backup copy as the main database in 3 steps : */
596 : /* first rename the main directory in .tmp */
597 : /* then rename the edited copy under regular name */
598 : /* and finally dispose the .tmp directory */
599 : /* That way there's no risk definitely losing data */
600 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
601 0 : VSIRename(osName, osTmpName) != 0)
602 : {
603 0 : CPLError(CE_Failure, CPLE_AppDefined,
604 : "Cannot rename %s to %s. Edited database during "
605 : "transaction is in %s"
606 : "Dataset should be closed",
607 : osName.c_str(), osTmpName.c_str(), osEditedName.c_str());
608 0 : pConnection->SetLocked(FALSE);
609 0 : FGdbDriver::Release(osName);
610 0 : return OGRERR_FAILURE;
611 : }
612 :
613 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
614 0 : VSIRename(osEditedName, osName) != 0)
615 : {
616 0 : CPLError(
617 : CE_Failure, CPLE_AppDefined,
618 : "Cannot rename %s to %s. The original geodatabase is in '%s'. "
619 : "Dataset should be closed",
620 : osEditedName.c_str(), osName.c_str(), osTmpName.c_str());
621 0 : pConnection->SetLocked(FALSE);
622 0 : FGdbDriver::Release(osName);
623 0 : return OGRERR_FAILURE;
624 : }
625 :
626 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
627 0 : CPLUnlinkTree(osTmpName) != 0)
628 : {
629 0 : CPLError(CE_Warning, CPLE_AppDefined,
630 : "Cannot remove %s. Manual cleanup required",
631 : osTmpName.c_str());
632 : }
633 : }
634 :
635 0 : pConnection->m_pGeodatabase = new Geodatabase;
636 0 : long hr = ::OpenGeodatabase(StringToWString(osName),
637 0 : *(pConnection->m_pGeodatabase));
638 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE_REOPEN") ||
639 0 : FAILED(hr))
640 : {
641 0 : delete pConnection->m_pGeodatabase;
642 0 : pConnection->m_pGeodatabase = nullptr;
643 0 : pConnection->SetLocked(FALSE);
644 0 : FGdbDriver::Release(osName);
645 0 : GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
646 0 : return OGRERR_FAILURE;
647 : }
648 :
649 0 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
650 0 : pDS->Open(osNameOri, TRUE, nullptr);
651 : // pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
652 0 : poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
653 :
654 0 : pConnection->SetLocked(FALSE);
655 :
656 0 : return OGRERR_NONE;
657 : }
658 :
659 : /************************************************************************/
660 : /* RollbackTransaction() */
661 : /************************************************************************/
662 :
663 0 : OGRErr FGdbTransactionManager::RollbackTransaction(GDALDataset *&poDSInOut,
664 : int &bOutHasReopenedDS)
665 : {
666 0 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
667 :
668 0 : bOutHasReopenedDS = FALSE;
669 :
670 0 : OGRMutexedDataSource *poMutexedDS = (OGRMutexedDataSource *)poDSInOut;
671 0 : FGdbDataSource *poDS = (FGdbDataSource *)poMutexedDS->GetBaseDataSource();
672 0 : FGdbDatabaseConnection *pConnection = poDS->GetConnection();
673 0 : if (!pConnection->IsLocked())
674 : {
675 0 : CPLError(CE_Failure, CPLE_NotSupported, "No transaction in progress");
676 0 : return OGRERR_FAILURE;
677 : }
678 :
679 0 : bOutHasReopenedDS = TRUE;
680 :
681 0 : CPLString osName(poMutexedDS->GetDescription());
682 0 : CPLString osNameOri(osName);
683 0 : if (osName.back() == '/' || osName.back() == '\\')
684 0 : osName.pop_back();
685 :
686 : // int bPerLayerCopyingForTransaction =
687 : // poDS->HasPerLayerCopyingForTransaction();
688 :
689 0 : pConnection->m_nRefCount++;
690 0 : delete poDSInOut;
691 0 : poDSInOut = nullptr;
692 0 : poMutexedDS = nullptr;
693 0 : poDS = nullptr;
694 :
695 0 : pConnection->CloseGeodatabase();
696 :
697 0 : CPLString osEditedName(osName);
698 0 : osEditedName += ".ogredited";
699 :
700 0 : OGRErr eErr = OGRERR_NONE;
701 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
702 0 : CPLUnlinkTree(osEditedName) != 0)
703 : {
704 0 : CPLError(CE_Warning, CPLE_AppDefined,
705 : "Cannot remove %s. Manual cleanup required",
706 : osEditedName.c_str());
707 0 : eErr = OGRERR_FAILURE;
708 : }
709 :
710 0 : pConnection->m_pGeodatabase = new Geodatabase;
711 0 : long hr = ::OpenGeodatabase(StringToWString(osName),
712 0 : *(pConnection->m_pGeodatabase));
713 0 : if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
714 : {
715 0 : delete pConnection->m_pGeodatabase;
716 0 : pConnection->m_pGeodatabase = nullptr;
717 0 : pConnection->SetLocked(FALSE);
718 0 : FGdbDriver::Release(osName);
719 0 : GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
720 0 : return OGRERR_FAILURE;
721 : }
722 :
723 0 : FGdbDataSource *pDS = new FGdbDataSource(true, pConnection, true);
724 0 : pDS->Open(osNameOri, TRUE, nullptr);
725 : // pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
726 0 : poDSInOut = new OGRMutexedDataSource(pDS, TRUE, FGdbDriver::hMutex, TRUE);
727 :
728 0 : pConnection->SetLocked(FALSE);
729 :
730 0 : return eErr;
731 : }
732 :
733 : /***********************************************************************/
734 : /* Release() */
735 : /***********************************************************************/
736 :
737 87 : void FGdbDriver::Release(const char *pszName)
738 : {
739 174 : CPLMutexHolderOptionalLockD(FGdbDriver::hMutex);
740 :
741 87 : if (poMapConnections == nullptr)
742 0 : poMapConnections = new std::map<CPLString, FGdbDatabaseConnection *>();
743 :
744 87 : FGdbDatabaseConnection *pConnection = (*poMapConnections)[pszName];
745 87 : if (pConnection != nullptr)
746 : {
747 87 : pConnection->m_nRefCount--;
748 87 : CPLDebug("FileGDB", "ref_count of %s = %d now", pszName,
749 : pConnection->m_nRefCount);
750 87 : if (pConnection->m_nRefCount == 0)
751 : {
752 76 : pConnection->CloseGeodatabase();
753 76 : delete pConnection;
754 76 : poMapConnections->erase(pszName);
755 : }
756 : }
757 87 : }
758 :
759 : /***********************************************************************/
760 : /* GetTransactionManager() */
761 : /***********************************************************************/
762 :
763 42 : FGdbTransactionManager *FGdbDriver::GetTransactionManager()
764 : {
765 42 : CPLMutexHolderD(&FGdbDriver::hMutex);
766 42 : if (m_poTransactionManager == nullptr)
767 6 : m_poTransactionManager = new FGdbTransactionManager();
768 84 : return m_poTransactionManager;
769 : }
770 :
771 : /***********************************************************************/
772 : /* CloseGeodatabase() */
773 : /***********************************************************************/
774 :
775 77 : void FGdbDatabaseConnection::CloseGeodatabase()
776 : {
777 77 : if (m_pGeodatabase != nullptr)
778 : {
779 77 : CPLDebug("FileGDB", "Really closing %s now", m_osName.c_str());
780 77 : ::CloseGeodatabase(*m_pGeodatabase);
781 77 : delete m_pGeodatabase;
782 77 : m_pGeodatabase = nullptr;
783 : }
784 77 : }
785 :
786 : /***********************************************************************/
787 : /* OpenGeodatabase() */
788 : /***********************************************************************/
789 :
790 1 : int FGdbDatabaseConnection::OpenGeodatabase(const char *pszFSName)
791 : {
792 1 : m_pGeodatabase = new Geodatabase;
793 2 : long hr = ::OpenGeodatabase(StringToWString(CPLString(pszFSName)),
794 2 : *m_pGeodatabase);
795 1 : if (FAILED(hr))
796 : {
797 0 : delete m_pGeodatabase;
798 0 : m_pGeodatabase = nullptr;
799 0 : return FALSE;
800 : }
801 1 : return TRUE;
802 : }
803 :
804 : /************************************************************************/
805 : /* OGRFileGDBDeleteDataSource() */
806 : /************************************************************************/
807 :
808 2 : static CPLErr OGRFileGDBDeleteDataSource(const char *pszDataSource)
809 : {
810 4 : CPLMutexHolderD(&FGdbDriver::hMutex);
811 :
812 6 : std::wstring wstr = StringToWString(pszDataSource);
813 :
814 2 : long hr = 0;
815 :
816 2 : if (S_OK != (hr = ::DeleteGeodatabase(wstr)))
817 : {
818 1 : GDBErr(hr, "Failed to delete Geodatabase");
819 1 : return CE_Failure;
820 : }
821 :
822 1 : return CE_None;
823 : }
824 :
825 : /***********************************************************************/
826 : /* RegisterOGRFileGDB() */
827 : /***********************************************************************/
828 :
829 15 : void RegisterOGRFileGDB()
830 :
831 : {
832 15 : if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
833 0 : return;
834 :
835 15 : if (!GDAL_CHECK_VERSION("OGR FGDB"))
836 0 : return;
837 :
838 15 : GDALDriver *poDriver = new GDALDriver();
839 15 : OGRFileGDBDriverSetCommonMetadata(poDriver);
840 :
841 15 : poDriver->pfnOpen = OGRFileGDBDriverOpen;
842 15 : poDriver->pfnCreate = OGRFileGDBDriverCreate;
843 15 : poDriver->pfnDelete = OGRFileGDBDeleteDataSource;
844 15 : poDriver->pfnUnloadDriver = OGRFileGDBDriverUnload;
845 :
846 15 : GetGDALDriverManager()->RegisterDriver(poDriver);
847 : }
|