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