/******************************************************************************
 *
 * Project:  GDAL Core
 * Purpose:  Implementation of GDALDriver class (and C wrappers)
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 1998, 2000, Frank Warmerdam
 * Copyright (c) 2007-2014, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "cpl_port.h"
#include "gdal.h"
#include "gdal_priv.h"
#include "gdal_rat.h"

#include <cerrno>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>

#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_minixml.h"
#include "cpl_progress.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "ogr_core.h"
#include "ogrsf_frmts.h"

CPL_CVSID("$Id: gdaldriver.cpp 74cca99362f3810b39913ba1b7ec423d59f30940 2018-09-20 21:08:22 +0200 Even Rouault $")

CPL_C_START
// TODO(schwehr): Why is this not in a header?
const char* GDALClientDatasetGetFilename( const char* pszFilename );
CPL_C_END

/************************************************************************/
/*                             GDALDriver()                             */
/************************************************************************/

GDALDriver::GDALDriver() :
    pfnOpen(nullptr),
    pfnCreate(nullptr),
    pfnDelete(nullptr),
    pfnCreateCopy(nullptr),
    pDriverData(nullptr),
    pfnUnloadDriver(nullptr),
    pfnIdentify(nullptr),
    pfnRename(nullptr),
    pfnCopyFiles(nullptr),
    pfnOpenWithDriverArg(nullptr),
    pfnCreateVectorOnly(nullptr),
    pfnDeleteDataSource(nullptr)
{}

/************************************************************************/
/*                            ~GDALDriver()                             */
/************************************************************************/

GDALDriver::~GDALDriver()

{
    if( pfnUnloadDriver != nullptr )
        pfnUnloadDriver( this );
}

/************************************************************************/
/*                         GDALCreateDriver()                           */
/************************************************************************/

/**
 * \brief Create a GDALDriver.
 *
 * Creates a driver in the GDAL heap.
 */

GDALDriverH CPL_STDCALL GDALCreateDriver()
{
    return new GDALDriver();
}

/************************************************************************/
/*                         GDALDestroyDriver()                          */
/************************************************************************/

/**
 * \brief Destroy a GDALDriver.
 *
 * This is roughly equivalent to deleting the driver, but is guaranteed
 * to take place in the GDAL heap.  It is important this that function
 * not be called on a driver that is registered with the GDALDriverManager.
 *
 * @param hDriver the driver to destroy.
 */

void CPL_STDCALL GDALDestroyDriver( GDALDriverH hDriver )

{
    if( hDriver != nullptr )
        delete GDALDriver::FromHandle(hDriver);
}

/************************************************************************/
/*                               Create()                               */
/************************************************************************/

/**
 * \brief Create a new dataset with this driver.
 *
 * What argument values are legal for particular drivers is driver specific,
 * and there is no way to query in advance to establish legal values.
 *
 * That function will try to validate the creation option list passed to the
 * driver with the GDALValidateCreationOptions() method. This check can be
 * disabled by defining the configuration option
 * GDAL_VALIDATE_CREATION_OPTIONS=NO.
 *
 * After you have finished working with the returned dataset, it is
 * <b>required</b> to close it with GDALClose(). This does not only close the
 * file handle, but also ensures that all the data and metadata has been written
 * to the dataset (GDALFlushCache() is not sufficient for that purpose).
 *
 * In some situations, the new dataset can be created in another process through
 * the \ref gdal_api_proxy mechanism.
 *
 * In GDAL 2, the arguments nXSize, nYSize and nBands can be passed to 0 when
 * creating a vector-only dataset for a compatible driver.
 *
 * Equivalent of the C function GDALCreate().
 *
 * @param pszFilename the name of the dataset to create.  UTF-8 encoded.
 * @param nXSize width of created raster in pixels.
 * @param nYSize height of created raster in pixels.
 * @param nBands number of bands.
 * @param eType type of raster.
 * @param papszOptions list of driver specific control parameters.
 * The APPEND_SUBDATASET=YES option can be
 * specified to avoid prior destruction of existing dataset.
 *
 * @return NULL on failure, or a new GDALDataset.
 */

GDALDataset * GDALDriver::Create( const char * pszFilename,
                                  int nXSize, int nYSize, int nBands,
                                  GDALDataType eType, char ** papszOptions )

{
/* -------------------------------------------------------------------- */
/*      Does this format support creation.                              */
/* -------------------------------------------------------------------- */
    if( pfnCreate == nullptr && pfnCreateVectorOnly == nullptr )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "GDALDriver::Create() ... no create method implemented"
                  " for this format." );

        return nullptr;
    }
/* -------------------------------------------------------------------- */
/*      Do some rudimentary argument checking.                          */
/* -------------------------------------------------------------------- */
    if( nBands < 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Attempt to create dataset with %d bands is illegal,"
                  "Must be >= 0.",
                  nBands );
        return nullptr;
    }

    if( GetMetadataItem(GDAL_DCAP_RASTER) != nullptr &&
        GetMetadataItem(GDAL_DCAP_VECTOR) == nullptr &&
        (nXSize < 1 || nYSize < 1) )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Attempt to create %dx%d dataset is illegal,"
                  "sizes must be larger than zero.",
                  nXSize, nYSize );
        return nullptr;
    }

    const char* pszClientFilename = GDALClientDatasetGetFilename(pszFilename);
    if( pszClientFilename != nullptr && !EQUAL(GetDescription(), "MEM") &&
        !EQUAL(GetDescription(), "VRT")  )
    {
        GDALDriver* poAPIPROXYDriver = GDALGetAPIPROXYDriver();
        if( poAPIPROXYDriver != this )
        {
            if( poAPIPROXYDriver == nullptr ||
                poAPIPROXYDriver->pfnCreate == nullptr )
                return nullptr;
            char** papszOptionsDup = CSLDuplicate(papszOptions);
            papszOptionsDup = CSLAddNameValue(papszOptionsDup, "SERVER_DRIVER",
                                              GetDescription());
            GDALDataset* poDstDS = poAPIPROXYDriver->pfnCreate(
                pszClientFilename, nXSize, nYSize, nBands,
                eType, papszOptionsDup );

            CSLDestroy(papszOptionsDup);

            if( poDstDS != nullptr )
            {
                if( poDstDS->GetDescription() == nullptr
                    || strlen(poDstDS->GetDescription()) == 0 )
                    poDstDS->SetDescription( pszFilename );

                if( poDstDS->poDriver == nullptr )
                    poDstDS->poDriver = poAPIPROXYDriver;
            }

            if( poDstDS != nullptr || CPLGetLastErrorNo() != CPLE_NotSupported )
                return poDstDS;
        }
    }

/* -------------------------------------------------------------------- */
/*      Make sure we cleanup if there is an existing dataset of this    */
/*      name.  But even if that seems to fail we will continue since    */
/*      it might just be a corrupt file or something.                   */
/* -------------------------------------------------------------------- */
    if( !CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false) )
    {
        // Someone issuing Create("foo.tif") on a
        // memory driver doesn't expect files with those names to be deleted
        // on a file system...
        // This is somewhat messy. Ideally there should be a way for the
        // driver to overload the default behaviour
        if( !EQUAL(GetDescription(), "MEM") &&
            !EQUAL(GetDescription(), "Memory") )
        {
            QuietDelete( pszFilename );
        }
    }

/* -------------------------------------------------------------------- */
/*      Validate creation options.                                      */
/* -------------------------------------------------------------------- */
    if( CPLTestBool(CPLGetConfigOption("GDAL_VALIDATE_CREATION_OPTIONS",
                                       "YES")) )
        GDALValidateCreationOptions( this, papszOptions );

/* -------------------------------------------------------------------- */
/*      Proceed with creation.                                          */
/* -------------------------------------------------------------------- */
    CPLDebug( "GDAL", "GDALDriver::Create(%s,%s,%d,%d,%d,%s,%p)",
              GetDescription(), pszFilename, nXSize, nYSize, nBands,
              GDALGetDataTypeName( eType ),
              papszOptions );

    GDALDataset *poDS = nullptr;
    if( pfnCreate != nullptr )
    {
        poDS = pfnCreate( pszFilename, nXSize, nYSize, nBands, eType,
                          papszOptions );
    }
    else if( nBands < 1 )
    {
        poDS = pfnCreateVectorOnly( this, pszFilename, papszOptions );
    }

    if( poDS != nullptr )
    {
        if( poDS->GetDescription() == nullptr
            || strlen(poDS->GetDescription()) == 0 )
            poDS->SetDescription( pszFilename );

        if( poDS->poDriver == nullptr )
            poDS->poDriver = this;

        poDS->AddToDatasetOpenList();
    }

    return poDS;
}

/************************************************************************/
/*                             GDALCreate()                             */
/************************************************************************/

/**
 * \brief Create a new dataset with this driver.
 *
 * @see GDALDriver::Create()
 */

GDALDatasetH CPL_DLL CPL_STDCALL
GDALCreate( GDALDriverH hDriver, const char * pszFilename,
            int nXSize, int nYSize, int nBands, GDALDataType eBandType,
            CSLConstList papszOptions )

{
    VALIDATE_POINTER1( hDriver, "GDALCreate", nullptr );

    return
        GDALDriver::FromHandle(hDriver)->Create( pszFilename,
                                                    nXSize, nYSize, nBands,
                                                    eBandType,
                                                    const_cast<char**>(papszOptions) );
}

/************************************************************************/
/*                          DefaultCopyMasks()                          */
/************************************************************************/

//! @cond Doxygen_Suppress
CPLErr GDALDriver::DefaultCopyMasks( GDALDataset *poSrcDS,
                                     GDALDataset *poDstDS,
                                     int bStrict )

{
    return DefaultCopyMasks(poSrcDS, poDstDS, bStrict,
                            nullptr, nullptr, nullptr);
}

CPLErr GDALDriver::DefaultCopyMasks( GDALDataset *poSrcDS,
                                     GDALDataset *poDstDS,
                                     int bStrict,
                                     CSLConstList /*papszOptions*/,
                                     GDALProgressFunc pfnProgress,
                                     void * pProgressData )

{
    if( pfnProgress == nullptr )
        pfnProgress = GDALDummyProgress;

    int nBands = poSrcDS->GetRasterCount();
    if (nBands == 0)
        return CE_None;

/* -------------------------------------------------------------------- */
/*      Try to copy mask if it seems appropriate.                       */
/* -------------------------------------------------------------------- */
    const char* papszOptions[2] = { "COMPRESSED=YES", nullptr };
    CPLErr eErr = CE_None;

    int nTotalBandsWithMask = 0;
    for( int iBand = 0; iBand < nBands; ++iBand )
    {
        GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand( iBand+1 );

        int nMaskFlags = poSrcBand->GetMaskFlags();
        if( !(nMaskFlags &
                 (GMF_ALL_VALID|GMF_PER_DATASET|GMF_ALPHA|GMF_NODATA) ) )
        {
            nTotalBandsWithMask ++;
        }
    }

    int iBandWithMask = 0;
    for( int iBand = 0;
         eErr == CE_None && iBand < nBands;
         ++iBand )
    {
        GDALRasterBand *poSrcBand = poSrcDS->GetRasterBand( iBand+1 );

        int nMaskFlags = poSrcBand->GetMaskFlags();
        if( eErr == CE_None
            && !(nMaskFlags &
                 (GMF_ALL_VALID|GMF_PER_DATASET|GMF_ALPHA|GMF_NODATA) ) )
        {
            GDALRasterBand *poDstBand = poDstDS->GetRasterBand( iBand+1 );
            if (poDstBand != nullptr)
            {
                eErr = poDstBand->CreateMaskBand( nMaskFlags );
                if( eErr == CE_None )
                {
                    void* pScaledData = GDALCreateScaledProgress(
                        double(iBandWithMask) / nTotalBandsWithMask,
                        double(iBandWithMask + 1) / nTotalBandsWithMask,
                        pfnProgress, pProgressData );
                    eErr = GDALRasterBandCopyWholeRaster(
                        poSrcBand->GetMaskBand(),
                        poDstBand->GetMaskBand(),
                        papszOptions,
                        GDALScaledProgress, pScaledData);
                    GDALDestroyScaledProgress(pScaledData);
                }
                else if( !bStrict )
                {
                    eErr = CE_None;
                }
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Try to copy a per-dataset mask if we have one.                  */
/* -------------------------------------------------------------------- */
    const int nMaskFlags = poSrcDS->GetRasterBand(1)->GetMaskFlags();
    if( eErr == CE_None
        && !(nMaskFlags & (GMF_ALL_VALID|GMF_ALPHA|GMF_NODATA) )
        && (nMaskFlags & GMF_PER_DATASET) )
    {
        eErr = poDstDS->CreateMaskBand( nMaskFlags );
        if( eErr == CE_None )
        {
            eErr = GDALRasterBandCopyWholeRaster(
                poSrcDS->GetRasterBand(1)->GetMaskBand(),
                poDstDS->GetRasterBand(1)->GetMaskBand(),
                papszOptions,
                pfnProgress, pProgressData);
        }
        else if( !bStrict )
        {
            eErr = CE_None;
        }
    }

    return eErr;
}

/************************************************************************/
/*                         DefaultCreateCopy()                          */
/************************************************************************/

GDALDataset *GDALDriver::DefaultCreateCopy( const char * pszFilename,
                                            GDALDataset * poSrcDS,
                                            int bStrict, char ** papszOptions,
                                            GDALProgressFunc pfnProgress,
                                            void * pProgressData )

{
    if( pfnProgress == nullptr )
        pfnProgress = GDALDummyProgress;

    CPLErrorReset();

/* -------------------------------------------------------------------- */
/*      Validate that we can create the output as requested.            */
/* -------------------------------------------------------------------- */
    const int nXSize = poSrcDS->GetRasterXSize();
    const int nYSize = poSrcDS->GetRasterYSize();
    const int nBands = poSrcDS->GetRasterCount();

    CPLDebug( "GDAL", "Using default GDALDriver::CreateCopy implementation." );

    const int nLayerCount = poSrcDS->GetLayerCount();
    if( nBands == 0 && nLayerCount == 0 &&
        GetMetadataItem(GDAL_DCAP_VECTOR) == nullptr )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "GDALDriver::DefaultCreateCopy does not support zero band" );
        return nullptr;
    }
    if( poSrcDS->GetDriver() != nullptr &&
        poSrcDS->GetDriver()->GetMetadataItem(GDAL_DCAP_RASTER) != nullptr &&
        poSrcDS->GetDriver()->GetMetadataItem(GDAL_DCAP_VECTOR) == nullptr &&
        GetMetadataItem(GDAL_DCAP_RASTER) == nullptr &&
        GetMetadataItem(GDAL_DCAP_VECTOR) != nullptr )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Source driver is raster-only whereas output driver is "
                  "vector-only" );
        return nullptr;
    }
    else if( poSrcDS->GetDriver() != nullptr &&
        poSrcDS->GetDriver()->GetMetadataItem(GDAL_DCAP_RASTER) == nullptr &&
        poSrcDS->GetDriver()->GetMetadataItem(GDAL_DCAP_VECTOR) != nullptr &&
        GetMetadataItem(GDAL_DCAP_RASTER) != nullptr &&
        GetMetadataItem(GDAL_DCAP_VECTOR) == nullptr )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Source driver is vector-only whereas output driver is "
                  "raster-only" );
        return nullptr;
    }

    if( !pfnProgress( 0.0, nullptr, pProgressData ) )
    {
        CPLError( CE_Failure, CPLE_UserInterrupt, "User terminated" );
        return nullptr;
    }

/* -------------------------------------------------------------------- */
/*      Propagate some specific structural metadata as options if it    */
/*      appears to be supported by the target driver and the caller     */
/*      didn't provide values.                                          */
/* -------------------------------------------------------------------- */
    char **papszCreateOptions = CSLDuplicate( papszOptions );
    const char * const apszOptItems[] = {
        "NBITS", "IMAGE_STRUCTURE",
        "PIXELTYPE", "IMAGE_STRUCTURE",
        nullptr };

    for( int iOptItem = 0;
         nBands > 0 && apszOptItems[iOptItem] != nullptr;
         iOptItem += 2 )
    {
        // does the source have this metadata item on the first band?
        const char *pszValue =
            poSrcDS->GetRasterBand(1)->GetMetadataItem(
                apszOptItems[iOptItem], apszOptItems[iOptItem+1] );

        if( pszValue == nullptr )
            continue;

        // Do not override provided value.
        if( CSLFetchNameValue( papszCreateOptions, pszValue ) != nullptr )
            continue;

        // Does this appear to be a supported creation option on this driver?
        const char *pszOptionList =
            GetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST );

        if( pszOptionList == nullptr
            || strstr(pszOptionList,apszOptItems[iOptItem]) == nullptr )
            continue;

        papszCreateOptions = CSLSetNameValue( papszCreateOptions,
                                              apszOptItems[iOptItem],
                                              pszValue );
    }

/* -------------------------------------------------------------------- */
/*      Create destination dataset.                                     */
/* -------------------------------------------------------------------- */
    GDALDataType eType = GDT_Unknown;

    if( nBands > 0 )
        eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    GDALDataset  *poDstDS = Create( pszFilename, nXSize, nYSize,
                                    nBands, eType, papszCreateOptions );

    CSLDestroy(papszCreateOptions);

    if( poDstDS == nullptr )
        return nullptr;

    int nDstBands = poDstDS->GetRasterCount();
    CPLErr eErr = CE_None;
    if( nDstBands != nBands )
    {
        if( GetMetadataItem(GDAL_DCAP_RASTER) != nullptr )
        {
            // Should not happen for a well-behaved driver.
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Output driver created only %d bands whereas %d were expected",
                nDstBands, nBands );
            eErr = CE_Failure;
        }
        nDstBands = 0;
    }

/* -------------------------------------------------------------------- */
/*      Try setting the projection and geotransform if it seems         */
/*      suitable.                                                       */
/* -------------------------------------------------------------------- */
    double adfGeoTransform[6] = {};

    if( nDstBands == 0 && !bStrict )
        CPLPushErrorHandler(CPLQuietErrorHandler);

    if( eErr == CE_None
        && poSrcDS->GetGeoTransform( adfGeoTransform ) == CE_None
        // TODO(schwehr): The default value check should be a function.
        && (adfGeoTransform[0] != 0.0
            || adfGeoTransform[1] != 1.0
            || adfGeoTransform[2] != 0.0
            || adfGeoTransform[3] != 0.0
            || adfGeoTransform[4] != 0.0
            || adfGeoTransform[5] != 1.0) )
    {
        eErr = poDstDS->SetGeoTransform( adfGeoTransform );
        if( !bStrict )
            eErr = CE_None;
    }

    if( eErr == CE_None
        && poSrcDS->GetProjectionRef() != nullptr
        && strlen(poSrcDS->GetProjectionRef()) > 0 )
    {
        eErr = poDstDS->SetProjection( poSrcDS->GetProjectionRef() );
        if( !bStrict )
            eErr = CE_None;
    }

/* -------------------------------------------------------------------- */
/*      Copy GCPs.                                                      */
/* -------------------------------------------------------------------- */
    if( poSrcDS->GetGCPCount() > 0 && eErr == CE_None )
    {
        eErr = poDstDS->SetGCPs( poSrcDS->GetGCPCount(),
                                 poSrcDS->GetGCPs(),
                                 poSrcDS->GetGCPProjection() );
        if( !bStrict )
            eErr = CE_None;
    }

    if( nDstBands == 0 && !bStrict )
        CPLPopErrorHandler();

/* -------------------------------------------------------------------- */
/*      Copy metadata.                                                  */
/* -------------------------------------------------------------------- */
    if( poSrcDS->GetMetadata() != nullptr )
        poDstDS->SetMetadata( poSrcDS->GetMetadata() );

/* -------------------------------------------------------------------- */
/*      Copy transportable special domain metadata (RPCs).  It would    */
/*      be nice to copy geolocation, but it is pretty fragile.          */
/* -------------------------------------------------------------------- */
    char **papszMD = poSrcDS->GetMetadata( "RPC" );
    if( papszMD )
        poDstDS->SetMetadata( papszMD, "RPC" );

/* -------------------------------------------------------------------- */
/*      Loop copying bands.                                             */
/* -------------------------------------------------------------------- */
    for( int iBand = 0;
         eErr == CE_None && iBand < nDstBands;
         ++iBand )
    {
        GDALRasterBand * const poSrcBand = poSrcDS->GetRasterBand( iBand+1 );
        GDALRasterBand * const poDstBand = poDstDS->GetRasterBand( iBand+1 );

/* -------------------------------------------------------------------- */
/*      Do we need to copy a colortable.                                */
/* -------------------------------------------------------------------- */
        GDALColorTable * const poCT = poSrcBand->GetColorTable();
        if( poCT != nullptr )
            poDstBand->SetColorTable( poCT );

/* -------------------------------------------------------------------- */
/*      Do we need to copy other metadata?  Most of this is             */
/*      non-critical, so lets not bother folks if it fails are we       */
/*      are not in strict mode.                                         */
/* -------------------------------------------------------------------- */
        if( !bStrict )
            CPLPushErrorHandler( CPLQuietErrorHandler );

        if( strlen(poSrcBand->GetDescription()) > 0 )
            poDstBand->SetDescription( poSrcBand->GetDescription() );

        if( CSLCount(poSrcBand->GetMetadata()) > 0 )
            poDstBand->SetMetadata( poSrcBand->GetMetadata() );

        int bSuccess = FALSE;
        double dfValue = poSrcBand->GetOffset( &bSuccess );
        if( bSuccess && dfValue != 0.0 )
            poDstBand->SetOffset( dfValue );

        dfValue = poSrcBand->GetScale( &bSuccess );
        if( bSuccess && dfValue != 1.0 )
            poDstBand->SetScale( dfValue );

        dfValue = poSrcBand->GetNoDataValue( &bSuccess );
        if( bSuccess )
            poDstBand->SetNoDataValue( dfValue );

        if( poSrcBand->GetColorInterpretation() != GCI_Undefined
            && poSrcBand->GetColorInterpretation()
            != poDstBand->GetColorInterpretation() )
            poDstBand->SetColorInterpretation(
                poSrcBand->GetColorInterpretation() );

        char** papszCatNames = poSrcBand->GetCategoryNames();
        if (nullptr != papszCatNames)
            poDstBand->SetCategoryNames( papszCatNames );

        // Only copy RAT if it is of reasonable size to fit in memory
        GDALRasterAttributeTable* poRAT = poSrcBand->GetDefaultRAT();
        if( poRAT != nullptr &&
            static_cast<GIntBig>(poRAT->GetColumnCount()) *
                poRAT->GetRowCount() < 1024 * 1024 )
        {
            poDstBand->SetDefaultRAT(poRAT);
        }

        if( !bStrict )
        {
            CPLPopErrorHandler();
            CPLErrorReset();
        }
        else
        {
            eErr = CPLGetLastErrorType();
        }
    }

/* -------------------------------------------------------------------- */
/*      Copy image data.                                                */
/* -------------------------------------------------------------------- */
    if( eErr == CE_None && nDstBands > 0 )
        eErr = GDALDatasetCopyWholeRaster( poSrcDS, poDstDS,
                                           nullptr, pfnProgress, pProgressData );

/* -------------------------------------------------------------------- */
/*      Should we copy some masks over?                                 */
/* -------------------------------------------------------------------- */
    if( eErr == CE_None && nDstBands > 0 )
        eErr = DefaultCopyMasks( poSrcDS, poDstDS, eErr );

/* -------------------------------------------------------------------- */
/*      Copy vector layers                                              */
/* -------------------------------------------------------------------- */
    if( eErr == CE_None )
    {
        if( nLayerCount > 0 && poDstDS->TestCapability(ODsCCreateLayer) )
        {
            for( int iLayer = 0; iLayer < nLayerCount; ++iLayer )
            {
                OGRLayer *poLayer = poSrcDS->GetLayer(iLayer);

                if( poLayer == nullptr )
                    continue;

                poDstDS->CopyLayer( poLayer, poLayer->GetName(), nullptr );
            }
        }
    }

/* -------------------------------------------------------------------- */
/*      Try to cleanup the output dataset if the translation failed.    */
/* -------------------------------------------------------------------- */
    if( eErr != CE_None )
    {
        delete poDstDS;
        if( !CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false) )
        {
            // Only delete if creating a new file
            Delete( pszFilename );
        }
        return nullptr;
    }
    else
    {
        CPLErrorReset();
    }

    return poDstDS;
}
//! @endcond

/************************************************************************/
/*                             CreateCopy()                             */
/************************************************************************/

/**
 * \brief Create a copy of a dataset.
 *
 * This method will attempt to create a copy of a raster dataset with the
 * indicated filename, and in this drivers format.  Band number, size,
 * type, projection, geotransform and so forth are all to be copied from
 * the provided template dataset.
 *
 * Note that many sequential write once formats (such as JPEG and PNG) don't
 * implement the Create() method but do implement this CreateCopy() method.
 * If the driver doesn't implement CreateCopy(), but does implement Create()
 * then the default CreateCopy() mechanism built on calling Create() will
 * be used.
 * So to test if CreateCopy() is available, you can test if GDAL_DCAP_CREATECOPY
 * or GDAL_DCAP_CREATE is set in the GDAL metadata.
 *
 * It is intended that CreateCopy() will often be used with a source dataset
 * which is a virtual dataset allowing configuration of band types, and other
 * information without actually duplicating raster data (see the VRT driver).
 * This is what is done by the gdal_translate utility for example.
 *
 * That function will try to validate the creation option list passed to the
 * driver with the GDALValidateCreationOptions() method. This check can be
 * disabled by defining the configuration option
 * GDAL_VALIDATE_CREATION_OPTIONS=NO.
 *
 * After you have finished working with the returned dataset, it is
 * <b>required</b> to close it with GDALClose(). This does not only close the
 * file handle, but also ensures that all the data and metadata has been written
 * to the dataset (GDALFlushCache() is not sufficient for that purpose).
 *
 * In some situations, the new dataset can be created in another process through
 * the \ref gdal_api_proxy mechanism.
 *
 * @param pszFilename the name for the new dataset.  UTF-8 encoded.
 * @param poSrcDS the dataset being duplicated.
 * @param bStrict TRUE if the copy must be strictly equivalent, or more
 * normally FALSE indicating that the copy may adapt as needed for the
 * output format.
 * @param papszOptions additional format dependent options controlling
 * creation of the output file. The APPEND_SUBDATASET=YES option can be
 * specified to avoid prior destruction of existing dataset.
 * @param pfnProgress a function to be used to report progress of the copy.
 * @param pProgressData application data passed into progress function.
 *
 * @return a pointer to the newly created dataset (may be read-only access).
 */

GDALDataset *GDALDriver::CreateCopy( const char * pszFilename,
                                     GDALDataset * poSrcDS,
                                     int bStrict, char ** papszOptions,
                                     GDALProgressFunc pfnProgress,
                                     void * pProgressData )

{
    if( pfnProgress == nullptr )
        pfnProgress = GDALDummyProgress;

    const char* pszClientFilename = GDALClientDatasetGetFilename(pszFilename);
    if( pszClientFilename != nullptr && !EQUAL(GetDescription(), "MEM") &&
        !EQUAL(GetDescription(), "VRT") )
    {
        GDALDriver* poAPIPROXYDriver = GDALGetAPIPROXYDriver();
        if( poAPIPROXYDriver != this )
        {
            if( poAPIPROXYDriver->pfnCreateCopy == nullptr )
                return nullptr;
            char** papszOptionsDup = CSLDuplicate(papszOptions);
            papszOptionsDup = CSLAddNameValue(papszOptionsDup, "SERVER_DRIVER",
                                              GetDescription());
            GDALDataset* const poDstDS = poAPIPROXYDriver->pfnCreateCopy(
                pszClientFilename, poSrcDS, bStrict, papszOptionsDup,
                pfnProgress, pProgressData);
            if( poDstDS != nullptr )
            {
                if( poDstDS->GetDescription() == nullptr
                    || strlen(poDstDS->GetDescription()) == 0 )
                    poDstDS->SetDescription( pszFilename );

                if( poDstDS->poDriver == nullptr )
                    poDstDS->poDriver = poAPIPROXYDriver;
            }

            CSLDestroy(papszOptionsDup);
            if( poDstDS != nullptr || CPLGetLastErrorNo() != CPLE_NotSupported )
                return poDstDS;
        }
    }

/* -------------------------------------------------------------------- */
/*      Make sure we cleanup if there is an existing dataset of this    */
/*      name.  But even if that seems to fail we will continue since    */
/*      it might just be a corrupt file or something.                   */
/* -------------------------------------------------------------------- */
    const bool bAppendSubdataset =
        CPLFetchBool(const_cast<const char **>(papszOptions),
                     "APPEND_SUBDATASET", false);
    if( !bAppendSubdataset &&
        CPLFetchBool(const_cast<const char **>(papszOptions),
                     "QUIET_DELETE_ON_CREATE_COPY", true) )
    {
        // Someone issuing CreateCopy("foo.tif") on a
        // memory driver doesn't expect files with those names to be deleted
        // on a file system...
        // This is somewhat messy. Ideally there should be a way for the
        // driver to overload the default behaviour
        if( !EQUAL(GetDescription(), "MEM") &&
            !EQUAL(GetDescription(), "Memory") )
        {
            QuietDelete( pszFilename );
        }
    }

    char** papszOptionsToDelete = nullptr;
    int iIdxQuietDeleteOnCreateCopy =
        CSLPartialFindString(papszOptions, "QUIET_DELETE_ON_CREATE_COPY=");
    if( iIdxQuietDeleteOnCreateCopy >= 0 )
    {
        if( papszOptionsToDelete == nullptr )
            papszOptionsToDelete = CSLDuplicate(papszOptions);
        papszOptions =
            CSLRemoveStrings(papszOptionsToDelete, iIdxQuietDeleteOnCreateCopy,
                             1, nullptr);
        papszOptionsToDelete = papszOptions;
    }

/* -------------------------------------------------------------------- */
/*      If _INTERNAL_DATASET=YES, the returned dataset will not be      */
/*      registered in the global list of open datasets.                 */
/* -------------------------------------------------------------------- */
    const int iIdxInternalDataset =
        CSLPartialFindString(papszOptions, "_INTERNAL_DATASET=");
    bool bInternalDataset = false;
    if( iIdxInternalDataset >= 0 )
    {
        bInternalDataset =
            CPLFetchBool(const_cast<const char **>(papszOptions),
                         "_INTERNAL_DATASET", false);
        if( papszOptionsToDelete == nullptr )
            papszOptionsToDelete = CSLDuplicate(papszOptions);
        papszOptions =
            CSLRemoveStrings(papszOptionsToDelete, iIdxInternalDataset,
                             1, nullptr);
        papszOptionsToDelete = papszOptions;
    }

/* -------------------------------------------------------------------- */
/*      Validate creation options.                                      */
/* -------------------------------------------------------------------- */
    if( CPLTestBool(
            CPLGetConfigOption("GDAL_VALIDATE_CREATION_OPTIONS", "YES") ) )
        GDALValidateCreationOptions( this, papszOptions );

/* -------------------------------------------------------------------- */
/*      Advise the source raster that we are going to read it completely */
/* -------------------------------------------------------------------- */

    const int nXSize = poSrcDS->GetRasterXSize();
    const int nYSize = poSrcDS->GetRasterYSize();
    const int nBandCount = poSrcDS->GetRasterCount();
    GDALDataType eDT = GDT_Unknown;
    if( nBandCount > 0 )
    {
        GDALRasterBand* poSrcBand = poSrcDS->GetRasterBand(1);
        if( poSrcBand )
            eDT = poSrcBand->GetRasterDataType();
    }
    poSrcDS->AdviseRead( 0, 0, nXSize, nYSize, nXSize, nYSize, eDT,
                         nBandCount, nullptr, nullptr );

/* -------------------------------------------------------------------- */
/*      If the format provides a CreateCopy() method use that,          */
/*      otherwise fallback to the internal implementation using the     */
/*      Create() method.                                                */
/* -------------------------------------------------------------------- */
    GDALDataset *poDstDS = nullptr;
    if( pfnCreateCopy != nullptr &&
        !CPLTestBool(CPLGetConfigOption("GDAL_DEFAULT_CREATE_COPY", "NO")) )
    {
        poDstDS = pfnCreateCopy( pszFilename, poSrcDS, bStrict, papszOptions,
                                 pfnProgress, pProgressData );
        if( poDstDS != nullptr )
        {
            if( poDstDS->GetDescription() == nullptr
                || strlen(poDstDS->GetDescription()) == 0 )
                poDstDS->SetDescription( pszFilename );

            if( poDstDS->poDriver == nullptr )
                poDstDS->poDriver = this;

            if( !bInternalDataset )
                poDstDS->AddToDatasetOpenList();
        }
    }
    else
    {
        poDstDS = DefaultCreateCopy( pszFilename, poSrcDS, bStrict,
                                  papszOptions, pfnProgress, pProgressData );
    }

    CSLDestroy(papszOptionsToDelete);
    return poDstDS;
}

/************************************************************************/
/*                           GDALCreateCopy()                           */
/************************************************************************/

/**
 * \brief Create a copy of a dataset.
 *
 * @see GDALDriver::CreateCopy()
 */

GDALDatasetH CPL_STDCALL GDALCreateCopy( GDALDriverH hDriver,
                                         const char * pszFilename,
                                         GDALDatasetH hSrcDS,
                                         int bStrict, CSLConstList papszOptions,
                                         GDALProgressFunc pfnProgress,
                                         void * pProgressData )

{
    VALIDATE_POINTER1( hDriver, "GDALCreateCopy", nullptr );
    VALIDATE_POINTER1( hSrcDS, "GDALCreateCopy", nullptr );

    return GDALDriver::FromHandle(hDriver)->
        CreateCopy( pszFilename, GDALDataset::FromHandle(hSrcDS),
                    bStrict, const_cast<char**>(papszOptions),
                    pfnProgress, pProgressData );
}

/************************************************************************/
/*                            QuietDelete()                             */
/************************************************************************/

/**
 * \brief Delete dataset if found.
 *
 * This is a helper method primarily used by Create() and
 * CreateCopy() to predelete any dataset of the name soon to be
 * created.  It will attempt to delete the named dataset if
 * one is found, otherwise it does nothing.  An error is only
 * returned if the dataset is found but the delete fails.
 *
 * This is a static method and it doesn't matter what driver instance
 * it is invoked on.  It will attempt to discover the correct driver
 * using Identify().
 *
 * @param pszName the dataset name to try and delete.
 * @return CE_None if the dataset does not exist, or is deleted without issues.
 */

CPLErr GDALDriver::QuietDelete( const char *pszName )

{
    VSIStatBufL sStat;
    const bool bExists =
        VSIStatExL(pszName, &sStat,
                   VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0;

#ifdef S_ISFIFO
    if( bExists && S_ISFIFO(sStat.st_mode) )
        return CE_None;
#endif

    if( bExists &&
        VSI_ISDIR(sStat.st_mode) )
    {
        // It is not desirable to remove directories quietly.  Necessary to
        // avoid ogr_mitab_12 to destroy file created at ogr_mitab_7.
        return CE_None;
    }

    CPLPushErrorHandler(CPLQuietErrorHandler);
    GDALDriver * const poDriver =
        GDALDriver::FromHandle( GDALIdentifyDriver( pszName, nullptr ) );
    CPLPopErrorHandler();

    if( poDriver == nullptr )
        return CE_None;

    CPLDebug( "GDAL", "QuietDelete(%s) invoking Delete()", pszName );

    const bool bQuiet =
        !bExists && poDriver->pfnDelete == nullptr &&
        poDriver->pfnDeleteDataSource == nullptr;
    if( bQuiet )
        CPLPushErrorHandler(CPLQuietErrorHandler);
    CPLErr eErr = poDriver->Delete( pszName );
    if( bQuiet )
    {
        CPLPopErrorHandler();
        CPLErrorReset();
        eErr = CE_None;
    }
    return eErr;
}

/************************************************************************/
/*                               Delete()                               */
/************************************************************************/

/**
 * \brief Delete named dataset.
 *
 * The driver will attempt to delete the named dataset in a driver specific
 * fashion.  Full featured drivers will delete all associated files,
 * database objects, or whatever is appropriate.  The default behaviour when
 * no driver specific behaviour is provided is to attempt to delete all the
 * files that are returned by GDALGetFileList() on the dataset handle.
 *
 * It is unwise to have open dataset handles on this dataset when it is
 * deleted.
 *
 * Equivalent of the C function GDALDeleteDataset().
 *
 * @param pszFilename name of dataset to delete.
 *
 * @return CE_None on success, or CE_Failure if the operation fails.
 */

CPLErr GDALDriver::Delete( const char * pszFilename )

{
    if( pfnDelete != nullptr )
        return pfnDelete( pszFilename );
    else if( pfnDeleteDataSource != nullptr )
        return pfnDeleteDataSource( this, pszFilename );

/* -------------------------------------------------------------------- */
/*      Collect file list.                                              */
/* -------------------------------------------------------------------- */
    GDALDatasetH hDS = GDALOpenEx(pszFilename, 0, nullptr, nullptr, nullptr);

    if( hDS == nullptr )
    {
        if( CPLGetLastErrorNo() == 0 )
            CPLError( CE_Failure, CPLE_OpenFailed,
                      "Unable to open %s to obtain file list.", pszFilename );

        return CE_Failure;
    }

    char **papszFileList = GDALGetFileList( hDS );

    GDALClose( hDS );
    hDS = nullptr;

    if( CSLCount( papszFileList ) == 0 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Unable to determine files associated with %s, "
                  "delete fails.", pszFilename );
        CSLDestroy( papszFileList );
        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Delete all files.                                               */
/* -------------------------------------------------------------------- */
    CPLErr eErr = CE_None;
    for( int i = 0; papszFileList[i] != nullptr; ++i )
    {
        if( VSIUnlink( papszFileList[i] ) != 0 )
        {
            CPLError( CE_Failure, CPLE_AppDefined,
                      "Deleting %s failed:\n%s",
                      papszFileList[i],
                      VSIStrerror(errno) );
            eErr = CE_Failure;
        }
    }

    CSLDestroy( papszFileList );

    return eErr;
}

/************************************************************************/
/*                         GDALDeleteDataset()                          */
/************************************************************************/

/**
 * \brief Delete named dataset.
 *
 * @see GDALDriver::Delete()
 */

CPLErr CPL_STDCALL GDALDeleteDataset( GDALDriverH hDriver,
                                      const char * pszFilename )

{
    if( hDriver == nullptr )
        hDriver = GDALIdentifyDriver( pszFilename, nullptr );

    if( hDriver == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "No identifiable driver for %s.",
                  pszFilename );
        return CE_Failure;
    }

    return GDALDriver::FromHandle(hDriver)->Delete( pszFilename );
}

/************************************************************************/
/*                           DefaultRename()                            */
/*                                                                      */
/*      The generic implementation based on the file list used when     */
/*      there is no format specific implementation.                     */
/************************************************************************/

//! @cond Doxygen_Suppress
CPLErr GDALDriver::DefaultRename( const char * pszNewName,
                                  const char *pszOldName )

{
/* -------------------------------------------------------------------- */
/*      Collect file list.                                              */
/* -------------------------------------------------------------------- */
    GDALDatasetH hDS = GDALOpen(pszOldName, GA_ReadOnly);

    if( hDS == nullptr )
    {
        if( CPLGetLastErrorNo() == 0 )
            CPLError( CE_Failure, CPLE_OpenFailed,
                      "Unable to open %s to obtain file list.", pszOldName );

        return CE_Failure;
    }

    char **papszFileList = GDALGetFileList( hDS );

    GDALClose( hDS );

    if( CSLCount( papszFileList ) == 0 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Unable to determine files associated with %s,\n"
                  "rename fails.", pszOldName );

        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Produce a list of new filenames that correspond to the old      */
/*      names.                                                          */
/* -------------------------------------------------------------------- */
    CPLErr eErr = CE_None;
    char **papszNewFileList =
        CPLCorrespondingPaths( pszOldName, pszNewName, papszFileList );

    if( papszNewFileList == nullptr )
        return CE_Failure;

    for( int i = 0; papszFileList[i] != nullptr; ++i )
    {
        if( CPLMoveFile( papszNewFileList[i], papszFileList[i] ) != 0 )
        {
            eErr = CE_Failure;
            // Try to put the ones we moved back.
            for( --i; i >= 0; i-- )
                CPLMoveFile( papszFileList[i], papszNewFileList[i] );
            break;
        }
    }

    CSLDestroy( papszNewFileList );
    CSLDestroy( papszFileList );

    return eErr;
}
//! @endcond

/************************************************************************/
/*                               Rename()                               */
/************************************************************************/

/**
 * \brief Rename a dataset.
 *
 * Rename a dataset. This may including moving the dataset to a new directory
 * or even a new filesystem.
 *
 * It is unwise to have open dataset handles on this dataset when it is
 * being renamed.
 *
 * Equivalent of the C function GDALRenameDataset().
 *
 * @param pszNewName new name for the dataset.
 * @param pszOldName old name for the dataset.
 *
 * @return CE_None on success, or CE_Failure if the operation fails.
 */

CPLErr GDALDriver::Rename( const char * pszNewName, const char *pszOldName )

{
    if( pfnRename != nullptr )
        return pfnRename( pszNewName, pszOldName );

    return DefaultRename( pszNewName, pszOldName );
}

/************************************************************************/
/*                         GDALRenameDataset()                          */
/************************************************************************/

/**
 * \brief Rename a dataset.
 *
 * @see GDALDriver::Rename()
 */

CPLErr CPL_STDCALL GDALRenameDataset( GDALDriverH hDriver,
                                      const char * pszNewName,
                                      const char * pszOldName )

{
    if( hDriver == nullptr )
        hDriver = GDALIdentifyDriver( pszOldName, nullptr );

    if( hDriver == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "No identifiable driver for %s.",
                  pszOldName );
        return CE_Failure;
    }

    return GDALDriver::FromHandle(hDriver)->Rename( pszNewName, pszOldName );
}

/************************************************************************/
/*                          DefaultCopyFiles()                          */
/*                                                                      */
/*      The default implementation based on file lists used when        */
/*      there is no format specific implementation.                     */
/************************************************************************/

//! @cond Doxygen_Suppress
CPLErr GDALDriver::DefaultCopyFiles( const char *pszNewName,
                                     const char *pszOldName )

{
/* -------------------------------------------------------------------- */
/*      Collect file list.                                              */
/* -------------------------------------------------------------------- */
    GDALDatasetH hDS = GDALOpen(pszOldName,GA_ReadOnly);

    if( hDS == nullptr )
    {
        if( CPLGetLastErrorNo() == 0 )
            CPLError( CE_Failure, CPLE_OpenFailed,
                      "Unable to open %s to obtain file list.", pszOldName );

        return CE_Failure;
    }

    char **papszFileList = GDALGetFileList( hDS );

    GDALClose( hDS );
    hDS = nullptr;

    if( CSLCount( papszFileList ) == 0 )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Unable to determine files associated with %s,\n"
                  "rename fails.", pszOldName );

        return CE_Failure;
    }

/* -------------------------------------------------------------------- */
/*      Produce a list of new filenames that correspond to the old      */
/*      names.                                                          */
/* -------------------------------------------------------------------- */
    CPLErr eErr = CE_None;
    char **papszNewFileList =
        CPLCorrespondingPaths( pszOldName, pszNewName, papszFileList );

    if( papszNewFileList == nullptr )
        return CE_Failure;

    for( int i = 0; papszFileList[i] != nullptr; ++i )
    {
        if( CPLCopyFile( papszNewFileList[i], papszFileList[i] ) != 0 )
        {
            eErr = CE_Failure;
            // Try to put the ones we moved back.
            for( --i; i >= 0; --i )
                VSIUnlink( papszNewFileList[i] );
            break;
        }
    }

    CSLDestroy( papszNewFileList );
    CSLDestroy( papszFileList );

    return eErr;
}
//! @endcond

/************************************************************************/
/*                             CopyFiles()                              */
/************************************************************************/

/**
 * \brief Copy the files of a dataset.
 *
 * Copy all the files associated with a dataset.
 *
 * Equivalent of the C function GDALCopyDatasetFiles().
 *
 * @param pszNewName new name for the dataset.
 * @param pszOldName old name for the dataset.
 *
 * @return CE_None on success, or CE_Failure if the operation fails.
 */

CPLErr GDALDriver::CopyFiles( const char *pszNewName, const char *pszOldName )

{
    if( pfnCopyFiles != nullptr )
        return pfnCopyFiles( pszNewName, pszOldName );

    return DefaultCopyFiles( pszNewName, pszOldName );
}

/************************************************************************/
/*                        GDALCopyDatasetFiles()                        */
/************************************************************************/

/**
 * \brief Copy the files of a dataset.
 *
 * @see GDALDriver::CopyFiles()
 */

CPLErr CPL_STDCALL GDALCopyDatasetFiles( GDALDriverH hDriver,
                                         const char *pszNewName,
                                         const char *pszOldName )

{
    if( hDriver == nullptr )
        hDriver = GDALIdentifyDriver( pszOldName, nullptr );

    if( hDriver == nullptr )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "No identifiable driver for %s.",
                  pszOldName );
        return CE_Failure;
    }

    return GDALDriver::FromHandle(hDriver)->
        CopyFiles( pszNewName, pszOldName );
}

/************************************************************************/
/*                       GDALGetDriverShortName()                       */
/************************************************************************/

/**
 * \brief Return the short name of a driver
 *
 * This is the string that can be
 * passed to the GDALGetDriverByName() function.
 *
 * For the GeoTIFF driver, this is "GTiff"
 *
 * @param hDriver the handle of the driver
 * @return the short name of the driver. The
 *         returned string should not be freed and is owned by the driver.
 */

const char * CPL_STDCALL GDALGetDriverShortName( GDALDriverH hDriver )

{
    VALIDATE_POINTER1( hDriver, "GDALGetDriverShortName", nullptr );

    return GDALDriver::FromHandle(hDriver)->GetDescription();
}

/************************************************************************/
/*                       GDALGetDriverLongName()                        */
/************************************************************************/

/**
 * \brief Return the long name of a driver
 *
 * For the GeoTIFF driver, this is "GeoTIFF"
 *
 * @param hDriver the handle of the driver
 * @return the long name of the driver or empty string. The
 *         returned string should not be freed and is owned by the driver.
 */

const char * CPL_STDCALL GDALGetDriverLongName( GDALDriverH hDriver )

{
    VALIDATE_POINTER1( hDriver, "GDALGetDriverLongName", nullptr );

    const char *pszLongName =
        GDALDriver::FromHandle(hDriver)->
            GetMetadataItem( GDAL_DMD_LONGNAME );

    if( pszLongName == nullptr )
        return "";

    return pszLongName;
}

/************************************************************************/
/*                       GDALGetDriverHelpTopic()                       */
/************************************************************************/

/**
 * \brief Return the URL to the help that describes the driver
 *
 * That URL is relative to the GDAL documentation directory.
 *
 * For the GeoTIFF driver, this is "frmt_gtiff.html"
 *
 * @param hDriver the handle of the driver
 * @return the URL to the help that describes the driver or NULL. The
 *         returned string should not be freed and is owned by the driver.
 */

const char * CPL_STDCALL GDALGetDriverHelpTopic( GDALDriverH hDriver )

{
    VALIDATE_POINTER1( hDriver, "GDALGetDriverHelpTopic", nullptr );

    return GDALDriver::FromHandle(hDriver)->
        GetMetadataItem( GDAL_DMD_HELPTOPIC );
}

/************************************************************************/
/*                   GDALGetDriverCreationOptionList()                  */
/************************************************************************/

/**
 * \brief Return the list of creation options of the driver
 *
 * Return the list of creation options of the driver used by Create() and
 * CreateCopy() as an XML string
 *
 * @param hDriver the handle of the driver
 * @return an XML string that describes the list of creation options or
 *         empty string. The returned string should not be freed and is
 *         owned by the driver.
 */

const char * CPL_STDCALL GDALGetDriverCreationOptionList( GDALDriverH hDriver )

{
    VALIDATE_POINTER1( hDriver, "GDALGetDriverCreationOptionList", nullptr );

    const char *pszOptionList =
        GDALDriver::FromHandle(hDriver)->
            GetMetadataItem( GDAL_DMD_CREATIONOPTIONLIST );

    if( pszOptionList == nullptr )
        return "";

    return pszOptionList;
}

/************************************************************************/
/*                   GDALValidateCreationOptions()                      */
/************************************************************************/

/**
 * \brief Validate the list of creation options that are handled by a driver
 *
 * This is a helper method primarily used by Create() and
 * CreateCopy() to validate that the passed in list of creation options
 * is compatible with the GDAL_DMD_CREATIONOPTIONLIST metadata item defined
 * by some drivers. @see GDALGetDriverCreationOptionList()
 *
 * If the GDAL_DMD_CREATIONOPTIONLIST metadata item is not defined, this
 * function will return TRUE. Otherwise it will check that the keys and values
 * in the list of creation options are compatible with the capabilities declared
 * by the GDAL_DMD_CREATIONOPTIONLIST metadata item. In case of incompatibility
 * a (non fatal) warning will be emitted and FALSE will be returned.
 *
 * @param hDriver the handle of the driver with whom the lists of creation option
 *                must be validated
 * @param papszCreationOptions the list of creation options. An array of strings,
 *                             whose last element is a NULL pointer
 * @return TRUE if the list of creation options is compatible with the Create()
 *         and CreateCopy() method of the driver, FALSE otherwise.
 */

int CPL_STDCALL GDALValidateCreationOptions( GDALDriverH hDriver,
                                             CSLConstList papszCreationOptions )
{
    VALIDATE_POINTER1( hDriver, "GDALValidateCreationOptions", FALSE );
    const char *pszOptionList =
        GDALDriver::FromHandle(hDriver)->GetMetadataItem(
            GDAL_DMD_CREATIONOPTIONLIST );
    CPLString osDriver;
    osDriver.Printf("driver %s",
                    GDALDriver::FromHandle(hDriver)->GetDescription());
    CSLConstList papszOptionsToValidate = papszCreationOptions;
    char** papszOptionsToFree = nullptr;
    if( CSLFetchNameValue( papszCreationOptions, "APPEND_SUBDATASET") )
    {
        papszOptionsToFree =
            CSLSetNameValue(CSLDuplicate(papszCreationOptions),
                            "APPEND_SUBDATASET", nullptr);
        papszOptionsToValidate = papszOptionsToFree;
    }
    const bool bRet = CPL_TO_BOOL(
        GDALValidateOptions(
            pszOptionList,
            papszOptionsToValidate,
            "creation option",
            osDriver ) );
    CSLDestroy(papszOptionsToFree);
    return bRet;
}

/************************************************************************/
/*                     GDALValidateOpenOptions()                        */
/************************************************************************/

int GDALValidateOpenOptions( GDALDriverH hDriver,
                             const char* const* papszOpenOptions)
{
    VALIDATE_POINTER1( hDriver, "GDALValidateOpenOptions", FALSE );
    const char *pszOptionList = GDALDriver::FromHandle(hDriver)->
        GetMetadataItem( GDAL_DMD_OPENOPTIONLIST );
    CPLString osDriver;
    osDriver.Printf("driver %s",
                    GDALDriver::FromHandle(hDriver)->GetDescription());
    return GDALValidateOptions( pszOptionList, papszOpenOptions,
                                "open option",
                                osDriver );
}

/************************************************************************/
/*                           GDALValidateOptions()                      */
/************************************************************************/

int GDALValidateOptions( const char* pszOptionList,
                         const char* const* papszOptionsToValidate,
                         const char* pszErrorMessageOptionType,
                         const char* pszErrorMessageContainerName)
{
    if( papszOptionsToValidate == nullptr || *papszOptionsToValidate == nullptr)
        return TRUE;
    if( pszOptionList == nullptr )
        return TRUE;

    CPLXMLNode* psNode = CPLParseXMLString(pszOptionList);
    if (psNode == nullptr)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Could not parse %s list of %s. Assuming options are valid.",
                 pszErrorMessageOptionType, pszErrorMessageContainerName);
        return TRUE;
    }

    bool bRet = true;
    while(*papszOptionsToValidate)
    {
        char* pszKey = nullptr;
        const char* pszValue = CPLParseNameValue(*papszOptionsToValidate, &pszKey);
        if (pszKey == nullptr)
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "%s '%s' is not formatted with the key=value format",
                     pszErrorMessageOptionType,
                     *papszOptionsToValidate);
            bRet = false;

            ++papszOptionsToValidate;
            continue;
        }

        if( EQUAL(pszKey, "VALIDATE_OPEN_OPTIONS") )
        {
            ++papszOptionsToValidate;
            CPLFree(pszKey);
            continue;
        }

        // Must we be forgiving in case of missing option ?
        bool bWarnIfMissingKey = true;
        if( pszKey[0] == '@' )
        {
            bWarnIfMissingKey = false;
            memmove(pszKey, pszKey + 1, strlen(pszKey+1)+1);
        }

        CPLXMLNode* psChildNode = psNode->psChild;
        while(psChildNode)
        {
            if (EQUAL(psChildNode->pszValue, "OPTION"))
            {
                const char* pszOptionName = CPLGetXMLValue(psChildNode, "name", "");
                /* For option names terminated by wildcard (NITF BLOCKA option names for example) */
                if (strlen(pszOptionName) > 0 &&
                    pszOptionName[strlen(pszOptionName) - 1] == '*' &&
                    EQUALN(pszOptionName, pszKey, strlen(pszOptionName) - 1))
                {
                    break;
                }

                /* For option names beginning by a wildcard */
                if( pszOptionName[0] == '*' &&
                    strlen(pszKey) > strlen(pszOptionName) &&
                    EQUAL( pszKey + strlen(pszKey) - strlen(pszOptionName + 1), pszOptionName + 1 ) )
                {
                    break;
                }

                if (EQUAL(pszOptionName, pszKey) )
                {
                    break;
                }
                const char* pszAlias = CPLGetXMLValue(psChildNode, "alias",
                            CPLGetXMLValue(psChildNode, "deprecated_alias", ""));
                if (EQUAL(pszAlias, pszKey) )
                {
                    CPLDebug("GDAL", "Using deprecated alias '%s'. New name is '%s'",
                             pszAlias, pszOptionName);
                    break;
                }
            }
            psChildNode = psChildNode->psNext;
        }
        if (psChildNode == nullptr)
        {
            if( bWarnIfMissingKey &&
                (!EQUAL(pszErrorMessageOptionType, "open option") ||
                 CPLFetchBool(papszOptionsToValidate,
                              "VALIDATE_OPEN_OPTIONS", true)) )
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                        "%s does not support %s %s",
                        pszErrorMessageContainerName,
                        pszErrorMessageOptionType,
                        pszKey);
                bRet = false;
            }

            CPLFree(pszKey);
            ++papszOptionsToValidate;
            continue;
        }

#ifdef DEBUG
        CPLXMLNode* psChildSubNode = psChildNode->psChild;
        while(psChildSubNode)
        {
            if( psChildSubNode->eType == CXT_Attribute )
            {
                if( !(EQUAL(psChildSubNode->pszValue, "name") ||
                      EQUAL(psChildSubNode->pszValue, "alias") ||
                      EQUAL(psChildSubNode->pszValue, "deprecated_alias") ||
                      EQUAL(psChildSubNode->pszValue, "alt_config_option") ||
                      EQUAL(psChildSubNode->pszValue, "description") ||
                      EQUAL(psChildSubNode->pszValue, "type") ||
                      EQUAL(psChildSubNode->pszValue, "min") ||
                      EQUAL(psChildSubNode->pszValue, "max") ||
                      EQUAL(psChildSubNode->pszValue, "default") ||
                      EQUAL(psChildSubNode->pszValue, "maxsize") ||
                      EQUAL(psChildSubNode->pszValue, "required") ||
                      EQUAL(psChildSubNode->pszValue, "scope")) )
                {
                    /* Driver error */
                    CPLError(CE_Warning, CPLE_NotSupported,
                             "%s : unhandled attribute '%s' for %s %s.",
                             pszErrorMessageContainerName,
                             psChildSubNode->pszValue,
                             pszKey,
                             pszErrorMessageOptionType);
                }
            }
            psChildSubNode = psChildSubNode->psNext;
        }
#endif

        const char* pszType = CPLGetXMLValue(psChildNode, "type", nullptr);
        const char* pszMin = CPLGetXMLValue(psChildNode, "min", nullptr);
        const char* pszMax = CPLGetXMLValue(psChildNode, "max", nullptr);
        if (pszType != nullptr)
        {
            if (EQUAL(pszType, "INT") || EQUAL(pszType, "INTEGER"))
            {
                const char* pszValueIter = pszValue;
                while (*pszValueIter)
                {
                    if (!((*pszValueIter >= '0' && *pszValueIter <= '9') ||
                           *pszValueIter == '+' || *pszValueIter == '-'))
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s of type int.",
                             pszValue, pszKey, pszErrorMessageOptionType);
                        bRet = false;
                        break;
                    }
                    ++pszValueIter;
                }
                if( *pszValueIter == '\0' )
                {
                    if( pszMin && atoi(pszValue) < atoi(pszMin) )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s that should be >= %s.",
                             pszValue, pszKey, pszErrorMessageOptionType, pszMin);
                        bRet = false;
                    }
                    if( pszMax && atoi(pszValue) > atoi(pszMax) )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s that should be <= %s.",
                             pszValue, pszKey, pszErrorMessageOptionType, pszMax);
                        bRet = false;
                    }
                }
            }
            else if (EQUAL(pszType, "UNSIGNED INT"))
            {
                const char* pszValueIter = pszValue;
                while (*pszValueIter)
                {
                    if (!((*pszValueIter >= '0' && *pszValueIter <= '9') ||
                           *pszValueIter == '+'))
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s of type unsigned int.",
                             pszValue, pszKey, pszErrorMessageOptionType);
                        bRet = false;
                        break;
                    }
                    ++pszValueIter;
                }
                if( *pszValueIter == '\0' )
                {
                    if( pszMin && atoi(pszValue) < atoi(pszMin) )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                            "'%s' is an unexpected value for %s %s that should be >= %s.",
                            pszValue, pszKey, pszErrorMessageOptionType, pszMin);
                        bRet = false;
                    }
                    if( pszMax && atoi(pszValue) > atoi(pszMax) )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                            "'%s' is an unexpected value for %s %s that should be <= %s.",
                            pszValue, pszKey, pszErrorMessageOptionType, pszMax);
                        bRet = false;
                    }
                }
            }
            else if (EQUAL(pszType, "FLOAT"))
            {
                char* endPtr = nullptr;
                double dfVal = CPLStrtod(pszValue, &endPtr);
                if ( !(endPtr == nullptr || *endPtr == '\0') )
                {
                    CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s of type float.",
                             pszValue, pszKey, pszErrorMessageOptionType);
                    bRet = false;
                }
                else
                {
                    if( pszMin && dfVal < CPLAtof(pszMin) )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s that should be >= %s.",
                             pszValue, pszKey, pszErrorMessageOptionType, pszMin);
                        bRet = false;
                    }
                    if( pszMax && dfVal > CPLAtof(pszMax) )
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s that should be <= %s.",
                             pszValue, pszKey, pszErrorMessageOptionType, pszMax);
                        bRet = false;
                    }
                }
            }
            else if (EQUAL(pszType, "BOOLEAN"))
            {
                if (!(EQUAL(pszValue, "ON") || EQUAL(pszValue, "TRUE") || EQUAL(pszValue, "YES") ||
                      EQUAL(pszValue, "OFF") || EQUAL(pszValue, "FALSE") || EQUAL(pszValue, "NO")))
                {
                    CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s of type boolean.",
                             pszValue, pszKey, pszErrorMessageOptionType);
                    bRet = false;
                }
            }
            else if (EQUAL(pszType, "STRING-SELECT"))
            {
                bool bMatchFound = false;
                CPLXMLNode* psStringSelect = psChildNode->psChild;
                while(psStringSelect)
                {
                    if (psStringSelect->eType == CXT_Element &&
                        EQUAL(psStringSelect->pszValue, "Value"))
                    {
                        CPLXMLNode* psOptionNode = psStringSelect->psChild;
                        while(psOptionNode)
                        {
                            if (psOptionNode->eType == CXT_Text &&
                                EQUAL(psOptionNode->pszValue, pszValue))
                            {
                                bMatchFound = true;
                                break;
                            }
                            if( psOptionNode->eType == CXT_Attribute &&
                                (EQUAL(psOptionNode->pszValue, "alias") ||
                                 EQUAL(psOptionNode->pszValue, "deprecated_alias") ) &&
                                 EQUAL(psOptionNode->psChild->pszValue, pszValue) )
                            {
                                bMatchFound = true;
                                break;
                            }
                            psOptionNode = psOptionNode->psNext;
                        }
                        if (bMatchFound)
                            break;
                    }
                    psStringSelect = psStringSelect->psNext;
                }
                if (!bMatchFound)
                {
                    CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is an unexpected value for %s %s of type string-select.",
                             pszValue, pszKey, pszErrorMessageOptionType);
                    bRet = false;
                }
            }
            else if (EQUAL(pszType, "STRING"))
            {
                const char* pszMaxSize = CPLGetXMLValue(psChildNode, "maxsize", nullptr);
                if (pszMaxSize != nullptr)
                {
                    if (static_cast<int>(strlen(pszValue)) > atoi(pszMaxSize))
                    {
                        CPLError(CE_Warning, CPLE_NotSupported,
                             "'%s' is of size %d, whereas maximum size for %s %s is %d.",
                             pszValue, static_cast<int>(strlen(pszValue)), pszKey,
                                 pszErrorMessageOptionType, atoi(pszMaxSize));
                        bRet = false;
                    }
                }
            }
            else
            {
                /* Driver error */
                CPLError(CE_Warning, CPLE_NotSupported,
                         "%s : type '%s' for %s %s is not recognized.",
                         pszErrorMessageContainerName,
                         pszType,
                         pszKey,
                         pszErrorMessageOptionType);
            }
        }
        else
        {
            /* Driver error */
            CPLError(CE_Warning, CPLE_NotSupported,
                     "%s : no type for %s %s.",
                     pszErrorMessageContainerName,
                     pszKey,
                     pszErrorMessageOptionType);
        }
        CPLFree(pszKey);
        ++papszOptionsToValidate;
    }

    CPLDestroyXMLNode(psNode);
    return bRet ? TRUE : FALSE;
}

/************************************************************************/
/*                         GDALIdentifyDriver()                         */
/************************************************************************/

/**
 * \brief Identify the driver that can open a raster file.
 *
 * This function will try to identify the driver that can open the passed file
 * name by invoking the Identify method of each registered GDALDriver in turn.
 * The first driver that successful identifies the file name will be returned.
 * If all drivers fail then NULL is returned.
 *
 * In order to reduce the need for such searches touch the operating system
 * file system machinery, it is possible to give an optional list of files.
 * This is the list of all files at the same level in the file system as the
 * target file, including the target file. The filenames will not include any
 * path components, are essentially just the output of VSIReadDir() on the
 * parent directory. If the target object does not have filesystem semantics
 * then the file list should be NULL.
 *
 * @param pszFilename the name of the file to access.  In the case of
 * exotic drivers this may not refer to a physical file, but instead contain
 * information for the driver on how to access a dataset.
 *
 * @param papszFileList an array of strings, whose last element is the NULL
 * pointer.  These strings are filenames that are auxiliary to the main
 * filename. The passed value may be NULL.
 *
 * @return A GDALDriverH handle or NULL on failure.  For C++ applications
 * this handle can be cast to a GDALDriver *.
 */

GDALDriverH CPL_STDCALL
GDALIdentifyDriver( const char * pszFilename,
                    CSLConstList papszFileList )

{
    return GDALIdentifyDriverEx( pszFilename, 0, nullptr, papszFileList );
}

/************************************************************************/
/*                         GDALIdentifyDriverEx()                       */
/************************************************************************/

/**
 * \brief Identify the driver that can open a raster file.
 *
 * This function will try to identify the driver that can open the passed file
 * name by invoking the Identify method of each registered GDALDriver in turn.
 * The first driver that successful identifies the file name will be returned.
 * If all drivers fail then NULL is returned.
 *
 * In order to reduce the need for such searches touch the operating system
 * file system machinery, it is possible to give an optional list of files.
 * This is the list of all files at the same level in the file system as the
 * target file, including the target file. The filenames will not include any
 * path components, are essentially just the output of VSIReadDir() on the
 * parent directory. If the target object does not have filesystem semantics
 * then the file list should be NULL.
 *
 * @param pszFilename the name of the file to access.  In the case of
 * exotic drivers this may not refer to a physical file, but instead contain
 * information for the driver on how to access a dataset.
 *
 * @param nIdentifyFlags a combination of GDAL_OF_RASTER for raster drivers
 * or GDAL_OF_VECTOR for vector drivers. If none of the value is specified,
 * both kinds are implied.
 *
 * @param papszAllowedDrivers NULL to consider all candidate drivers, or a NULL
 * terminated list of strings with the driver short names that must be considered.
 *
 * @param papszFileList an array of strings, whose last element is the NULL
 * pointer.  These strings are filenames that are auxiliary to the main
 * filename. The passed value may be NULL.
 *
 * @return A GDALDriverH handle or NULL on failure.  For C++ applications
 * this handle can be cast to a GDALDriver *.
 *
 * @since GDAL 2.2
 */

GDALDriverH CPL_STDCALL
GDALIdentifyDriverEx( const char* pszFilename,
                      unsigned int nIdentifyFlags,
                      const char* const* papszAllowedDrivers,
                      const char* const* papszFileList )
{
    GDALDriverManager  *poDM = GetGDALDriverManager();
    CPLAssert( nullptr != poDM );
    GDALOpenInfo oOpenInfo( pszFilename, GA_ReadOnly, papszFileList );

    CPLErrorReset();

    const int nDriverCount = poDM->GetDriverCount();

    // First pass: only use drivers that have a pfnIdentify implementation.
    for( int iDriver = -1; iDriver < nDriverCount; ++iDriver )
    {
        GDALDriver* poDriver;

        if( iDriver < 0 )
            poDriver = GDALGetAPIPROXYDriver();
        else
        {
            poDriver = poDM->GetDriver( iDriver );
            if (papszAllowedDrivers != nullptr &&
                CSLFindString(papszAllowedDrivers,
                              GDALGetDriverShortName(poDriver)) == -1)
                continue;
        }

        VALIDATE_POINTER1( poDriver, "GDALIdentifyDriver", nullptr );

        if( poDriver->pfnIdentify == nullptr )
        {
            continue;
        }

        if (papszAllowedDrivers != nullptr &&
            CSLFindString(papszAllowedDrivers,
                          GDALGetDriverShortName(poDriver)) == -1)
            continue;
        if( (nIdentifyFlags & GDAL_OF_RASTER) != 0 &&
            (nIdentifyFlags & GDAL_OF_VECTOR) == 0 &&
            poDriver->GetMetadataItem(GDAL_DCAP_RASTER) == nullptr )
            continue;
        if( (nIdentifyFlags & GDAL_OF_VECTOR) != 0 &&
            (nIdentifyFlags & GDAL_OF_RASTER) == 0 &&
            poDriver->GetMetadataItem(GDAL_DCAP_VECTOR) == nullptr )
            continue;

        if( poDriver->pfnIdentify( &oOpenInfo ) > 0 )
            return poDriver;
    }

    // Second pass: slow method.
    for( int iDriver = -1; iDriver < nDriverCount; ++iDriver )
    {
        GDALDriver* poDriver;

        if( iDriver < 0 )
            poDriver = GDALGetAPIPROXYDriver();
        else
        {
            poDriver = poDM->GetDriver( iDriver );
            if (papszAllowedDrivers != nullptr &&
                CSLFindString(papszAllowedDrivers,
                              GDALGetDriverShortName(poDriver)) == -1)
                continue;
        }

        VALIDATE_POINTER1( poDriver, "GDALIdentifyDriver", nullptr );

        if( (nIdentifyFlags & GDAL_OF_RASTER) != 0 &&
            (nIdentifyFlags & GDAL_OF_VECTOR) == 0 &&
            poDriver->GetMetadataItem(GDAL_DCAP_RASTER) == nullptr )
            continue;
        if( (nIdentifyFlags & GDAL_OF_VECTOR) != 0 &&
            (nIdentifyFlags & GDAL_OF_RASTER) == 0 &&
            poDriver->GetMetadataItem(GDAL_DCAP_VECTOR) == nullptr )
            continue;

        if( poDriver->pfnIdentify != nullptr )
        {
            if( poDriver->pfnIdentify( &oOpenInfo ) == 0 )
                continue;
        }

        GDALDataset     *poDS;
        if( poDriver->pfnOpen != nullptr )
        {
            poDS = poDriver->pfnOpen( &oOpenInfo );
            if( poDS != nullptr )
            {
                delete poDS;
                return GDALDriver::ToHandle(poDriver);
            }

            if( CPLGetLastErrorNo() != 0 )
                return nullptr;
        }
        else if( poDriver->pfnOpenWithDriverArg != nullptr )
        {
            poDS = poDriver->pfnOpenWithDriverArg( poDriver, &oOpenInfo );
            if( poDS != nullptr )
            {
                delete poDS;
                return GDALDriver::ToHandle(poDriver);
            }

            if( CPLGetLastErrorNo() != 0 )
                return nullptr;
        }
    }

    return nullptr;
}

/************************************************************************/
/*                          SetMetadataItem()                           */
/************************************************************************/

CPLErr GDALDriver::SetMetadataItem( const char *pszName,
                                    const char *pszValue,
                                    const char *pszDomain )

{
    if( pszDomain == nullptr || pszDomain[0] == '\0' )
    {
        /* Automatically sets GDAL_DMD_EXTENSIONS from GDAL_DMD_EXTENSION */
        if( EQUAL(pszName, GDAL_DMD_EXTENSION) &&
            GDALMajorObject::GetMetadataItem(GDAL_DMD_EXTENSIONS) == nullptr )
        {
            GDALMajorObject::SetMetadataItem(GDAL_DMD_EXTENSIONS, pszValue);
        }
    }
    return GDALMajorObject::SetMetadataItem(pszName, pszValue, pszDomain);
}
