LCOV - code coverage report
Current view: top level - ogr/ogrsf_frmts/filegdb - FGdbDriver.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 130 408 31.9 %
Date: 2025-01-18 12:42:00 Functions: 9 12 75.0 %

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

Generated by: LCOV version 1.14