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 
38 CPL_CVSID("$Id: FGdbDriver.cpp 1761acd90777d5bcc49eddbc13c193098f0ed40b 2020-10-01 12:12:00 +0200 Even Rouault $")
39 
40 extern "C" void RegisterOGRFileGDB();
41 
42 /************************************************************************/
43 /*                            FGdbDriver()                              */
44 /************************************************************************/
FGdbDriver()45 FGdbDriver::FGdbDriver(): OGRSFDriver(), hMutex(nullptr)
46 {
47 }
48 
49 /************************************************************************/
50 /*                            ~FGdbDriver()                             */
51 /************************************************************************/
~FGdbDriver()52 FGdbDriver::~FGdbDriver()
53 
54 {
55     if( !oMapConnections.empty() )
56         CPLDebug("FileGDB", "Remaining %d connections. Bug?",
57                  (int)oMapConnections.size());
58     if( hMutex != nullptr )
59         CPLDestroyMutex(hMutex);
60     hMutex = nullptr;
61 }
62 
63 /************************************************************************/
64 /*                              GetName()                               */
65 /************************************************************************/
66 
GetName()67 const char *FGdbDriver::GetName()
68 
69 {
70     return "FileGDB";
71 }
72 
73 /************************************************************************/
74 /*                                Open()                                */
75 /************************************************************************/
76 
Open(const char * pszFilename,int bUpdate)77 OGRDataSource *FGdbDriver::Open( const char* pszFilename, int bUpdate )
78 
79 {
80     // First check if we have to do any work.
81     size_t nLen = strlen(pszFilename);
82     if( nLen == 1 && pszFilename[0] == '.' )
83     {
84         char* pszCurrentDir = CPLGetCurrentDir();
85         if( pszCurrentDir )
86         {
87             size_t nLen2 = strlen(pszCurrentDir);
88             bool bOK = (nLen2 >= 4 && EQUAL(pszCurrentDir + nLen2 - 4, ".gdb"));
89             CPLFree(pszCurrentDir);
90             if( !bOK )
91                 return nullptr;
92         }
93         else
94         {
95             return nullptr;
96         }
97     }
98     else if(! ((nLen >= 4 && EQUAL(pszFilename + nLen - 4, ".gdb")) ||
99                (nLen >= 5 && EQUAL(pszFilename + nLen - 5, ".gdb/"))) )
100         return nullptr;
101 
102     long hr;
103 
104     /* Check that the filename is really a directory, to avoid confusion with */
105     /* Garmin MapSource - gdb format which can be a problem when the FileGDB */
106     /* driver is loaded as a plugin, and loaded before the GPSBabel driver */
107     /* (http://trac.osgeo.org/osgeo4w/ticket/245) */
108     VSIStatBuf stat;
109     if( CPLStat( pszFilename, &stat ) != 0 || !VSI_ISDIR(stat.st_mode) )
110     {
111         return nullptr;
112     }
113 
114     CPLMutexHolderD(&hMutex);
115 
116     FGdbDatabaseConnection* pConnection = oMapConnections[pszFilename];
117     if( pConnection != nullptr )
118     {
119         if( pConnection->IsFIDHackInProgress() )
120         {
121             CPLError(CE_Failure, CPLE_AppDefined,
122                      "Cannot open geodatabase at the moment since it is in 'FID hack mode'");
123             return nullptr;
124         }
125 
126         pConnection->m_nRefCount ++;
127         CPLDebug("FileGDB", "ref_count of %s = %d now", pszFilename,
128                  pConnection->m_nRefCount);
129     }
130     else
131     {
132         Geodatabase* pGeoDatabase = new Geodatabase;
133         hr = ::OpenGeodatabase(StringToWString(pszFilename), *pGeoDatabase);
134 
135         if (FAILED(hr))
136         {
137             delete pGeoDatabase;
138 
139             if( OGRGetDriverByName("OpenFileGDB") != nullptr && bUpdate == FALSE )
140             {
141                 std::wstring fgdb_error_desc_w;
142                 std::string fgdb_error_desc("Unknown error");
143                 fgdbError er;
144                 er = FileGDBAPI::ErrorInfo::GetErrorDescription(static_cast<fgdbError>(hr), fgdb_error_desc_w);
145                 if ( er == S_OK )
146                 {
147                     fgdb_error_desc = WStringToString(fgdb_error_desc_w);
148                 }
149                 CPLDebug("FileGDB", "Cannot open %s with FileGDB driver: %s. Failing silently so OpenFileGDB can be tried",
150                          pszFilename,
151                          fgdb_error_desc.c_str());
152             }
153             else
154             {
155                 GDBErr(hr, "Failed to open Geodatabase");
156             }
157             oMapConnections.erase(pszFilename);
158             return nullptr;
159         }
160 
161         CPLDebug("FileGDB", "Really opening %s", pszFilename);
162         pConnection = new FGdbDatabaseConnection(pszFilename, pGeoDatabase);
163         oMapConnections[pszFilename] = pConnection;
164     }
165 
166     FGdbDataSource* pDS;
167 
168     pDS = new FGdbDataSource(this, pConnection);
169 
170     if(!pDS->Open( pszFilename, bUpdate, nullptr ) )
171     {
172         delete pDS;
173         return nullptr;
174     }
175     else
176     {
177         OGRMutexedDataSource* poMutexedDS =
178                 new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
179         if( bUpdate )
180             return OGRCreateEmulatedTransactionDataSourceWrapper(poMutexedDS, this, TRUE, FALSE);
181         else
182             return poMutexedDS;
183     }
184 }
185 
186 /***********************************************************************/
187 /*                     CreateDataSource()                              */
188 /***********************************************************************/
189 
CreateDataSource(const char * conn,char ** papszOptions)190 OGRDataSource* FGdbDriver::CreateDataSource( const char * conn,
191                                            char **papszOptions)
192 {
193     long hr;
194     Geodatabase *pGeodatabase;
195     std::wstring wconn = StringToWString(conn);
196     int bUpdate = TRUE; // If we're creating, we must be writing.
197     VSIStatBuf stat;
198 
199     CPLMutexHolderD(&hMutex);
200 
201     /* We don't support options yet, so warn if they send us some */
202     if ( papszOptions )
203     {
204         /* TODO: warning, ignoring options */
205     }
206 
207     /* Only accept names of form "filename.gdb" and */
208     /* also .gdb.zip to be able to return FGDB with MapServer OGR output (#4199) */
209     const char* pszExt = CPLGetExtension(conn);
210     if ( !(EQUAL(pszExt,"gdb") || EQUAL(pszExt, "zip")) )
211     {
212         CPLError( CE_Failure, CPLE_AppDefined,
213                   "FGDB data source name must use 'gdb' extension.\n" );
214         return nullptr;
215     }
216 
217     /* Don't try to create on top of something already there */
218     if( CPLStat( conn, &stat ) == 0 )
219     {
220         CPLError( CE_Failure, CPLE_AppDefined,
221                   "%s already exists.\n", conn );
222         return nullptr;
223     }
224 
225     /* Try to create the geodatabase */
226     pGeodatabase = new Geodatabase; // Create on heap so we can store it in the Datasource
227     hr = CreateGeodatabase(wconn, *pGeodatabase);
228 
229     /* Handle creation errors */
230     if ( S_OK != hr )
231     {
232         const char *errstr = "Error creating geodatabase (%s).\n";
233         if ( hr == -2147220653 )
234             errstr = "File already exists (%s).\n";
235         delete pGeodatabase;
236         CPLError( CE_Failure, CPLE_AppDefined, errstr, conn );
237         return nullptr;
238     }
239 
240     FGdbDatabaseConnection* pConnection = new FGdbDatabaseConnection(conn, pGeodatabase);
241     oMapConnections[conn] = pConnection;
242 
243     /* Ready to embed the Geodatabase in an OGR Datasource */
244     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
245     if ( ! pDS->Open(conn, bUpdate, nullptr) )
246     {
247         delete pDS;
248         return nullptr;
249     }
250     else
251         return OGRCreateEmulatedTransactionDataSourceWrapper(
252             new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE), this,
253             TRUE, FALSE);
254 }
255 
256 /************************************************************************/
257 /*                           StartTransaction()                         */
258 /************************************************************************/
259 
StartTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)260 OGRErr FGdbDriver::StartTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
261 {
262     CPLMutexHolderOptionalLockD(hMutex);
263 
264     bOutHasReopenedDS = FALSE;
265 
266     OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
267     FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
268     if( !poDS->GetUpdate() )
269         return OGRERR_FAILURE;
270     FGdbDatabaseConnection* pConnection = poDS->GetConnection();
271     if( pConnection->GetRefCount() != 1 )
272     {
273         CPLError(CE_Failure, CPLE_AppDefined,
274                  "Cannot start transaction as database is opened in another connection");
275         return OGRERR_FAILURE;
276     }
277     if( pConnection->IsLocked() )
278     {
279         CPLError(CE_Failure, CPLE_AppDefined,
280                  "Transaction is already in progress");
281         return OGRERR_FAILURE;
282     }
283 
284     bOutHasReopenedDS = TRUE;
285 
286     CPLString osName(poMutexedDS->GetName());
287     CPLString osNameOri(osName);
288     if( osName.back()== '/' || osName.back()== '\\' )
289         osName.resize(osName.size()-1);
290 
291 #ifndef WIN32
292     int bPerLayerCopyingForTransaction = poDS->HasPerLayerCopyingForTransaction();
293 #endif
294 
295     pConnection->m_nRefCount ++;
296     delete poDSInOut;
297     poDSInOut = nullptr;
298     poMutexedDS = nullptr;
299     poDS = nullptr;
300 
301     pConnection->CloseGeodatabase();
302 
303     CPLString osEditedName(osName);
304     osEditedName += ".ogredited";
305 
306     CPLPushErrorHandler(CPLQuietErrorHandler);
307     CPL_IGNORE_RET_VAL(CPLUnlinkTree(osEditedName));
308     CPLPopErrorHandler();
309 
310     OGRErr eErr = OGRERR_NONE;
311 
312     CPLString osDatabaseToReopen;
313 #ifndef WIN32
314     if( bPerLayerCopyingForTransaction )
315     {
316         int bError = FALSE;
317 
318         if( VSIMkdir( osEditedName, 0755 ) != 0 )
319         {
320             CPLError( CE_Failure, CPLE_AppDefined,
321                       "Cannot create directory '%s'.",
322                       osEditedName.c_str() );
323             bError = TRUE;
324         }
325 
326         // Only copy a0000000X.Y files with X >= 1 && X <= 8, gdb and timestamps
327         // and symlink others
328         char** papszFiles = VSIReadDir(osName);
329         for(char** papszIter = papszFiles; !bError && *papszIter; ++papszIter)
330         {
331             if( strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0 )
332                 continue;
333             if( ((*papszIter)[0] == 'a' && atoi((*papszIter)+1) >= 1 &&
334                  atoi((*papszIter)+1) <= 8) || EQUAL(*papszIter, "gdb") ||
335                  EQUAL(*papszIter, "timestamps") )
336             {
337                 if( CPLCopyFile(CPLFormFilename(osEditedName, *papszIter, nullptr),
338                                 CPLFormFilename(osName, *papszIter, nullptr)) != 0 )
339                 {
340                     bError = TRUE;
341                     CPLError(CE_Failure, CPLE_AppDefined,
342                              "Cannot copy %s", *papszIter);
343                 }
344             }
345             else
346             {
347                 CPLString osSourceFile;
348                 if( CPLIsFilenameRelative(osName) )
349                     osSourceFile = CPLFormFilename(CPLSPrintf("../%s", CPLGetFilename(osName.c_str())), *papszIter, nullptr);
350                 else
351                     osSourceFile = osName;
352                 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
353                     CPLSymlink( osSourceFile,
354                                 CPLFormFilename(osEditedName.c_str(), *papszIter, nullptr),
355                                 nullptr ) != 0 )
356                 {
357                     bError = TRUE;
358                     CPLError(CE_Failure, CPLE_AppDefined,
359                              "Cannot symlink %s", *papszIter);
360                 }
361             }
362         }
363         CSLDestroy(papszFiles);
364 
365         if( bError )
366         {
367             eErr = OGRERR_FAILURE;
368             osDatabaseToReopen = osName;
369         }
370         else
371             osDatabaseToReopen = osEditedName;
372     }
373     else
374 #endif
375     {
376         if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
377             CPLCopyTree( osEditedName, osName ) != 0 )
378         {
379             CPLError(CE_Failure, CPLE_AppDefined,
380                     "Cannot backup geodatabase");
381             eErr = OGRERR_FAILURE;
382             osDatabaseToReopen = osName;
383         }
384         else
385             osDatabaseToReopen = osEditedName;
386     }
387 
388     pConnection->m_pGeodatabase = new Geodatabase;
389     long hr = ::OpenGeodatabase(StringToWString(osDatabaseToReopen), *(pConnection->m_pGeodatabase));
390     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") || FAILED(hr))
391     {
392         delete pConnection->m_pGeodatabase;
393         pConnection->m_pGeodatabase = nullptr;
394         Release(osName);
395         GDBErr(hr, CPLSPrintf("Failed to open %s. Dataset should be closed",
396                               osDatabaseToReopen.c_str()));
397 
398         return OGRERR_FAILURE;
399     }
400 
401     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
402     pDS->Open(osDatabaseToReopen, TRUE, osNameOri);
403 
404 #ifndef WIN32
405     if( eErr == OGRERR_NONE && bPerLayerCopyingForTransaction )
406     {
407         pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
408         pDS->SetSymlinkFlagOnAllLayers();
409     }
410 #endif
411 
412     poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
413 
414     if( eErr == OGRERR_NONE )
415         pConnection->SetLocked(TRUE);
416     return eErr;
417 }
418 
419 /************************************************************************/
420 /*                           CommitTransaction()                        */
421 /************************************************************************/
422 
CommitTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)423 OGRErr FGdbDriver::CommitTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
424 {
425     CPLMutexHolderOptionalLockD(hMutex);
426 
427     bOutHasReopenedDS = FALSE;
428 
429     OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
430     FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
431     FGdbDatabaseConnection* pConnection = poDS->GetConnection();
432     if( !pConnection->IsLocked() )
433     {
434         CPLError(CE_Failure, CPLE_NotSupported,
435                  "No transaction in progress");
436         return OGRERR_FAILURE;
437     }
438 
439     bOutHasReopenedDS = TRUE;
440 
441     CPLString osName(poMutexedDS->GetName());
442     CPLString osNameOri(osName);
443     if( osName.back()== '/' || osName.back()== '\\' )
444         osName.resize(osName.size()-1);
445 
446 #ifndef WIN32
447     int bPerLayerCopyingForTransaction = poDS->HasPerLayerCopyingForTransaction();
448 #endif
449 
450     pConnection->m_nRefCount ++;
451     delete poDSInOut;
452     poDSInOut = nullptr;
453     poMutexedDS = nullptr;
454     poDS = nullptr;
455 
456     pConnection->CloseGeodatabase();
457 
458     CPLString osEditedName(osName);
459     osEditedName += ".ogredited";
460 
461 #ifndef WIN32
462     if( bPerLayerCopyingForTransaction )
463     {
464         int bError = FALSE;
465         char** papszFiles;
466         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         papszFiles = VSIReadDir(osName);
471         for(char** papszIter = papszFiles; !bError && *papszIter; ++papszIter)
472         {
473             if( strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0 )
474                 continue;
475             VSIStatBufL sStat;
476             if( (*papszIter)[0] == 'a' &&
477                 VSIStatL( CPLFormFilename(osEditedName, *papszIter, nullptr), &sStat ) != 0 )
478             {
479                 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
480                     VSIRename( CPLFormFilename(osName, *papszIter, nullptr),
481                                CPLFormFilename(osName, *papszIter, "tmp") ) != 0 )
482                 {
483                     CPLError(CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s",
484                              CPLFormFilename(osName, *papszIter, nullptr),
485                              CPLFormFilename(osName, *papszIter, "tmp"));
486                     bError = TRUE;
487                 }
488                 else
489                     aosTmpFilesToClean.push_back(CPLFormFilename(osName, *papszIter, "tmp"));
490             }
491         }
492         CSLDestroy(papszFiles);
493 
494         // Move modified files from edited directory to main directory
495         papszFiles = VSIReadDir(osEditedName);
496         for(char** papszIter = papszFiles; !bError && *papszIter; ++papszIter)
497         {
498             if( strcmp(*papszIter, ".") == 0 || strcmp(*papszIter, "..") == 0 )
499                 continue;
500             struct stat sStat;
501             if( lstat( CPLFormFilename(osEditedName, *papszIter, nullptr), &sStat ) != 0 )
502             {
503                 CPLError(CE_Failure, CPLE_AppDefined, "Cannot stat %s",
504                          CPLFormFilename(osEditedName, *papszIter, nullptr));
505                 bError = TRUE;
506             }
507             else if( !S_ISLNK(sStat.st_mode) )
508             {
509                 // If there was such a file in original directory, first rename it
510                 // as a temporary file
511                 if( lstat( CPLFormFilename(osName, *papszIter, nullptr), &sStat ) == 0 )
512                 {
513                     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
514                         VSIRename( CPLFormFilename(osName, *papszIter, nullptr),
515                                    CPLFormFilename(osName, *papszIter, "tmp") ) != 0 )
516                     {
517                         CPLError(CE_Failure, CPLE_AppDefined, "Cannot rename %s to %s",
518                                  CPLFormFilename(osName, *papszIter, nullptr),
519                                  CPLFormFilename(osName, *papszIter, "tmp"));
520                         bError = TRUE;
521                     }
522                     else
523                         aosTmpFilesToClean.push_back(CPLFormFilename(osName, *papszIter, "tmp"));
524                 }
525                 if( !bError )
526                 {
527                     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
528                         CPLMoveFile( CPLFormFilename(osName, *papszIter, nullptr),
529                                      CPLFormFilename(osEditedName, *papszIter, nullptr) ) != 0 )
530                     {
531                         CPLError(CE_Failure, CPLE_AppDefined, "Cannot move %s to %s",
532                                  CPLFormFilename(osEditedName, *papszIter, nullptr),
533                                  CPLFormFilename(osName, *papszIter, nullptr));
534                         bError = TRUE;
535                     }
536                     else
537                         CPLDebug("FileGDB", "Move %s to %s",
538                                  CPLFormFilename(osEditedName, *papszIter, nullptr),
539                                  CPLFormFilename(osName, *papszIter, nullptr));
540                 }
541             }
542         }
543         CSLDestroy(papszFiles);
544 
545         if( !bError )
546         {
547             for(size_t i=0;i<aosTmpFilesToClean.size();i++)
548             {
549                 if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE4") ||
550                     VSIUnlink(aosTmpFilesToClean[i]) != 0 )
551                 {
552                     CPLError(CE_Warning, CPLE_AppDefined,
553                              "Cannot remove %s. Manual cleanup required", aosTmpFilesToClean[i].c_str());
554                 }
555             }
556         }
557 
558         if( bError )
559         {
560             CPLError(CE_Failure, CPLE_AppDefined,
561                      "An error occurred while moving files from %s back to %s. "
562                      "Manual cleaning must be done and dataset should be closed",
563                      osEditedName.c_str(),
564                      osName.c_str());
565             pConnection->SetLocked(FALSE);
566             Release(osName);
567             return OGRERR_FAILURE;
568         }
569         else if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE5") ||
570                  CPLUnlinkTree(osEditedName) != 0 )
571         {
572             CPLError(CE_Warning, CPLE_AppDefined,
573                     "Cannot remove %s. Manual cleanup required", osEditedName.c_str());
574         }
575     }
576     else
577 #endif
578     {
579         CPLString osTmpName(osName);
580         osTmpName += ".ogrtmp";
581 
582         /* Install the backup copy as the main database in 3 steps : */
583         /* first rename the main directory  in .tmp */
584         /* then rename the edited copy under regular name */
585         /* and finally dispose the .tmp directory */
586         /* That way there's no risk definitely losing data */
587         if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
588             VSIRename(osName, osTmpName) != 0 )
589         {
590             CPLError(CE_Failure, CPLE_AppDefined,
591                     "Cannot rename %s to %s. Edited database during transaction is in %s"
592                     "Dataset should be closed",
593                     osName.c_str(), osTmpName.c_str(), osEditedName.c_str());
594             pConnection->SetLocked(FALSE);
595             Release(osName);
596             return OGRERR_FAILURE;
597         }
598 
599         if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
600             VSIRename(osEditedName, osName) != 0 )
601         {
602             CPLError(CE_Failure, CPLE_AppDefined,
603                     "Cannot rename %s to %s. The original geodatabase is in '%s'. "
604                     "Dataset should be closed",
605                     osEditedName.c_str(), osName.c_str(), osTmpName.c_str());
606             pConnection->SetLocked(FALSE);
607             Release(osName);
608             return OGRERR_FAILURE;
609         }
610 
611         if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE3") ||
612             CPLUnlinkTree(osTmpName) != 0 )
613         {
614             CPLError(CE_Warning, CPLE_AppDefined,
615                     "Cannot remove %s. Manual cleanup required", osTmpName.c_str());
616         }
617     }
618 
619     pConnection->m_pGeodatabase = new Geodatabase;
620     long hr = ::OpenGeodatabase(StringToWString(osName), *(pConnection->m_pGeodatabase));
621     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE_REOPEN") || FAILED(hr))
622     {
623         delete pConnection->m_pGeodatabase;
624         pConnection->m_pGeodatabase = nullptr;
625         pConnection->SetLocked(FALSE);
626         Release(osName);
627         GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
628         return OGRERR_FAILURE;
629     }
630 
631     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
632     pDS->Open(osNameOri, TRUE, nullptr);
633     //pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
634     poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
635 
636     pConnection->SetLocked(FALSE);
637 
638     return OGRERR_NONE;
639 }
640 
641 /************************************************************************/
642 /*                           RollbackTransaction()                      */
643 /************************************************************************/
644 
RollbackTransaction(OGRDataSource * & poDSInOut,int & bOutHasReopenedDS)645 OGRErr FGdbDriver::RollbackTransaction(OGRDataSource*& poDSInOut, int& bOutHasReopenedDS)
646 {
647     CPLMutexHolderOptionalLockD(hMutex);
648 
649     bOutHasReopenedDS = FALSE;
650 
651     OGRMutexedDataSource* poMutexedDS = (OGRMutexedDataSource*)poDSInOut;
652     FGdbDataSource* poDS = (FGdbDataSource* )poMutexedDS->GetBaseDataSource();
653     FGdbDatabaseConnection* pConnection = poDS->GetConnection();
654     if( !pConnection->IsLocked() )
655     {
656         CPLError(CE_Failure, CPLE_NotSupported,
657                  "No transaction in progress");
658         return OGRERR_FAILURE;
659     }
660 
661     bOutHasReopenedDS = TRUE;
662 
663     CPLString osName(poMutexedDS->GetName());
664     CPLString osNameOri(osName);
665     if( osName.back()== '/' || osName.back()== '\\' )
666         osName.resize(osName.size()-1);
667 
668     //int bPerLayerCopyingForTransaction = poDS->HasPerLayerCopyingForTransaction();
669 
670     pConnection->m_nRefCount ++;
671     delete poDSInOut;
672     poDSInOut = nullptr;
673     poMutexedDS = nullptr;
674     poDS = nullptr;
675 
676     pConnection->CloseGeodatabase();
677 
678     CPLString osEditedName(osName);
679     osEditedName += ".ogredited";
680 
681     OGRErr eErr = OGRERR_NONE;
682     if( EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE1") ||
683         CPLUnlinkTree(osEditedName) != 0 )
684     {
685         CPLError(CE_Warning, CPLE_AppDefined,
686                  "Cannot remove %s. Manual cleanup required", osEditedName.c_str());
687         eErr = OGRERR_FAILURE;
688     }
689 
690     pConnection->m_pGeodatabase = new Geodatabase;
691     long hr = ::OpenGeodatabase(StringToWString(osName), *(pConnection->m_pGeodatabase));
692     if (EQUAL(CPLGetConfigOption("FGDB_SIMUL_FAIL", ""), "CASE2") ||
693         FAILED(hr))
694     {
695         delete pConnection->m_pGeodatabase;
696         pConnection->m_pGeodatabase = nullptr;
697         pConnection->SetLocked(FALSE);
698         Release(osName);
699         GDBErr(hr, "Failed to re-open Geodatabase. Dataset should be closed");
700         return OGRERR_FAILURE;
701     }
702 
703     FGdbDataSource* pDS = new FGdbDataSource(this, pConnection);
704     pDS->Open(osNameOri, TRUE, nullptr);
705     //pDS->SetPerLayerCopyingForTransaction(bPerLayerCopyingForTransaction);
706     poDSInOut = new OGRMutexedDataSource(pDS, TRUE, hMutex, TRUE);
707 
708     pConnection->SetLocked(FALSE);
709 
710     return eErr;
711 }
712 
713 /***********************************************************************/
714 /*                            Release()                                */
715 /***********************************************************************/
716 
Release(const char * pszName)717 void FGdbDriver::Release(const char* pszName)
718 {
719     CPLMutexHolderOptionalLockD(hMutex);
720 
721     FGdbDatabaseConnection* pConnection = oMapConnections[pszName];
722     if( pConnection != nullptr )
723     {
724         pConnection->m_nRefCount --;
725         CPLDebug("FileGDB", "ref_count of %s = %d now", pszName,
726                  pConnection->m_nRefCount);
727         if( pConnection->m_nRefCount == 0 )
728         {
729             pConnection->CloseGeodatabase();
730             delete pConnection;
731             oMapConnections.erase(pszName);
732         }
733     }
734 }
735 
736 /***********************************************************************/
737 /*                         CloseGeodatabase()                          */
738 /***********************************************************************/
739 
CloseGeodatabase()740 void FGdbDatabaseConnection::CloseGeodatabase()
741 {
742     if( m_pGeodatabase != nullptr )
743     {
744         CPLDebug("FileGDB", "Really closing %s now", m_osName.c_str());
745         ::CloseGeodatabase(*m_pGeodatabase);
746         delete m_pGeodatabase;
747         m_pGeodatabase = nullptr;
748     }
749 }
750 
751 /***********************************************************************/
752 /*                         OpenGeodatabase()                           */
753 /***********************************************************************/
754 
OpenGeodatabase(const char * pszFSName)755 int FGdbDatabaseConnection::OpenGeodatabase(const char* pszFSName)
756 {
757     m_pGeodatabase = new Geodatabase;
758     long hr = ::OpenGeodatabase(StringToWString(CPLString(pszFSName)), *m_pGeodatabase);
759     if (FAILED(hr))
760     {
761         delete m_pGeodatabase;
762         m_pGeodatabase = nullptr;
763         return FALSE;
764     }
765     return TRUE;
766 }
767 
768 /***********************************************************************/
769 /*                         TestCapability()                            */
770 /***********************************************************************/
771 
TestCapability(const char * pszCap)772 int FGdbDriver::TestCapability( const char * pszCap )
773 {
774     if (EQUAL(pszCap, ODrCCreateDataSource) )
775         return TRUE;
776 
777     else if (EQUAL(pszCap, ODrCDeleteDataSource) )
778         return TRUE;
779 
780     return FALSE;
781 }
782 /************************************************************************/
783 /*                          DeleteDataSource()                          */
784 /************************************************************************/
785 
DeleteDataSource(const char * pszDataSource)786 OGRErr FGdbDriver::DeleteDataSource( const char *pszDataSource )
787 {
788     CPLMutexHolderD(&hMutex);
789 
790     std::wstring wstr = StringToWString(pszDataSource);
791 
792     long hr = 0;
793 
794     if( S_OK != (hr = ::DeleteGeodatabase(wstr)) )
795     {
796         GDBErr(hr, "Failed to delete Geodatabase");
797         return OGRERR_FAILURE;
798     }
799 
800     return OGRERR_NONE;
801 }
802 
803 /***********************************************************************/
804 /*                       RegisterOGRFileGDB()                          */
805 /***********************************************************************/
806 
RegisterOGRFileGDB()807 void RegisterOGRFileGDB()
808 
809 {
810     if (! GDAL_CHECK_VERSION("OGR FGDB"))
811         return;
812 
813     OGRSFDriver* poDriver = new FGdbDriver;
814     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
815                                 "ESRI FileGDB" );
816     poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "gdb" );
817     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "drivers/vector/filegdb.html" );
818 
819     poDriver->SetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST,
820                                "<CreationOptionList/>" );
821 
822     poDriver->SetMetadataItem( GDAL_DS_LAYER_CREATIONOPTIONLIST,
823 "<LayerCreationOptionList>"
824 "  <Option name='FEATURE_DATASET' type='string' description='FeatureDataset folder into to put the new layer'/>"
825 "  <Option name='LAYER_ALIAS' type='string' description='Alias of layer name'/>"
826 "  <Option name='GEOMETRY_NAME' type='string' description='Name of geometry column' default='SHAPE'/>"
827 "  <Option name='GEOMETRY_NULLABLE' type='boolean' description='Whether the values of the geometry column can be NULL' default='YES'/>"
828 "  <Option name='FID' type='string' description='Name of OID column' default='OBJECTID' deprecated_alias='OID_NAME'/>"
829 "  <Option name='XYTOLERANCE' type='float' description='Snapping tolerance, used for advanced ArcGIS features like network and topology rules, on 2D coordinates, in the units of the CRS'/>"
830 "  <Option name='ZTOLERANCE' type='float' description='Snapping tolerance, used for advanced ArcGIS features like network and topology rules, on Z coordinates, in the units of the CRS'/>"
831 "  <Option name='XORIGIN' type='float' description='X origin of the coordinate precision grid'/>"
832 "  <Option name='YORIGIN' type='float' description='Y origin of the coordinate precision grid'/>"
833 "  <Option name='ZORIGIN' type='float' description='Z origin of the coordinate precision grid'/>"
834 "  <Option name='XYSCALE' type='float' description='X,Y scale of the coordinate precision grid'/>"
835 "  <Option name='ZSCALE' type='float' description='Z scale of the coordinate precision grid'/>"
836 "  <Option name='XML_DEFINITION' type='string' description='XML definition to create the new table. The root node of such a XML definition must be a &lt;esri:DataElement&gt; element conformant to FileGDBAPI.xsd'/>"
837 "  <Option name='CREATE_MULTIPATCH' type='boolean' description='Whether to write geometries of layers of type MultiPolygon as MultiPatch' default='NO'/>"
838 "  <Option name='COLUMN_TYPES' type='string' description='A list of strings of format field_name=fgdb_filed_type (separated by comma) to force the FileGDB column type of fields to be created'/>"
839 "  <Option name='CONFIGURATION_KEYWORD' type='string-select' description='Customize how data is stored. By default text in UTF-8 and data up to 1TB'>"
840 "    <Value>DEFAULTS</Value>"
841 "    <Value>TEXT_UTF16</Value>"
842 "    <Value>MAX_FILE_SIZE_4GB</Value>"
843 "    <Value>MAX_FILE_SIZE_256TB</Value>"
844 "    <Value>GEOMETRY_OUTOFLINE</Value>"
845 "    <Value>BLOB_OUTOFLINE</Value>"
846 "    <Value>GEOMETRY_AND_BLOB_OUTOFLINE</Value>"
847 "  </Option>"
848 "</LayerCreationOptionList>");
849 
850     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATATYPES,
851                                "Integer Real String Date DateTime Binary" );
852     poDriver->SetMetadataItem( GDAL_DMD_CREATIONFIELDDATASUBTYPES, "Int16 Float32" );
853     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_FIELDS, "YES" );
854     poDriver->SetMetadataItem( GDAL_DCAP_DEFAULT_FIELDS, "YES" );
855     poDriver->SetMetadataItem( GDAL_DCAP_NOTNULL_GEOMFIELDS, "YES" );
856 
857     OGRSFDriverRegistrar::GetRegistrar()->RegisterDriver(poDriver);
858 }
859