/******************************************************************************
 *
 * Project:  GDAL Core
 * Purpose:  GDAL Client/server dataset mechanism.
 * Author:   Even Rouault, <even dot rouault at mines-paris dot org>
 *
 ******************************************************************************
 * Copyright (c) 2013, 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"

#ifdef WIN32
  #ifdef _WIN32_WINNT
    #undef _WIN32_WINNT
  #endif
  #define _WIN32_WINNT 0x0501
  #include <winsock2.h>
  #include <ws2tcpip.h>
  using CPL_SOCKET = SOCKET;
  #ifndef HAVE_GETADDRINFO
    #define HAVE_GETADDRINFO 1
  #endif
  #define CONNECT_LEN(x) static_cast<int>(x)
#else
  #include <sys/types.h>
  #include <sys/socket.h>
  #include <sys/un.h>
  #include <netinet/in.h>
  #include <arpa/inet.h>
  #include <netdb.h>
  using CPL_SOCKET = int;
  #define INVALID_SOCKET -1
  #define SOCKET_ERROR -1
  #define SOCKADDR struct sockaddr
  #define WSAGetLastError() errno
  #define WSACleanup()
  #define closesocket(s) close(s)
  #define CONNECT_LEN(x) (x)
#endif

#ifdef BUFFER_READ
#include <sys/ioctl.h>
#endif

#include <cerrno>
#include <climits>
#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#if HAVE_UNISTD_H
#  include <unistd.h>
#endif
#include <algorithm>
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>

#include "cpl_config.h"
#include "cpl_conv.h"
#include "cpl_error.h"
#include "cpl_minixml.h"
#include "cpl_multiproc.h"
#include "cpl_progress.h"
#include "cpl_spawn.h"
#include "cpl_string.h"
#include "cpl_vsi.h"
#include "gdal.h"
#include "gdal_pam.h"
#include "gdal_priv.h"
#include "gdal_rat.h"
#include "gdal_version.h"

/*!
\page gdal_api_proxy GDAL API Proxy

\section gdal_api_proxy_intro Introduction

(GDAL >= 1.10.0)

When dealing with some file formats, particularly the drivers relying on
third-party (potentially closed-source) libraries, it is difficult to ensure
that those third-party libraries will be robust to hostile/corrupted datasource.

The implemented solution is to have a (private) API_PROXY driver that will
expose a GDALClientDataset object, which will forward all the GDAL API calls to
another process ("server"), where the real driver will be effectively run. This
way, if the server aborts due to a fatal error, the calling process will be
unaffected and will report a clean error instead of aborting itself.

\section gdal_api_proxy_enabling How to enable ?

The API_PROXY mechanism can be enabled by setting the GDAL_API_PROXY config
option to YES.  The option can also be set to a list of file extensions that
must be the only ones to trigger this mechanism (e.g. GDAL_API_PROXY=ecw,sid).

When enabled, datasets can be handled with GDALOpen(), GDALCreate() or
GDALCreateCopy() with their nominal filename (or connection string).

Alternatively, the API_PROXY mechanism can be used selectively on a datasource
by prefixing its name with API_PROXY:, for example GDALOpen("API_PROXY:foo.tif",
GA_ReadOnly).

\section gdal_api_proxy_options Advanced options

For now, the server launched is the gdalserver executable on Windows. On Unix,
the default behaviour is to just fork() the current process. It is also possible
to launch the gdalserver executable by forcing GDAL_API_PROXY_SERVER=YES.  The
full filename of the gdalserver executable can also be specified in the
GDAL_API_PROXY_SERVER.

It is also possible to connect to a gdalserver in TCP, possibly on a remote
host. In that case, gdalserver must be launched on a host with "gdalserver
-tcpserver the_tcp_port". And the client must set
GDAL_API_PROXY_SERVER="hostname:the_tcp_port", where hostname is a string or a
IP address.

On Unix, gdalserver can also be launched on a Unix socket, which "gdalserver
-unixserver /a/filename".  Clients should then set GDAL_API_PROXY_SERVER to
"/a/filename".

In case of many dataset opening or creation, to avoid the cost of repeated
process forking, a pool of unused connections is established. Each time a
dataset is closed, the associated connection is pushed in the pool (if there's
an empty bucket). When a following dataset is to be opened, one of those
connections will be reused. This behaviour is controlled with the
GDAL_API_PROXY_CONN_POOL config option that is set to YES by default, and will
keep a maximum of 4 unused connections.  GDAL_API_PROXY_CONN_POOL can be set to
a integer value to specify the maximum number of unused connections.

\section gdal_api_proxy_limitations Limitations

Datasets stored in the memory virtual file system (/vsimem) or handled by the
MEM driver are excluded from the API Proxy mechanism.

Additionnaly, for GDALCreate() or GDALCreateCopy(), the VRT driver is also
excluded from that mechanism.

Currently, the client dataset returned is not protected by a mutex, so it is
unsafe to use it concurrently from multiple threads. However, it is safe to use
several client datasets from multiple threads.

\section gdal_api_proxy_concurrent Concurrent use of a dataset

Starting with GDAL 2.1 (Unix only), if the gdalserver executable is launched in
TCP (or Unix socket) mode, and with the -nofork flag, clients that will open the
same dataset name through the API Proxy will be associated with the same dataset
object on the server, thus enabling, for example, safe "concurrent" write from
several clients.

But in that mode, only one thread is used in the server, hence
reducing scalability and client isolation. Furthermore some operations, like
"gdal_translate api_proxy:in.tif api_proxy:out.tif" are not possible, since they
would deadlock the server.

*/

/* REMINDER: upgrade this number when the on-wire protocol changes */
/* Note: please at least keep the version exchange protocol unchanged ! */
#define GDAL_CLIENT_SERVER_PROTOCOL_MAJOR 3
#define GDAL_CLIENT_SERVER_PROTOCOL_MINOR 0

CPL_C_START
int CPL_DLL GDALServerLoop(CPL_FILE_HANDLE fin, CPL_FILE_HANDLE fout);
const char* GDALClientDatasetGetFilename(const char* pszFilename);
int CPL_DLL GDALServerLoopSocket(const CPL_SOCKET& nSocket);
void CPL_DLL* GDALServerLoopInstanceCreateFromSocket(const CPL_SOCKET& nSocket);
int  CPL_DLL  GDALServerLoopInstanceRunIteration(void* pInstance);
void CPL_DLL  GDALServerLoopInstanceDestroy(void* pInstance);
CPL_C_END

#define BUFFER_SIZE 1024
struct GDALPipe
{
    CPL_FILE_HANDLE fin;
    CPL_FILE_HANDLE fout;
    CPL_SOCKET      nSocket;
    int             bOK;
    GByte           abyBuffer[BUFFER_SIZE];
    int             nBufferSize;
#ifdef BUFFER_READ
    GByte           abyRecvBuffer[BUFFER_SIZE];
    int             nRecvBufferSize;
#endif
};

struct GDALServerSpawnedProcess
{
    CPLSpawnedProcess *sp;
    GDALPipe          *p;
};

struct GDALServerAsyncProgress
{
    bool   bUpdated;
    double dfComplete;
    char  *pszProgressMsg;
    int    bRet;
    CPLMutex  *hMutex;
};

enum InstrEnum
{
    INSTR_INVALID = 0,
    INSTR_GetGDALVersion = 1, /* do not change this ! */
    INSTR_EXIT,
    INSTR_EXIT_FAIL,
    INSTR_SetConfigOption,
    INSTR_Progress,
    INSTR_Reset,
    INSTR_Open,
    INSTR_Identify,
    INSTR_Create,
    INSTR_CreateCopy,
    INSTR_QuietDelete,
    INSTR_AddBand,
    INSTR_GetGeoTransform,
    INSTR_SetGeoTransform,
    INSTR_GetProjectionRef,
    INSTR_SetProjection,
    INSTR_GetGCPCount,
    INSTR_GetGCPProjection,
    INSTR_GetGCPs,
    INSTR_SetGCPs,
    INSTR_GetFileList,
    INSTR_FlushCache,
    INSTR_SetDescription,
    INSTR_GetMetadata,
    INSTR_GetMetadataItem,
    INSTR_SetMetadata,
    INSTR_SetMetadataItem,
    INSTR_IRasterIO_Read,
    INSTR_IRasterIO_Write,
    INSTR_IBuildOverviews,
    INSTR_AdviseRead,
    INSTR_CreateMaskBand,
    INSTR_Band_First,
    INSTR_Band_FlushCache,
    INSTR_Band_GetCategoryNames,
    INSTR_Band_SetCategoryNames,
    INSTR_Band_SetDescription,
    INSTR_Band_GetMetadata,
    INSTR_Band_GetMetadataItem,
    INSTR_Band_SetMetadata,
    INSTR_Band_SetMetadataItem,
    INSTR_Band_GetColorInterpretation,
    INSTR_Band_SetColorInterpretation,
    INSTR_Band_GetNoDataValue,
    INSTR_Band_GetMinimum,
    INSTR_Band_GetMaximum,
    INSTR_Band_GetOffset,
    INSTR_Band_GetScale,
    INSTR_Band_SetNoDataValue,
    INSTR_Band_SetOffset,
    INSTR_Band_SetScale,
    INSTR_Band_IReadBlock,
    INSTR_Band_IWriteBlock,
    INSTR_Band_IRasterIO_Read,
    INSTR_Band_IRasterIO_Write,
    INSTR_Band_GetStatistics,
    INSTR_Band_ComputeStatistics,
    INSTR_Band_SetStatistics,
    INSTR_Band_ComputeRasterMinMax,
    INSTR_Band_GetHistogram,
    INSTR_Band_GetDefaultHistogram,
    INSTR_Band_SetDefaultHistogram,
    INSTR_Band_HasArbitraryOverviews,
    INSTR_Band_GetOverviewCount,
    INSTR_Band_GetOverview,
    INSTR_Band_GetMaskBand,
    INSTR_Band_GetMaskFlags,
    INSTR_Band_CreateMaskBand,
    INSTR_Band_Fill,
    INSTR_Band_GetColorTable,
    INSTR_Band_SetColorTable,
    INSTR_Band_GetUnitType,
    INSTR_Band_SetUnitType,
    INSTR_Band_BuildOverviews,
    INSTR_Band_GetDefaultRAT,
    INSTR_Band_SetDefaultRAT,
    INSTR_Band_AdviseRead,
    INSTR_Band_DeleteNoDataValue,
    INSTR_Band_End,
    INSTR_END
};

#ifdef DEBUG_VERBOSE
static const char* const apszInstr[] =
{
    "INVALID",
    "GetGDALVersion",
    "EXIT",
    "FAIL",
    "SetConfigOption",
    "Progress",
    "Reset",
    "Open",
    "Identify",
    "Create",
    "CreateCopy",
    "QuietDelete",
    "AddBand",
    "GetGeoTransform",
    "SetGeoTransform",
    "GetProjectionRef",
    "SetProjection",
    "GetGCPCount",
    "GetGCPProjection",
    "GetGCPs",
    "SetGCPs",
    "GetFileList",
    "FlushCache",
    "SetDescription",
    "GetMetadata",
    "GetMetadataItem",
    "SetMetadata",
    "SetMetadataItem",
    "IRasterIO_Read",
    "IRasterIO_Write",
    "IBuildOverviews",
    "AdviseRead",
    "CreateMaskBand",
    "Band_First",
    "Band_FlushCache",
    "Band_GetCategoryNames",
    "Band_SetCategoryNames",
    "Band_SetDescription",
    "Band_GetMetadata",
    "Band_GetMetadataItem",
    "Band_SetMetadata",
    "Band_SetMetadataItem",
    "Band_GetColorInterpretation",
    "Band_SetColorInterpretation",
    "Band_GetNoDataValue",
    "Band_GetMinimum",
    "Band_GetMaximum",
    "Band_GetOffset",
    "Band_GetScale",
    "Band_SetNoDataValue",
    "Band_SetOffset",
    "Band_SetScale",
    "Band_IReadBlock",
    "Band_IWriteBlock",
    "Band_IRasterIO_Read",
    "Band_IRasterIO_Write",
    "Band_GetStatistics",
    "Band_ComputeStatistics",
    "Band_SetStatistics",
    "Band_ComputeRasterMinMax",
    "Band_GetHistogram",
    "Band_GetDefaultHistogram",
    "Band_SetDefaultHistogram",
    "Band_HasArbitraryOverviews",
    "Band_GetOverviewCount",
    "Band_GetOverview",
    "Band_GetMaskBand",
    "Band_GetMaskFlags",
    "Band_CreateMaskBand",
    "Band_Fill",
    "Band_GetColorTable",
    "Band_SetColorTable",
    "Band_GetUnitType",
    "Band_SetUnitType",
    "Band_BuildOverviews",
    "Band_GetDefaultRAT",
    "Band_SetDefaultRAT",
    "Band_AdviseRead",
    "Band_DeleteNoDataValue",
    "Band_End",
    "END",
};
#endif

constexpr GByte abyEndOfJunkMarker[] = { 0xDE, 0xAD, 0xBE, 0xEF };

/* Recycling of connections to child processes */
constexpr int MAX_RECYCLED = 128;
constexpr int DEFAULT_RECYCLED = 4;
static bool bRecycleChild = false;
static int nMaxRecycled = 0;
static GDALServerSpawnedProcess* aspRecycled[MAX_RECYCLED];

/************************************************************************/
/*                          EnterObject                                 */
/************************************************************************/

#ifdef DEBUG_VERBOSE
class EnterObject
{
    const char* m_pszFunction;

    public:
        EnterObject(const char* pszFunction) : m_pszFunction(pszFunction)
        {
            CPLDebug("GDAL", "Enter %s", m_pszFunction);
        }

        ~EnterObject()
        {
            CPLDebug("GDAL", "Leave %s", m_pszFunction);
        }
};

#define CLIENT_ENTER() EnterObject o(__FUNCTION__)
#else
#define CLIENT_ENTER() while( false )
#endif

/************************************************************************/
/*                            MyChdir()                                 */
/************************************************************************/

static void MyChdir(
#ifndef WIN32
CPL_UNUSED
#endif
    const char* pszCWD)
{
#ifdef WIN32
    SetCurrentDirectory(pszCWD);
#else
    if(chdir(pszCWD) != 0)
        fprintf(stderr, "chdir(%s) failed\n", pszCWD);/*ok*/
#endif
}

/************************************************************************/
/*                        MyChdirRootDirectory()                        */
/************************************************************************/

static void MyChdirRootDirectory()
{
#ifdef WIN32
    SetCurrentDirectory("C:\\");
#else
    CPLAssert(chdir("/") == 0);
#endif
}

/************************************************************************/
/*                       GDALClientDataset                              */
/************************************************************************/

class GDALClientDataset final: public GDALPamDataset
{
    GDALServerSpawnedProcess                         *ssp = nullptr;
    GDALPipe                                         *p = nullptr;
    CPLString                                         osProjection{};
    CPLString                                         osGCPProjection{};
    bool                                              bFreeDriver = false;
    int                                               nGCPCount = 0;
    GDAL_GCP                                         *pasGCPs = nullptr;
    std::map<CPLString, char**>                       aoMapMetadata{};
    std::map< std::pair<CPLString,CPLString>, char*>  aoMapMetadataItem{};
    GDALServerAsyncProgress                          *async = nullptr;
    GByte                                             abyCaps[16]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /* 16 * 8 = 128 > INSTR_END */

        int                      mCreateCopy(const char* pszFilename,
                                             GDALDataset* poSrcDS,
                                             int bStrict, char** papszOptions,
                                             GDALProgressFunc pfnProgress,
                                             void * pProgressData);
        int                      mCreate( const char * pszName,
                                          int nXSize, int nYSize, int nBands,
                                          GDALDataType eType,
                                          char ** papszOptions );

                         explicit GDALClientDataset(GDALServerSpawnedProcess* ssp);

        static GDALClientDataset* CreateAndConnect();

    CPL_DISALLOW_COPY_ASSIGN(GDALClientDataset)

    protected:
       CPLErr IBuildOverviews( const char *, int, int *,
                                    int, int *, GDALProgressFunc, void * ) override;
       CPLErr IRasterIO( GDALRWFlag eRWFlag,
                               int nXOff, int nYOff, int nXSize, int nYSize,
                               void * pData, int nBufXSize, int nBufYSize,
                               GDALDataType eBufType,
                               int nBandCount, int *panBandMap,
                               GSpacing nPixelSpace, GSpacing nLineSpace, GSpacing nBandSpace,
                               GDALRasterIOExtraArg* psExtraArg) override;
  public:
    explicit GDALClientDataset(GDALPipe* p);
    ~GDALClientDataset() override;

        int                 Init(const char* pszFilename, GDALAccess eAccess,
                                 char** papszOpenOptions);

        void                AttachAsyncProgress(GDALServerAsyncProgress* asyncIn) { async = asyncIn; }
        int                 ProcessAsyncProgress();
        int                 SupportsInstr(InstrEnum instr) const { return abyCaps[instr / 8] & (1 << (instr % 8)); }

        void FlushCache() override;

        CPLErr AddBand( GDALDataType eType,
                        char **papszOptions=nullptr ) override;

        //virtual void        SetDescription( const char * );

        const char* GetMetadataItem( const char * pszName,
                                     const char * pszDomain = ""  ) override;
        char **GetMetadata( const char * pszDomain = "" ) override;
        CPLErr SetMetadata( char ** papszMetadata,
                            const char * pszDomain = "" ) override;
        CPLErr SetMetadataItem( const char * pszName,
                                const char * pszValue,
                                const char * pszDomain = "" ) override;

        const char* GetProjectionRef() override;
        CPLErr SetProjection( const char * ) override;

        CPLErr GetGeoTransform( double * ) override;
        CPLErr SetGeoTransform( double * ) override;

        int GetGCPCount() override;
        const char *GetGCPProjection() override;
        const GDAL_GCP *GetGCPs() override;
        CPLErr SetGCPs( int nGCPCount, const GDAL_GCP *pasGCPList,
                        const char *pszGCPProjection ) override;

        char **GetFileList() override;

        CPLErr AdviseRead( int nXOff, int nYOff, int nXSize, int nYSize,
                        int nBufXSize, int nBufYSize,
                        GDALDataType eDT,
                        int nBandCount, int *panBandList,
                        char **papszOptions ) override;

        CPLErr CreateMaskBand( int nFlags ) override;

        static GDALDataset *Open( GDALOpenInfo * );
        static int          Identify( GDALOpenInfo * );
        static GDALDataset *CreateCopy( const char * pszFilename,
                                        GDALDataset * poSrcDS, int bStrict, char ** papszOptions,
                                        GDALProgressFunc pfnProgress, void * pProgressData );
        static GDALDataset* Create( const char * pszName,
                                    int nXSize, int nYSize, int nBands,
                                    GDALDataType eType,
                                    char ** papszOptions );
        static CPLErr       Delete( const char * pszName );
};

/************************************************************************/
/*                       GDALClientRasterBand                           */
/************************************************************************/

class GDALClientRasterBand final: public GDALPamRasterBand
{
    friend class GDALClientDataset;

    GDALPipe                                        *p = nullptr;
    int                                              iSrvBand = 0;
    std::map<int, GDALRasterBand*>                   aMapOvrBands{};
    std::map<int, GDALRasterBand*>                   aMapOvrBandsCurrent{};
    GDALRasterBand                                  *poMaskBand = nullptr;
    std::map<CPLString, char**>                      aoMapMetadata{};
    std::map< std::pair<CPLString,CPLString>, char*> aoMapMetadataItem{};
    char                                           **papszCategoryNames = nullptr;
    GDALColorTable                                  *poColorTable = nullptr;
    char                                            *pszUnitType =nullptr;
    GDALRasterAttributeTable                        *poRAT = nullptr;
    std::vector<GDALRasterBand*>                     apoOldMaskBands{};
    GByte                                            abyCaps[16]{0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; /* 16 * 8 = 128 > INSTR_END */

    int                WriteInstr(InstrEnum instr);

    double             GetDouble( InstrEnum instr, int *pbSuccess );
    CPLErr             SetDouble( InstrEnum instr, double dfVal );

    GDALRasterBand    *CreateFakeMaskBand();

    bool                                             bEnableLineCaching = false;
    int                                              nSuccessiveLinesRead = 0;
    GDALDataType                                     eLastBufType = GDT_Unknown;
    int                                              nLastYOff = -1;
    GByte                                           *pabyCachedLines = nullptr;
    GDALDataType                                     eCachedBufType = GDT_Unknown;
    int                                              nCachedYStart = -1;
    int                                              nCachedLines = 0;

    void    InvalidateCachedLines();
    CPLErr  IRasterIO_read_internal(
                                int nXOff, int nYOff, int nXSize, int nYSize,
                                void * pData, int nBufXSize, int nBufYSize,
                                GDALDataType eBufType,
                                GSpacing nPixelSpace, GSpacing nLineSpace );

    CPL_DISALLOW_COPY_ASSIGN(GDALClientRasterBand)

  protected:

    CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void* pImage) override;
    CPLErr IWriteBlock(int nBlockXOff, int nBlockYOff, void* pImage) override;
    CPLErr IRasterIO( GDALRWFlag eRWFlag,
                      int nXOff, int nYOff, int nXSize, int nYSize,
                      void * pData, int nBufXSize, int nBufYSize,
                      GDALDataType eBufType,
                      GSpacing nPixelSpace, GSpacing nLineSpace,
                      GDALRasterIOExtraArg* psExtraArg) override;

  public:
    GDALClientRasterBand(GDALPipe* p, int iSrvBand,
                         GDALClientDataset* poDS, int nBand, GDALAccess eAccess,
                         int nRasterXSize, int nRasterYSize,
                         GDALDataType eDataType, int nBlockXSize, int nBlockYSize,
                         GByte abyCaps[16]);
    ~GDALClientRasterBand() override;

    int GetSrvBand() const { return iSrvBand; }
    int SupportsInstr(InstrEnum instr) const { return abyCaps[instr / 8] & (1 << (instr % 8)); }

    void ClearOverviewCache() { aMapOvrBandsCurrent.clear(); }

    CPLErr FlushCache() override;

    void SetDescription( const char * ) override;

    const char* GetMetadataItem( const char * pszName,
                                 const char * pszDomain = ""  ) override;
    char **GetMetadata( const char * pszDomain = "" ) override;
    CPLErr SetMetadata( char ** papszMetadata,
                        const char * pszDomain = "" ) override;
    CPLErr SetMetadataItem( const char * pszName,
                            const char * pszValue,
                            const char * pszDomain = "" ) override;

    GDALColorInterp GetColorInterpretation() override;
    CPLErr SetColorInterpretation( GDALColorInterp ) override;

    char **GetCategoryNames() override;
    double GetNoDataValue( int *pbSuccess = nullptr ) override;
    double GetMinimum( int *pbSuccess = nullptr ) override;
    double GetMaximum(int *pbSuccess = nullptr ) override;
    double GetOffset( int *pbSuccess = nullptr ) override;
    double GetScale( int *pbSuccess = nullptr ) override;

    GDALColorTable *GetColorTable() override;
    CPLErr SetColorTable( GDALColorTable * ) override;

    const char *GetUnitType() override;
    CPLErr SetUnitType( const char * ) override;

    CPLErr Fill(double dfRealValue, double dfImaginaryValue = 0) override;

    CPLErr SetCategoryNames( char ** ) override;
    CPLErr SetNoDataValue( double ) override;
    CPLErr DeleteNoDataValue() override;
    CPLErr SetOffset( double ) override;
    CPLErr SetScale( double ) override;

    CPLErr GetStatistics( int bApproxOK, int bForce,
                          double *pdfMin, double *pdfMax,
                          double *pdfMean, double *padfStdDev ) override;
    CPLErr ComputeStatistics( int bApproxOK,
                              double *pdfMin, double *pdfMax,
                              double *pdfMean, double *pdfStdDev,
                              GDALProgressFunc, void *pProgressData ) override;
    CPLErr SetStatistics( double dfMin, double dfMax,
                          double dfMean, double dfStdDev ) override;
    CPLErr ComputeRasterMinMax( int, double* ) override;

    CPLErr GetHistogram( double dfMin, double dfMax,
                         int nBuckets, GUIntBig *panHistogram,
                         int bIncludeOutOfRange, int bApproxOK,
                         GDALProgressFunc pfnProgress,
                         void *pProgressData ) override;

    CPLErr GetDefaultHistogram( double *pdfMin, double *pdfMax,
                                int *pnBuckets, GUIntBig ** ppanHistogram,
                                int bForce,
                                GDALProgressFunc, void *pProgressData) override;
    CPLErr SetDefaultHistogram( double dfMin, double dfMax,
                                int nBuckets, GUIntBig *panHistogram ) override;

    int HasArbitraryOverviews() override;
    int GetOverviewCount() override;
    GDALRasterBand *GetOverview(int) override;

    GDALRasterBand *GetMaskBand() override;
    int GetMaskFlags() override;
    CPLErr CreateMaskBand( int nFlags ) override;

    CPLErr BuildOverviews( const char *, int, int *,
                           GDALProgressFunc, void * ) override;

    GDALRasterAttributeTable *GetDefaultRAT() override;
    CPLErr SetDefaultRAT( const GDALRasterAttributeTable * ) override;

    CPLErr AdviseRead( int nXOff, int nYOff, int nXSize, int nYSize,
                    int nBufXSize, int nBufYSize,
                    GDALDataType eDT, char **papszOptions ) override;
    // virtual GDALRasterBand *GetRasterSampleOverview( GUIntBig ) override;
};

/************************************************************************/
/*                          GDALPipeBuild()                             */
/************************************************************************/

static GDALPipe* GDALPipeBuild(CPLSpawnedProcess* sp)
{
    GDALPipe* p = static_cast<GDALPipe*>(CPLMalloc(sizeof(GDALPipe)));
    p->bOK = TRUE;
    p->fin = CPLSpawnAsyncGetInputFileHandle(sp);
    p->fout = CPLSpawnAsyncGetOutputFileHandle(sp);
    p->nSocket = INVALID_SOCKET;
    p->nBufferSize = 0;
#ifdef BUFFER_READ
    p->nRecvBufferSize = 0;
#endif
    return p;
}

static GDALPipe* GDALPipeBuild(const CPL_SOCKET& nSocket)
{
    GDALPipe* p = static_cast<GDALPipe*>(CPLMalloc(sizeof(GDALPipe)));
    p->bOK = TRUE;
    p->fin = CPL_FILE_INVALID_HANDLE;
    p->fout = CPL_FILE_INVALID_HANDLE;
    p->nSocket = nSocket;
    p->nBufferSize = 0;
#ifdef BUFFER_READ
    p->nRecvBufferSize = 0;
#endif
    return p;
}

static GDALPipe* GDALPipeBuild(CPL_FILE_HANDLE fin, CPL_FILE_HANDLE fout)
{
    GDALPipe* p = static_cast<GDALPipe*>(CPLMalloc(sizeof(GDALPipe)));
    p->bOK = TRUE;
    p->fin = fin;
    p->fout = fout;
    p->nSocket = INVALID_SOCKET;
    p->nBufferSize = 0;
#ifdef BUFFER_READ
    p->nRecvBufferSize = 0;
#endif
    return p;
}

/************************************************************************/
/*                      GDALPipeWrite_internal()                        */
/************************************************************************/

static int GDALPipeWrite_internal(GDALPipe* p, const void* data, int length)
{
    if(!p->bOK)
        return FALSE;
    if( p->fout != CPL_FILE_INVALID_HANDLE )
    {
        int nRet = CPLPipeWrite(p->fout, data, length);
        if( !nRet )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Write to pipe failed");
            p->bOK = FALSE;
        }
        return nRet;
    }
    else
    {
        const char* pabyData = static_cast<const char*>(data);
        int nRemain = length;
        while( nRemain > 0 )
        {
            int nRet = static_cast<int>(send(p->nSocket, pabyData, nRemain, 0));
            if( nRet < 0 )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Write to socket failed");
                p->bOK = FALSE;
                return FALSE;
            }
            pabyData += nRet;
            nRemain -= nRet;
        }
        return TRUE;
    }
}

/************************************************************************/
/*                        GDALPipeFlushBuffer()                         */
/************************************************************************/

static int GDALPipeFlushBuffer(GDALPipe * p)
{
    if( p->nBufferSize == 0 )
        return TRUE;
    if( GDALPipeWrite_internal(p, p->abyBuffer, p->nBufferSize) )
    {
        p->nBufferSize = 0;
        return TRUE;
    }
    return FALSE;
}

/************************************************************************/
/*                            GDALPipeFree()                            */
/************************************************************************/

static void GDALPipeFree(GDALPipe * p)
{
    GDALPipeFlushBuffer(p);
    if( p->nSocket != INVALID_SOCKET )
    {
        closesocket(p->nSocket);
        WSACleanup();
    }
    CPLFree(p);
}

/************************************************************************/
/*                     GDALPipeReadSocketInternal()                     */
/************************************************************************/

static int GDALPipeReadSocketInternal(GDALPipe* p, void* data, int length)
{
    char* pabyData = static_cast<char*>(data);
    int nRemain = length;
    while( nRemain > 0 )
    {
        int nRet = static_cast<int>(recv(p->nSocket, pabyData, nRemain, 0));
        if( nRet <= 0 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Read from socket failed");
            p->bOK = FALSE;
            return FALSE;
        }
        pabyData += nRet;
        nRemain -= nRet;
    }
    return TRUE;
}

/************************************************************************/
/*                            GDALPipeRead()                            */
/************************************************************************/

static int GDALPipeRead(GDALPipe* p, void* data, int length)
{
    if(!p->bOK)
        return FALSE;
    if(!GDALPipeFlushBuffer(p))
        return FALSE;

#ifdef BUFFER_READ
begin:
    if( length <= p->nRecvBufferSize )
    {
        memcpy(data, p->abyRecvBuffer, length);
        memmove(p->abyRecvBuffer, p->abyRecvBuffer + length, p->nRecvBufferSize - length);
        p->nRecvBufferSize -= length;
        return TRUE;
    }
    if( p->nRecvBufferSize )
    {
        memcpy( data, p->abyRecvBuffer, p->nRecvBufferSize);
        data = (char*) data + p->nRecvBufferSize;
        length -= p->nRecvBufferSize;
        p->nRecvBufferSize = 0;
    }
#endif

    if( p->fout != CPL_FILE_INVALID_HANDLE )
    {
        if( CPLPipeRead(p->fin, data, length) )
            return TRUE;
        // fprintf(stderr, "[%d] Read from pipe failed\n", (int)getpid());
        CPLError(CE_Failure, CPLE_AppDefined, "Read from pipe failed");
        p->bOK = FALSE;
        return FALSE;
    }
    else
    {
#ifdef BUFFER_READ
        int nAvailable = 0;
        if( length < BUFFER_SIZE &&
            ioctl(p->nSocket, FIONREAD, &nAvailable) == 0 &&
            nAvailable > length )
        {
            //CPLDebug("GDAL", "%d bytes available", nAvailable);
            int ToRead = ( nAvailable > BUFFER_SIZE ) ? BUFFER_SIZE : nAvailable;
            if( !GDALPipeReadSocketInternal(p, p->abyRecvBuffer, ToRead) )
                return FALSE;
            p->nRecvBufferSize = ToRead;
            goto begin;
        }
#endif
        return GDALPipeReadSocketInternal(p, data, length);
    }
}

/************************************************************************/
/*                           GDALPipeWrite()                            */
/************************************************************************/

static int GDALPipeWrite(GDALPipe* p, const void* data,
                                 int length)
{
    //return GDALPipeWrite_internal(p, data, length);
    const GByte* pCur = static_cast<const GByte*>(data);
    int nRemain = length;
    while( nRemain > 0 )
    {
        if( p->nBufferSize + nRemain <= BUFFER_SIZE )
        {
            memcpy(p->abyBuffer + p->nBufferSize, pCur, nRemain);
            pCur += nRemain;
            p->nBufferSize += nRemain;
            nRemain = 0;
        }
        else if( nRemain > BUFFER_SIZE )
        {
            if( !GDALPipeFlushBuffer(p) )
                return FALSE;
            if( !GDALPipeWrite_internal(p, pCur, nRemain) )
                return FALSE;
            pCur += nRemain;
            nRemain = 0;
        }
        else
        {
            memcpy(p->abyBuffer + p->nBufferSize, pCur,
                   BUFFER_SIZE - p->nBufferSize);
            pCur += (BUFFER_SIZE - p->nBufferSize);
            nRemain -= (BUFFER_SIZE - p->nBufferSize);
            p->nBufferSize = BUFFER_SIZE;
            if( !GDALPipeFlushBuffer(p) )
                return FALSE;
        }
    }
    return TRUE;
}

/************************************************************************/
/*                          GDALPipeRead()                              */
/************************************************************************/

static int GDALPipeRead(GDALPipe* p, int* pnInt)
{
    return GDALPipeRead(p, pnInt, 4);
}

static int GDALPipeRead(GDALPipe* p, GIntBig* pnInt)
{
    return GDALPipeRead(p, pnInt, 8);
}

static int GDALPipeRead(GDALPipe* p, CPLErr* peErr)
{
    return GDALPipeRead(p, peErr, 4);
}

static int GDALPipeRead(GDALPipe* p, double* pdfDouble)
{
    return GDALPipeRead(p, pdfDouble, 8);
}

static int GDALPipeRead_nolength(GDALPipe* p, int nLength, void* pabyData)
{
    return GDALPipeRead(p, pabyData, nLength);
}

static int GDALPipeRead(GDALPipe* p, int nExpectedLength, void* pabyData)
{
    int nLength = 0;
    return GDALPipeRead(p, &nLength) &&
           nLength == nExpectedLength &&
           GDALPipeRead_nolength(p, nLength, pabyData);
}

static int GDALPipeRead(GDALPipe* p, char** ppszStr)
{
    int nLength = 0;
    if( !GDALPipeRead(p, &nLength) || nLength < 0 )
    {
        *ppszStr = nullptr;
        return FALSE;
    }
    if( nLength == 0 )
    {
        *ppszStr = nullptr;
        return TRUE;
    }
    *ppszStr = (nLength < INT_MAX-1) ? static_cast<char*>(VSIMalloc(nLength + 1)) : nullptr;
    if( *ppszStr == nullptr )
        return FALSE;
    if( nLength > 0 && !GDALPipeRead_nolength(p, nLength, *ppszStr) )
    {
        CPLFree(*ppszStr);
        *ppszStr = nullptr;
        return FALSE;
    }
    (*ppszStr)[nLength] = 0;
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, char*** ppapszStr)
{
    int nStrCount = 0;
    if( !GDALPipeRead(p, &nStrCount) )
        return FALSE;
    if( nStrCount < 0 )
    {
        *ppapszStr = nullptr;
        return TRUE;
    }

    *ppapszStr = static_cast<char**>(VSIMalloc2(sizeof(char*), (nStrCount + 1)));
    if( *ppapszStr == nullptr )
        return FALSE;
    for(int i=0;i<nStrCount;i++)
    {
        if( !GDALPipeRead(p, (*ppapszStr) + i) )
        {
            CSLDestroy(*ppapszStr);
            *ppapszStr = nullptr;
            return FALSE;
        }
    }
    (*ppapszStr)[nStrCount] = nullptr;
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, int nItems, int** ppanInt)
{
    int nSize = 0;
    *ppanInt = nullptr;
    if( !GDALPipeRead(p, &nSize) )
        return FALSE;
    if( nSize != nItems * static_cast<int>(sizeof(int)) )
        return FALSE;
    *ppanInt = static_cast<int*>(VSIMalloc(nSize));
    if( *ppanInt == nullptr )
        return FALSE;
    if( !GDALPipeRead_nolength(p, nSize, *ppanInt) )
        return FALSE;
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, int nItems, GUIntBig** ppanInt)
{
    int nSize = 0;
    *ppanInt = nullptr;
    if( !GDALPipeRead(p, &nSize) )
        return FALSE;
    if( nSize != nItems * static_cast<int>(sizeof(GUIntBig)) )
        return FALSE;
    *ppanInt = static_cast<GUIntBig*>(VSIMalloc(nSize));
    if( *ppanInt == nullptr )
        return FALSE;
    if( !GDALPipeRead_nolength(p, nSize, *ppanInt) )
        return FALSE;
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, GDALColorTable** ppoColorTable)
{
    int nPaletteInterp, nCount;
    *ppoColorTable = nullptr;
    if( !GDALPipeRead(p, &nPaletteInterp) )
        return FALSE;
    GDALColorTable* poColorTable;
    if( nPaletteInterp < 0 )
    {
        poColorTable = nullptr;
    }
    else
    {
        if( !GDALPipeRead(p, &nCount) )
            return FALSE;
        poColorTable = new GDALColorTable(static_cast<GDALPaletteInterp>(nPaletteInterp));
        for(int i=0; i<nCount; i++)
        {
            int c1, c2, c3, c4;
            if( !GDALPipeRead(p, &c1) ||
                !GDALPipeRead(p, &c2) ||
                !GDALPipeRead(p, &c3) ||
                !GDALPipeRead(p, &c4) )
            {
                delete poColorTable;
                return FALSE;
            }
            GDALColorEntry eEntry;
            eEntry.c1 = static_cast<short>(c1);
            eEntry.c2 = static_cast<short>(c2);
            eEntry.c3 = static_cast<short>(c3);
            eEntry.c4 = static_cast<short>(c4);
            poColorTable->SetColorEntry(i, &eEntry);
        }
    }
    *ppoColorTable = poColorTable;
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, GDALRasterAttributeTable** ppoRAT)
{
    *ppoRAT = nullptr;
    char* pszRAT = nullptr;
    if( !GDALPipeRead(p, &pszRAT))
        return FALSE;
    if( pszRAT == nullptr )
        return TRUE;

    CPLXMLNode* poNode = CPLParseXMLString( pszRAT );
    CPLFree(pszRAT);
    if( poNode == nullptr )
        return FALSE;

    *ppoRAT = new GDALDefaultRasterAttributeTable();
    if( (*ppoRAT)->XMLInit(poNode, nullptr) != CE_None )
    {
        CPLDestroyXMLNode(poNode);
        delete *ppoRAT;
        *ppoRAT = nullptr;
        return FALSE;
    }
    CPLDestroyXMLNode(poNode);
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, int* pnGCPCount, GDAL_GCP** ppasGCPs)
{
    *pnGCPCount = 0;
    *ppasGCPs = nullptr;
    int nGCPCount = 0;
    if( !GDALPipeRead(p, &nGCPCount) )
        return FALSE;
    GDAL_GCP* pasGCPs = static_cast<GDAL_GCP*>(CPLCalloc(nGCPCount, sizeof(GDAL_GCP)));
    for(int i=0;i<nGCPCount;i++)
    {
        if( !GDALPipeRead(p, &pasGCPs[i].pszId) ||
            !GDALPipeRead(p, &pasGCPs[i].pszInfo) ||
            !GDALPipeRead(p, &pasGCPs[i].dfGCPPixel) ||
            !GDALPipeRead(p, &pasGCPs[i].dfGCPLine) ||
            !GDALPipeRead(p, &pasGCPs[i].dfGCPX) ||
            !GDALPipeRead(p, &pasGCPs[i].dfGCPY) ||
            !GDALPipeRead(p, &pasGCPs[i].dfGCPZ) )
        {
            GDALDeinitGCPs(i, pasGCPs);
            CPLFree(pasGCPs);
            return FALSE;
        }
    }
    *pnGCPCount = nGCPCount;
    *ppasGCPs = pasGCPs;
    return TRUE;
}

static int GDALPipeRead(GDALPipe* p, GDALClientDataset* poDS,
                        GDALRasterBand** ppoBand, GByte abyCaps[16])
{
    int iSrvBand = 0;
    *ppoBand = nullptr;
    if( !GDALPipeRead(p, &iSrvBand) )
        return FALSE;
    if( iSrvBand < 0 )
        return TRUE;

    int iBand, nBandAccess, nXSize, nYSize, nDataType, nBlockXSize, nBlockYSize;
    if( !GDALPipeRead(p, &iBand) ||
        !GDALPipeRead(p, &nBandAccess) ||
        !GDALPipeRead(p, &nXSize) ||
        !GDALPipeRead(p, &nYSize) ||
        !GDALPipeRead(p, &nDataType) ||
        !GDALPipeRead(p, &nBlockXSize) ||
        !GDALPipeRead(p, &nBlockYSize) )
    {
        return FALSE;
    }

    char* pszDescription = nullptr;
    if( !GDALPipeRead(p, &pszDescription) )
        return FALSE;

    GDALClientRasterBand* poBand = new GDALClientRasterBand(p, iSrvBand,
                                                  poDS, iBand,
                                                  static_cast<GDALAccess>(nBandAccess),
                                                  nXSize, nYSize,
                                                  static_cast<GDALDataType>(nDataType),
                                                  nBlockXSize, nBlockYSize, abyCaps);
    if( pszDescription != nullptr )
        poBand->GDALMajorObject::SetDescription(pszDescription);
    CPLFree(pszDescription);

    *ppoBand = poBand;
    return TRUE;
}

/************************************************************************/
/*                GDALSkipUntilEndOfJunkMarker()                        */
/************************************************************************/

static int GDALSkipUntilEndOfJunkMarker(GDALPipe* p)
{
    if(!p->bOK)
        return FALSE;
    size_t nIter = 0;
    int nStep = 0;
    CPLString osJunk;
    const int nMarkerSize = static_cast<int>(sizeof(abyEndOfJunkMarker));
    GByte abyBuffer[sizeof(abyEndOfJunkMarker)];
    if( !GDALPipeRead_nolength(p, sizeof(abyBuffer), abyBuffer ) )
        return FALSE;
    if( memcmp(abyEndOfJunkMarker, abyBuffer, sizeof(abyBuffer)) == 0 )
        return TRUE;

    GByte c = 0;
    while(true)
    {
        if( nIter < sizeof(abyBuffer) )
            c = abyBuffer[nIter ++];
        else if( !GDALPipeRead_nolength(p, 1, &c ) )
            return FALSE;

        if( c != 0 )
            osJunk += c;
        if( c == abyEndOfJunkMarker[0] ) nStep = 1;
        else if( c == abyEndOfJunkMarker[nStep] )
        {
            nStep ++;
            if( nStep == nMarkerSize )
            {
                osJunk.resize(osJunk.size() - nMarkerSize);
                if( !osJunk.empty() )
                    CPLDebug("GDAL", "Got junk : %s", osJunk.c_str());
                return TRUE;
            }
        }
        else
            nStep = 0;
    }
}

/************************************************************************/
/*                         GDALPipeWrite()                              */
/************************************************************************/

static int GDALPipeWrite(GDALPipe* p, int nInt)
{
    return GDALPipeWrite(p, &nInt, 4);
}

static int GDALPipeWrite(GDALPipe* p, GIntBig nInt)
{
    return GDALPipeWrite(p, &nInt, 8);
}

static int GDALPipeWrite(GDALPipe* p, double dfDouble)
{
    return GDALPipeWrite(p, &dfDouble, 8);
}

static int GDALPipeWrite_nolength(GDALPipe* p, int nLength, const void* pabyData)
{
    return GDALPipeWrite(p, pabyData, nLength);
}

static int GDALPipeWrite(GDALPipe* p, int nLength, const void* pabyData)
{
    if( !GDALPipeWrite(p, nLength) ||
        !GDALPipeWrite_nolength(p, nLength, pabyData) )
        return FALSE;
    return TRUE;
}

static int GDALPipeWrite(GDALPipe* p, const char* pszStr)
{
    if( pszStr == nullptr )
        return GDALPipeWrite(p, 0);
    return GDALPipeWrite(p, static_cast<int>(strlen(pszStr)) + 1, pszStr);
}

static int GDALPipeWrite(GDALPipe* p, char** papszStr)
{
    if( papszStr == nullptr )
        return GDALPipeWrite(p, -1);

    int nCount = CSLCount(papszStr);
    if( !GDALPipeWrite(p, nCount) )
        return FALSE;
    for(int i=0; i < nCount; i++)
    {
        if( !GDALPipeWrite(p, papszStr[i]) )
            return FALSE;
    }
    return TRUE;
}

static int GDALPipeWrite(GDALPipe* p,
                         std::vector<GDALRasterBand*>& aBands,
                         GDALRasterBand* poBand)
{
    if( poBand == nullptr )
        GDALPipeWrite(p, -1);
    else
    {
        GDALPipeWrite(p, static_cast<int>(aBands.size()));
        aBands.push_back(poBand);
        GDALPipeWrite(p, poBand->GetBand());
        GDALPipeWrite(p, poBand->GetAccess());
        GDALPipeWrite(p, poBand->GetXSize());
        GDALPipeWrite(p, poBand->GetYSize());
        GDALPipeWrite(p, poBand->GetRasterDataType());
        int nBlockXSize, nBlockYSize;
        poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
        GDALPipeWrite(p, nBlockXSize);
        GDALPipeWrite(p, nBlockYSize);
        GDALPipeWrite(p, poBand->GetDescription() );
    }
    return TRUE;
}

static int GDALPipeWrite(GDALPipe* p, GDALColorTable* poColorTable)
{
    if( poColorTable == nullptr )
    {
        if( !GDALPipeWrite(p, -1) )
            return FALSE;
    }
    else
    {
        int nCount = poColorTable->GetColorEntryCount();
        if( !GDALPipeWrite(p, poColorTable->GetPaletteInterpretation()) ||
            !GDALPipeWrite(p, nCount) )
            return FALSE;

        for(int i=0; i < nCount; i++)
        {
            const GDALColorEntry* poColorEntry = poColorTable->GetColorEntry(i);
            if( !GDALPipeWrite(p, poColorEntry->c1) ||
                !GDALPipeWrite(p, poColorEntry->c2) ||
                !GDALPipeWrite(p, poColorEntry->c3) ||
                !GDALPipeWrite(p, poColorEntry->c4) )
                return FALSE;
        }
    }
    return TRUE;
}

static int GDALPipeWrite(GDALPipe* p, const GDALRasterAttributeTable* poRAT)
{
    // TODO(schwehr): Refactor and simplify.
    int bRet = FALSE;
    if( poRAT == nullptr )
    {
        bRet = GDALPipeWrite(p, static_cast<const char*>(nullptr));
    }
    else
    {
        CPLXMLNode* poNode = poRAT->Serialize();
        if( poNode != nullptr )
        {
            char* pszRAT = CPLSerializeXMLTree(poNode);
            bRet = GDALPipeWrite(p, pszRAT);
            CPLFree(pszRAT);
            CPLDestroyXMLNode(poNode);
        }
        else
        {
            bRet = GDALPipeWrite(p, static_cast<const char*>(nullptr));
        }
    }
    return bRet;
}

static int GDALPipeWrite(GDALPipe* p, int nGCPCount, const GDAL_GCP* pasGCPs)
{
    if( !GDALPipeWrite(p, nGCPCount ) )
        return FALSE;
    for( int i=0; i < nGCPCount; i++ )
    {
        if( !GDALPipeWrite(p, pasGCPs[i].pszId) ||
            !GDALPipeWrite(p, pasGCPs[i].pszInfo) ||
            !GDALPipeWrite(p, pasGCPs[i].dfGCPPixel) ||
            !GDALPipeWrite(p, pasGCPs[i].dfGCPLine) ||
            !GDALPipeWrite(p, pasGCPs[i].dfGCPX) ||
            !GDALPipeWrite(p, pasGCPs[i].dfGCPY) ||
            !GDALPipeWrite(p, pasGCPs[i].dfGCPZ) )
            return FALSE;
    }
    return TRUE;
}

/************************************************************************/
/*                    GDALPipeWriteConfigOption()                       */
/************************************************************************/

static int GDALPipeWriteConfigOption(GDALPipe* p, const char* pszKey,
                                     int bWriteIfNonNull = TRUE)
{
    const char* pszVal = CPLGetConfigOption(pszKey, nullptr);
    if( pszVal == nullptr && !bWriteIfNonNull )
        return TRUE;
    return GDALPipeWrite(p, INSTR_SetConfigOption) &&
           GDALPipeWrite(p, pszKey) &&
           GDALPipeWrite(p, pszVal);
}

/************************************************************************/
/*                    GDALEmitEndOfJunkMarker()                         */
/************************************************************************/

/* When receiving an instruction : */
/* - read all input arguments */
/* - do the call to the dataset or the band */
/* - as the previous call may potentially emit */
/*   unwanted content on the stdout, we emit */
/*   a special marker that the receiver will */
/*   wait until interpreting the rest of the */
/*   output arguments */
/* - emit output arguments */
static int GDALEmitEndOfJunkMarker(GDALPipe* p)
{
    return GDALPipeWrite_nolength(p, sizeof(abyEndOfJunkMarker),
                                  abyEndOfJunkMarker) ==
                                static_cast<int>(sizeof(abyEndOfJunkMarker));
}

/************************************************************************/
/*                       GDALConsumeErrors()                            */
/************************************************************************/

static void GDALConsumeErrors(GDALPipe* p)
{
    int nErrors = 0;
    if( !GDALPipeRead(p, &nErrors) )
        return;
    for( int i=0; i < nErrors; i++ )
    {
        int       eErr = 0;
        int       nErrNo = 0;
        char     *pszErrorMsg = nullptr;
        if( !GDALPipeRead(p, &eErr) ||
            !GDALPipeRead(p, &nErrNo) ||
            !GDALPipeRead(p, &pszErrorMsg) )
            return;
        CPLError(static_cast<CPLErr>(eErr),
                 static_cast<CPLErrorNum>(nErrNo), "%s",
                 pszErrorMsg ? pszErrorMsg : "unknown");
        CPLFree(pszErrorMsg);
    }
}

/************************************************************************/
/*                       GDALEmitReset()                                */
/************************************************************************/

static int GDALEmitReset(GDALPipe* p)
{
    int bOK = FALSE;
    if( !GDALPipeWrite(p, INSTR_Reset) ||
        !GDALSkipUntilEndOfJunkMarker(p) ||
        !GDALPipeRead(p, &bOK) )
        return FALSE;
    GDALConsumeErrors(p);
    return bOK;
}

/************************************************************************/
/*                       GDALEmitEXIT()                                 */
/************************************************************************/

static int GDALEmitEXIT(GDALPipe* p, InstrEnum instr = INSTR_EXIT )
{
    int bOK = FALSE;
    if( !GDALPipeWrite(p, instr) ||
        !GDALSkipUntilEndOfJunkMarker(p) ||
        !GDALPipeRead(p, &bOK) )
        return FALSE;
    return bOK;
}

/************************************************************************/
/*                    GDALServerSpawnAsyncFinish()                      */
/************************************************************************/

static int GDALServerSpawnAsyncFinish(GDALServerSpawnedProcess* ssp)
{
    if( bRecycleChild && ssp->p->bOK )
    {
        /* Store the descriptor in a free slot if available for a */
        /* later reuse */
        CPLMutexHolderD(GDALGetphDMMutex());
        for(int i = 0; i < nMaxRecycled; i ++)
        {
            if( aspRecycled[i] == nullptr )
            {
                if( !GDALEmitReset(ssp->p) )
                    break;

                aspRecycled[i] = ssp;
                return TRUE;
            }
        }
    }

    if(ssp->p->bOK)
    {
        GDALEmitEXIT(ssp->p);
    }

    CPLDebug("GDAL", "Destroy spawned process %p", ssp);
    GDALPipeFree(ssp->p);
    int nRet = ssp->sp ? CPLSpawnAsyncFinish(ssp->sp, TRUE, TRUE) : 0;
    CPLFree(ssp);
    return nRet;
}

/************************************************************************/
/*                      GDALCheckServerVersion()                        */
/************************************************************************/

static int GDALCheckServerVersion(GDALPipe* p)
{
    GDALPipeWrite(p, INSTR_GetGDALVersion);
    char bIsLSB = CPL_IS_LSB;
    GDALPipeWrite_nolength(p, 1, &bIsLSB);
    GDALPipeWrite(p, GDALVersionInfo("RELEASE_NAME"));
    GDALPipeWrite(p, GDAL_VERSION_MAJOR);
    GDALPipeWrite(p, GDAL_VERSION_MINOR);
    GDALPipeWrite(p, GDAL_CLIENT_SERVER_PROTOCOL_MAJOR);
    GDALPipeWrite(p, GDAL_CLIENT_SERVER_PROTOCOL_MINOR);
    GDALPipeWrite(p, 0); /* extra bytes */

    char* pszVersion = nullptr;
    int nMajor, nMinor, nProtocolMajor, nProtocolMinor, nExtraBytes;
    if( !GDALPipeRead(p, &pszVersion) ||
        !GDALPipeRead(p, &nMajor) ||
        !GDALPipeRead(p, &nMinor) ||
        !GDALPipeRead(p, &nProtocolMajor) ||
        !GDALPipeRead(p, &nProtocolMinor) ||
        !GDALPipeRead(p, &nExtraBytes) )
    {
        CPLFree(pszVersion);
        return FALSE;
    }

    if( nExtraBytes > 0 )
    {
        void* pTemp = VSIMalloc(nExtraBytes);
        if( !pTemp )
        {
            CPLFree(pszVersion);
            return FALSE;
        }
        if( !GDALPipeRead_nolength(p, nExtraBytes, pTemp) )
        {
            CPLFree(pszVersion);
            CPLFree(pTemp);
            return FALSE;
        }
        CPLFree(pTemp);
    }

    CPLDebug("GDAL",
             "Server version : %s (%d.%d), "
             "Server protocol version = %d.%d",
             pszVersion,
             nMajor, nMinor,
             nProtocolMajor, nProtocolMinor);
    CPLDebug("GDAL",
             "Client version : %s (%d.%d), "
             "Client protocol version = %d.%d",
             GDALVersionInfo("RELEASE_NAME"), GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR,
             GDAL_CLIENT_SERVER_PROTOCOL_MAJOR,
             GDAL_CLIENT_SERVER_PROTOCOL_MINOR);
    if( nProtocolMajor != GDAL_CLIENT_SERVER_PROTOCOL_MAJOR )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "GDAL server (GDAL version=%s, protocol version=%d.%d) is "
                 "incompatible with GDAL client (GDAL version=%s, protocol version=%d.%d)",
                 pszVersion,
                 nProtocolMajor, nProtocolMinor,
                 GDALVersionInfo("RELEASE_NAME"),
                 GDAL_CLIENT_SERVER_PROTOCOL_MAJOR,
                 GDAL_CLIENT_SERVER_PROTOCOL_MINOR);
        CPLFree(pszVersion);
        return FALSE;
    }
    else if( nProtocolMinor != GDAL_CLIENT_SERVER_PROTOCOL_MINOR )
    {
        CPLDebug("GDAL", "Note: client/server protocol versions differ by minor number.");
    }
    CPLFree(pszVersion);
    return TRUE;
}

/************************************************************************/
/*                       GDALServerLoopForked()                         */
/************************************************************************/

#ifndef WIN32
void CPLReinitAllMutex();

static int GDALServerLoopForked(CPL_FILE_HANDLE fin, CPL_FILE_HANDLE fout)
{
    /* Do not try to close datasets at process closing */
    GDALNullifyOpenDatasetsList();
    /* Nullify the existing mutex to avoid issues with locked mutex by */
    /* parent's process threads */
    GDALNullifyProxyPoolSingleton();
#ifdef CPL_MULTIPROC_PTHREAD
    CPLReinitAllMutex();
#endif

    memset(aspRecycled, 0, sizeof(aspRecycled));

    return GDALServerLoop(fin, fout);
}
#endif

/************************************************************************/
/*                      GDALServerSpawnAsync()                          */
/************************************************************************/

static GDALServerSpawnedProcess* GDALServerSpawnAsync()
{
    if( bRecycleChild )
    {
        /* Try to find an existing unused descriptor to reuse it */
        CPLMutexHolderD(GDALGetphDMMutex());
        for(int i = 0; i < nMaxRecycled; i ++)
        {
            if( aspRecycled[i] != nullptr )
            {
                GDALServerSpawnedProcess* ssp = aspRecycled[i];
                aspRecycled[i] = nullptr;
                return ssp;
            }
        }
    }

#ifdef WIN32
    const char* pszSpawnServer = CPLGetConfigOption("GDAL_API_PROXY_SERVER", "gdalserver");
#else
    const char* pszSpawnServer = CPLGetConfigOption("GDAL_API_PROXY_SERVER", "NO");
#endif

    const char* pszColon = strchr(pszSpawnServer, ':');
    if( pszColon != nullptr &&
        pszColon != pszSpawnServer + 1 /* do not confuse with c:/some_path/gdalserver.exe */ )
    {
        CPLString osHost(pszSpawnServer);
        osHost.resize(pszColon - pszSpawnServer);
        CPL_SOCKET nConnSocket = INVALID_SOCKET;

#ifdef WIN32
        WSADATA wsaData;

        int nRet1 = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (nRet1 != NO_ERROR)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "WSAStartup() failed with error: %d\n", nRet1);
            return nullptr;
        }
#endif

#ifdef HAVE_GETADDRINFO
        struct addrinfo sHints;
        struct addrinfo* psResults = nullptr;
        memset(&sHints, 0, sizeof(struct addrinfo));
        sHints.ai_family = AF_UNSPEC;
        sHints.ai_socktype = SOCK_STREAM;
        sHints.ai_flags = 0;
        sHints.ai_protocol = IPPROTO_TCP;

        int nRet2 = getaddrinfo(osHost, pszColon + 1, &sHints, &psResults);
        if (nRet2)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "getaddrinfo(): %s", gai_strerror(nRet2));
            WSACleanup();
            return nullptr;
        }

        // Used after for.
        struct addrinfo *psResultsIter = psResults;
        for( ; psResultsIter != nullptr; psResultsIter = psResultsIter->ai_next )
        {
            nConnSocket = socket(psResultsIter->ai_family,
                                 psResultsIter->ai_socktype,
                                 psResultsIter->ai_protocol);
            if (nConnSocket == INVALID_SOCKET)
                continue;

            if (connect(nConnSocket, psResultsIter->ai_addr,
                        CONNECT_LEN(psResultsIter->ai_addrlen)) != SOCKET_ERROR)
                break;

            closesocket(nConnSocket);
        }

        freeaddrinfo(psResults);

        if (psResultsIter == nullptr)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Could not connect");
            WSACleanup();
            return nullptr;
        }
#else
        struct sockaddr_in sockAddrIn;
        int nPort = atoi(pszColon + 1);
        sockAddrIn.sin_family = AF_INET;
        sockAddrIn.sin_addr.s_addr = inet_addr(osHost);
        if (sockAddrIn.sin_addr.s_addr == INADDR_NONE)
        {
            struct hostent *hp;
            hp = gethostbyname(osHost);
            if (hp == nullptr)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Unknown host : %s", osHost.c_str());
                WSACleanup();
                return nullptr;
            }
            else
            {
                sockAddrIn.sin_family = hp->h_addrtype;
                memcpy(&(sockAddrIn.sin_addr.s_addr), hp->h_addr, hp->h_length);
            }
        }
        sockAddrIn.sin_port = htons(nPort);

        nConnSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
        if (nConnSocket == INVALID_SOCKET)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "socket() failed with error: %d", WSAGetLastError());
            WSACleanup();
            return nullptr;
        }

        if (connect(nConnSocket, (const SOCKADDR *)&sockAddrIn, sizeof (sockAddrIn)) == SOCKET_ERROR )
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "connect() function failed with error: %d", WSAGetLastError());
            closesocket(nConnSocket);
            WSACleanup();
            return nullptr;
        }
#endif

        GDALServerSpawnedProcess* ssp = static_cast<GDALServerSpawnedProcess*>(
            CPLMalloc(sizeof(GDALServerSpawnedProcess)));
        ssp->sp = nullptr;
        ssp->p = GDALPipeBuild(nConnSocket);

        CPLDebug("GDAL", "Create spawned process %p", ssp);
        if( !GDALCheckServerVersion(ssp->p) )
        {
            GDALServerSpawnAsyncFinish(ssp);
            return nullptr;
        }
        return ssp;
    }

#ifndef WIN32
    VSIStatBuf sStat;
    if( VSIStat(pszSpawnServer, &sStat) == 0 && sStat.st_size == 0 )
    {
        int nConnSocket = socket(AF_UNIX, SOCK_STREAM, 0);
        if (nConnSocket >= 0)
        {
            struct sockaddr_un sockAddrUnix;
            sockAddrUnix.sun_family = AF_UNIX;
            CPLStrlcpy(sockAddrUnix.sun_path, pszSpawnServer, sizeof(sockAddrUnix.sun_path));

            if (connect(nConnSocket, reinterpret_cast<const SOCKADDR *>(&sockAddrUnix), sizeof (sockAddrUnix)) >= 0 )
            {
                GDALServerSpawnedProcess* ssp =
                    static_cast<GDALServerSpawnedProcess *>(
                        CPLMalloc(sizeof(GDALServerSpawnedProcess)));
                ssp->sp = nullptr;
                ssp->p = GDALPipeBuild(nConnSocket);

                CPLDebug("GDAL", "Create spawned process %p", ssp);
                if( !GDALCheckServerVersion(ssp->p) )
                {
                    GDALServerSpawnAsyncFinish(ssp);
                    return nullptr;
                }
                return ssp;
            }
            else
                closesocket(nConnSocket);
        }
    }
#endif

    if( EQUAL(pszSpawnServer, "YES") || EQUAL(pszSpawnServer, "ON") ||
        EQUAL(pszSpawnServer, "TRUE")  || EQUAL(pszSpawnServer, "1") )
        pszSpawnServer = "gdalserver";
#ifdef WIN32
    const char* apszGDALServer[] = { pszSpawnServer, "-stdinout", nullptr };
#else
    const char* apszGDALServer[] = { pszSpawnServer, "-pipe_in", "{pipe_in}", "-pipe_out", "{pipe_out}", nullptr };
    if( strstr(pszSpawnServer, "gdalserver") == nullptr )
        apszGDALServer[1] = nullptr;
#endif
    bool bCheckVersions = true;

    CPLSpawnedProcess* sp = nullptr;
#ifndef WIN32
    if( EQUAL(pszSpawnServer, "NO") || EQUAL(pszSpawnServer, "OFF") ||
        EQUAL(pszSpawnServer, "FALSE")  || EQUAL(pszSpawnServer, "0") )
    {
        sp = CPLSpawnAsync(GDALServerLoopForked, nullptr, TRUE, TRUE, FALSE, nullptr);
        bCheckVersions = false;
    }
    else
#endif
        sp = CPLSpawnAsync(nullptr, apszGDALServer, TRUE, TRUE, FALSE, nullptr);

    if( sp == nullptr )
        return nullptr;

    GDALServerSpawnedProcess* ssp =
        static_cast<GDALServerSpawnedProcess*>(CPLMalloc(sizeof(GDALServerSpawnedProcess)));
    ssp->sp = sp;
    ssp->p = GDALPipeBuild(sp);

    CPLDebug("GDAL", "Create spawned process %p", ssp);
    if( bCheckVersions && !GDALCheckServerVersion(ssp->p) )
    {
        GDALServerSpawnAsyncFinish(ssp);
        return nullptr;
    }
    return ssp;
}

/************************************************************************/
/*                        CPLErrOnlyRet()                               */
/************************************************************************/

static CPLErr CPLErrOnlyRet(GDALPipe* p)
{
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;

    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                         RunErrorHandler()                            */
/************************************************************************/

class GDALServerErrorDesc
{
    public:
        GDALServerErrorDesc(CPLErr eErrIn = CE_None,
                            CPLErrorNum nErrNoIn = CPLE_None,
                            const CPLString& osMsgIn = "") :
                eErr(eErrIn), nErrNo(nErrNoIn), osErrorMsg(osMsgIn) {}

        CPLErr    eErr;
        CPLErrorNum       nErrNo;
        CPLString osErrorMsg;
};

static void CPL_STDCALL RunErrorHandler(CPLErr eErr, CPLErrorNum nErrNo,
                                        const char* pszErrorMsg)
{
    GDALServerErrorDesc oDesc(eErr, nErrNo, pszErrorMsg);
    std::vector<GDALServerErrorDesc>* paoErrors =
        static_cast<std::vector<GDALServerErrorDesc>*>(CPLGetErrorHandlerUserData());
    if( paoErrors )
        paoErrors->push_back(oDesc);
}

/************************************************************************/
/*                        RunAsyncProgress()                            */
/************************************************************************/

static int CPL_STDCALL RunAsyncProgress(double dfComplete,
                                        const char *pszMessage,
                                        void *pProgressArg)
{
    /* We don't send the progress right now, since some drivers like ECW */
    /* call the progress callback from an helper thread, while calling methods */
    /* on the source dataset. So we could end up sending mixed content on the pipe */
    /* to the client. The best is to transmit the progress in a regularly called method */
    /* of the dataset, such as IReadBlock() / IRasterIO() */
    GDALServerAsyncProgress* asyncp = static_cast<GDALServerAsyncProgress*>(pProgressArg);
    CPLMutexHolderD(&(asyncp->hMutex));
    asyncp->bUpdated = true;
    asyncp->dfComplete = dfComplete;
    CPLFree(asyncp->pszProgressMsg);
    asyncp->pszProgressMsg = (pszMessage) ? CPLStrdup(pszMessage) : nullptr;
    return asyncp->bRet;
}

/************************************************************************/
/*                        RunSyncProgress()                             */
/************************************************************************/

static int CPL_STDCALL RunSyncProgress(double dfComplete,
                                       const char *pszMessage,
                                       void *pProgressArg)
{
    GDALPipe* p = static_cast<GDALPipe *>(pProgressArg);
    if( !GDALPipeWrite(p, INSTR_Progress) ||
        !GDALPipeWrite(p, dfComplete) ||
        !GDALPipeWrite(p, pszMessage) )
        return FALSE;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return FALSE;
    int bRet = FALSE;
    if( !GDALPipeRead(p, &bRet) )
        return FALSE;
    GDALConsumeErrors(p);
    return bRet;
}

/************************************************************************/
/*                         GDALServerInstance                           */
/************************************************************************/

class GDALServerInstance
{
    CPL_DISALLOW_COPY_ASSIGN(GDALServerInstance)

public:
    GDALPipe* p = nullptr;
    GDALDataset* poDS = nullptr;
    std::vector<GDALRasterBand*> aBands{};
    void* pBuffer = nullptr;
    int nBufferSize = 0;

        explicit GDALServerInstance(GDALPipe* p);
       ~GDALServerInstance();
};

GDALServerInstance::GDALServerInstance(GDALPipe* pIn) :
        p(pIn)
{
}

GDALServerInstance::~GDALServerInstance()
{
    CPLFree(pBuffer);

    if( poDS != nullptr )
    {
        delete poDS;
        poDS = nullptr;
    }
}

/************************************************************************/
/*                GDALServerLoopInstanceCreateFromSocket()              */
/************************************************************************/

void * GDALServerLoopInstanceCreateFromSocket(const CPL_SOCKET& nSocket)
{
#ifndef WIN32
    unsetenv("CPL_SHOW_MEM_STATS");
#endif
    CPLSetConfigOption("GDAL_API_PROXY", "NO");

    GDALPipe* p = GDALPipeBuild(nSocket);

    return new GDALServerInstance(p);
}

/************************************************************************/
/*                 GDALServerLoopInstanceRunIteration()                 */
/************************************************************************/

static int GDALServerLoopInternal(GDALServerInstance* poSrvInstance,
                                  GDALDataset* poSrcDS,
                                  GDALProgressFunc pfnProgress, void* pProgressData,
                                  int bIterateForever);

int    GDALServerLoopInstanceRunIteration(void* pInstance)
{
    GDALServerInstance* poSrvInstance = static_cast<GDALServerInstance*>(pInstance);
    int nRet = GDALServerLoopInternal(poSrvInstance, nullptr, nullptr, nullptr, FALSE);
    if( !poSrvInstance->p->bOK )
        nRet = FALSE;
    return nRet;
}

/************************************************************************/
/*                  GDALServerLoopInstanceDestroy()                     */
/************************************************************************/

void   GDALServerLoopInstanceDestroy(void* pInstance)
{
    GDALServerInstance* poSrvInstance = static_cast<GDALServerInstance*>(pInstance);
    GDALPipeFree(poSrvInstance->p);
    delete poSrvInstance;
}

/************************************************************************/
/*                         GDALServerLoop()                             */
/************************************************************************/

static int GDALServerLoop(GDALPipe* p,
                          GDALDataset* poSrcDS,
                          GDALProgressFunc pfnProgress, void* pProgressData)
{
    GDALServerInstance* poSrcInstance = new GDALServerInstance(p);
    int nRet = GDALServerLoopInternal(poSrcInstance, poSrcDS, pfnProgress, pProgressData, TRUE);
    delete poSrcInstance;

    return nRet;
}

/************************************************************************/
/*                         GDALServerLoopInternal()                     */
/************************************************************************/

static int GDALServerLoopInternal(GDALServerInstance* poSrvInstance,
                                  GDALDataset* poSrcDS,
                                  GDALProgressFunc pfnProgress, void* pProgressData,
                                  int bIterateForever)
{
    int nRet = 1;
    GDALDataset* poDS = poSrvInstance->poDS;
    std::vector<GDALServerErrorDesc> aoErrors;
    GDALServerAsyncProgress asyncp;
    memset(&asyncp, 0, sizeof(asyncp));
    asyncp.bRet = TRUE;
    void* pBuffer = poSrvInstance->pBuffer;
    int nBufferSize = poSrvInstance->nBufferSize;

    const char* pszOldVal = CPLGetConfigOption("GDAL_API_PROXY", nullptr);
    char* pszOldValDup = (pszOldVal) ? CPLStrdup(pszOldVal) : nullptr;
    CPLSetThreadLocalConfigOption("GDAL_API_PROXY", "OFF");

    if( poSrcDS == nullptr )
        CPLPushErrorHandlerEx(RunErrorHandler, &aoErrors);

    GDALPipe* p = poSrvInstance->p;

    // fprintf(stderr, "[%d] started\n", (int)getpid());
    int nIter = 0;
    //fprintf(stderr, "Beginning of loop: poSrcDS = %p, poDS = %p\n", poSrcDS, poDS);
    while(true)
    {
        nIter ++;
        if( !bIterateForever && nIter != 1 )
        {
            break;
        }

        int instr = 0;
        if( !GDALPipeRead(p, &instr) )
        {
            // fprintf(stderr, "[%d] instr failed\n", (int)getpid());
            break;
        }

#ifdef DEBUG_VERBOSE
        fprintf(stderr, "[%d] %s\n", (int)getpid(), (instr >= 0 && instr < INSTR_END) ? apszInstr[instr] : "unknown");/*ok*/
#endif

        GDALRasterBand* poBand = nullptr;

        if( instr == INSTR_EXIT )
        {
            if( poSrcDS == nullptr && poDS != nullptr )
            {
                delete poDS;
                poDS = nullptr;
                //fprintf(stderr, "INSTR_EXIT: poDS = %p\n", poDS);
                poSrvInstance->aBands.resize(0);
            }
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, TRUE);
            nRet = 0;
            break;
        }
        else if( instr == INSTR_EXIT_FAIL )
        {
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, TRUE);
            break;
        }
        else if( instr == INSTR_GetGDALVersion ||
                 instr == 0x01000000 )
        {
            /* Do not change this protocol ! */
            char bClientIsLSB = '\0';  // TODO(schwehr): bool char?
            char* pszClientVersion = nullptr;
            int nClientMajor, nClientMinor,
                nClientProtocolMajor, nClientProtocolMinor,
                nExtraBytes;
            if( !GDALPipeRead_nolength(p, 1, &bClientIsLSB) )
                break;
            if( bClientIsLSB != CPL_IS_LSB )
            {
                fprintf(stderr, "Server does not understand client endianness.\n");/*ok*/
                break;
            }

            if (!GDALPipeRead(p, &pszClientVersion) ||
                !GDALPipeRead(p, &nClientMajor) ||
                !GDALPipeRead(p, &nClientMinor) ||
                !GDALPipeRead(p, &nClientProtocolMajor) ||
                !GDALPipeRead(p, &nClientProtocolMinor) ||
                !GDALPipeRead(p, &nExtraBytes) )
            {
                CPLFree(pszClientVersion);
                break;
            }

            if( nExtraBytes > 0 )
            {
                void* pTemp = VSIMalloc(nExtraBytes);
                if( !pTemp )
                {
                    CPLFree(pszClientVersion);
                    break;
                }
                if( !GDALPipeRead_nolength(p, nExtraBytes, pTemp) )
                {
                    CPLFree(pszClientVersion);
                    CPLFree(pTemp);
                    break;
                }
                CPLFree(pTemp);
            }

            CPLFree(pszClientVersion);

            GDALPipeWrite(p, GDALVersionInfo("RELEASE_NAME"));
            GDALPipeWrite(p, GDAL_VERSION_MAJOR);
            GDALPipeWrite(p, GDAL_VERSION_MINOR);
            GDALPipeWrite(p, GDAL_CLIENT_SERVER_PROTOCOL_MAJOR);
            GDALPipeWrite(p, GDAL_CLIENT_SERVER_PROTOCOL_MINOR);
            GDALPipeWrite(p, 0); /* extra bytes */
            continue;
        }
        else if( instr == INSTR_SetConfigOption )
        {
            char *pszKey = nullptr;
            char *pszValue = nullptr;
            if( !GDALPipeRead(p, &pszKey) ||
                !GDALPipeRead(p, &pszValue) )
            {
                CPLFree(pszKey);
                break;
            }
            CPLSetConfigOption(pszKey, pszValue);
            CPLFree(pszKey);
            CPLFree(pszValue);
            continue;
        }
        else if( instr == INSTR_Progress )
        {
            double dfProgress = 0.0;
            char* pszProgressMsg = nullptr;
            if( !GDALPipeRead(p, &dfProgress) ||
                !GDALPipeRead(p, &pszProgressMsg) )
                break;
            CPLAssert( pfnProgress );
            // cppcheck-suppress nullPointer
            nRet = pfnProgress(dfProgress, pszProgressMsg, pProgressData);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, nRet);
            CPLFree(pszProgressMsg);
        }
        else if( instr == INSTR_Reset )
        {
            if( poSrcDS == nullptr && poDS != nullptr )
            {
                delete poDS;
                poDS = nullptr;
                //fprintf(stderr, "INSTR_Reset: poDS = %p\n", poDS);
                MyChdirRootDirectory();
                poSrvInstance->aBands.resize(0);
            }
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, TRUE);
        }
        else if( instr == INSTR_Open )
        {
            int nAccess;
            char* pszFilename = nullptr;
            char* pszCWD = nullptr;
            char** papszOpenOptions = nullptr;
            if( !GDALPipeRead(p, &nAccess) ||
                !GDALPipeRead(p, &pszFilename) ||
                !GDALPipeRead(p, &pszCWD) ||
                !GDALPipeRead(p, &papszOpenOptions) )
            {
                CPLFree(pszFilename);
                CPLFree(pszCWD);
                CSLDestroy(papszOpenOptions);
                break;
            }

            // This should not happen for clients that respect the (implied) protocol...
            if( poSrcDS == nullptr && poDS != nullptr && pszFilename != nullptr )
            {
                CPLFree(pszFilename);
                CPLFree(pszCWD);
                CSLDestroy(papszOpenOptions);

                GDALEmitEndOfJunkMarker(p);
                GDALPipeWrite(p, FALSE);

                GDALPipeWrite(p, 1); // 1 error
                GDALPipeWrite(p, CE_Failure);
                GDALPipeWrite(p, CPLE_NotSupported);
                GDALPipeWrite(p, "Only one dataset can be opened through a client connection");
                continue;
            }

            if( pszCWD != nullptr )
            {
                if( pszFilename )
                {
                    if( CPLIsFilenameRelative(pszFilename) )
                        MyChdir(pszCWD);
                }
                CPLFree(pszCWD);
            }
            if( poSrcDS != nullptr )
                poDS = poSrcDS;
            else if( poDS == nullptr && pszFilename != nullptr )
                poDS = GDALDataset::Open(pszFilename,
                                                 ((nAccess == GA_Update) ? GDAL_OF_UPDATE : 0) | GDAL_OF_SHARED,
                                                 nullptr,
                                                 papszOpenOptions,
                                                 nullptr);
            //fprintf(stderr, "INSTR_Open: poDS = %p\n", poDS);
            CPLFree(pszFilename);
            CSLDestroy(papszOpenOptions);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, poDS != nullptr);
            if( poDS != nullptr )
            {
                // cppcheck-suppress knownConditionTrueFalse
                CPLAssert(INSTR_END < 128);
                GByte abyCaps[16]; /* 16 * 8 = 128 */
                memset(abyCaps, 0, sizeof(abyCaps));
                /* We implement all known instructions (except marker ones) */
                for(int c = 1; c < INSTR_END; c++)
                {
                    if( c != INSTR_Band_First && c != INSTR_Band_End )
                        abyCaps[c / 8] |= (1 << (c % 8));
                }
                GDALPipeWrite(p, sizeof(abyCaps), abyCaps);
                GDALPipeWrite(p, poDS->GetDescription() );
                GDALDriver* poDriver = poDS->GetDriver();
                if( poDriver != nullptr )
                {
                    GDALPipeWrite(p, poDriver->GetDescription() );
                    char** papszItems = poDriver->GetMetadata();
                    for(int i = 0; papszItems[i] != nullptr; i++ )
                    {
                        char* pszKey = nullptr;
                        const char* pszVal = CPLParseNameValue(papszItems[i], &pszKey );
                        if( pszKey != nullptr )
                        {
                            GDALPipeWrite(p, pszKey );
                            GDALPipeWrite(p, pszVal );
                            CPLFree(pszKey);
                        }
                    }
                    GDALPipeWrite(p, static_cast<const char*>(nullptr));
                }
                else
                    GDALPipeWrite(p, static_cast<const char*>(nullptr));

                GDALPipeWrite(p, poDS->GetRasterXSize());
                GDALPipeWrite(p, poDS->GetRasterYSize());
                int nBands = poDS->GetRasterCount();
                GDALPipeWrite(p, nBands);
                int bAllSame = TRUE;
                GDALRasterBand* poFirstBand = nullptr;
                int nFBBlockXSize = 0;
                int nFBBlockYSize = 0;

                /* Check if all bands are identical */
                for( int i = 0; i < nBands; i++ )
                {
                    GDALRasterBand* poOtherBand = poDS->GetRasterBand(i+1);
                    if( strlen(poOtherBand->GetDescription()) > 0 )
                    {
                        bAllSame = FALSE;
                        break;
                    }
                    if( i == 0 )
                    {
                        poFirstBand = poOtherBand;
                        poOtherBand->GetBlockSize(&nFBBlockXSize, &nFBBlockYSize);
                    }
                    else
                    {
                        int nBlockXSize, nBlockYSize;
                        poOtherBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
                        if( poOtherBand->GetXSize() != poFirstBand->GetXSize() ||
                            poOtherBand->GetYSize() != poFirstBand->GetYSize() ||
                            poOtherBand->GetRasterDataType() != poFirstBand->GetRasterDataType() ||
                            nBlockXSize != nFBBlockXSize ||
                            nBlockYSize != nFBBlockYSize )
                        {
                            bAllSame = FALSE;
                            break;
                        }
                    }
                }

                /* Transmit bands */
                GDALPipeWrite(p, bAllSame);
                for( int i = 0; i < nBands; i++ )
                {
                    GDALRasterBand* poOtherBand = poDS->GetRasterBand(i+1);
                    if( i > 0 && bAllSame )
                        poSrvInstance->aBands.push_back(poOtherBand);
                    else
                        GDALPipeWrite(p, poSrvInstance->aBands, poOtherBand);
                }
            }
        }
        else if( instr == INSTR_Identify )
        {
            char* pszFilename = nullptr;
            char* pszCWD = nullptr;
            if( !GDALPipeRead(p, &pszFilename) ||
                pszFilename == nullptr ||
                !GDALPipeRead(p, &pszCWD) )
            {
                CPLFree(pszFilename);
                CPLFree(pszCWD);
                break;
            }

            if( pszCWD != nullptr )
            {
                if( pszFilename )
                {
                    if( CPLIsFilenameRelative(pszFilename) )
                        MyChdir(pszCWD);
                }
                CPLFree(pszCWD);
            }

            int bRet = GDALIdentifyDriver(pszFilename, nullptr) != nullptr;
            CPLFree(pszFilename);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, bRet);
            aoErrors.resize(0);
        }
        else if( instr == INSTR_Create )
        {
            char* pszFilename = nullptr;
            char* pszCWD = nullptr;
            int nXSize, nYSize, nBands, nDataType;
            char** papszOptions = nullptr;
            GDALDriver* poDriver = nullptr;
            if( !GDALPipeRead(p, &pszFilename) ||
                pszFilename == nullptr ||
                !GDALPipeRead(p, &pszCWD) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBands) ||
                !GDALPipeRead(p, &nDataType) ||
                !GDALPipeRead(p, &papszOptions) )
            {
                CPLFree(pszFilename);
                CPLFree(pszCWD);
                break;
            }

            if( pszCWD != nullptr )
            {
                if( pszFilename )
                {
                    if( CPLIsFilenameRelative(pszFilename) )
                        MyChdir(pszCWD);
                }
                CPLFree(pszCWD);
            }

            const char* pszDriver = CSLFetchNameValue(papszOptions, "SERVER_DRIVER");
            CPLString osDriver;
            if( pszDriver != nullptr )
            {
                osDriver = pszDriver;
                pszDriver = osDriver.c_str();
                poDriver = static_cast<GDALDriver*>(GDALGetDriverByName(pszDriver));
            }
            papszOptions = CSLSetNameValue(papszOptions, "SERVER_DRIVER", nullptr);
            if( poDriver != nullptr )
            {
                poDS = poDriver->Create(pszFilename, nXSize, nYSize, nBands,
                                        static_cast<GDALDataType>(nDataType),
                                        papszOptions);
                //fprintf(stderr, "INSTR_Create: poDS = %p\n", poDS);
            }
            else
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
                         (pszDriver) ? pszDriver : "(unknown)");

            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, poDS != nullptr);
            CPLFree(pszFilename);
            CSLDestroy(papszOptions);
        }
        else if( instr == INSTR_CreateCopy )
        {
            char* pszFilename = nullptr;
            char* pszSrcDescription = nullptr;
            char* pszCWD = nullptr;
            char** papszCreateOptions = nullptr;
            GDALDriver* poDriver = nullptr;
            int bStrict = FALSE;

            if( !GDALPipeRead(p, &pszFilename) ||
                pszFilename == nullptr ||
                !GDALPipeRead(p, &pszSrcDescription) ||
                !GDALPipeRead(p, &pszCWD) ||
                !GDALPipeRead(p, &bStrict) ||
                !GDALPipeRead(p, &papszCreateOptions) )
            {
                CPLFree(pszFilename);
                CPLFree(pszSrcDescription);
                CPLFree(pszCWD);
                break;
            }

            CPLFree(pszSrcDescription);

            if( !bIterateForever )
            {
                GDALPipeWrite(p, FALSE);

                GDALPipeWrite(p, 1); // 1 error
                GDALPipeWrite(p, CE_Failure);
                GDALPipeWrite(p, CPLE_NotSupported);
                GDALPipeWrite(p, "CreateCopy() not supported in -nofork mode (to avoid deadlocks)");

                CPLFree(pszFilename);
                CPLFree(pszCWD);
                CSLDestroy(papszCreateOptions);

                continue;
            }

            if( pszCWD != nullptr )
            {
                if( pszFilename )
                {
                    if( CPLIsFilenameRelative(pszFilename) )
                        MyChdir(pszCWD);
                }
                CPLFree(pszCWD);
            }

            const char* pszDriver = CSLFetchNameValue(papszCreateOptions, "SERVER_DRIVER");
            CPLString osDriver;
            if( pszDriver != nullptr )
            {
                osDriver = pszDriver;
                pszDriver = osDriver.c_str();
                poDriver = static_cast<GDALDriver*>(GDALGetDriverByName(pszDriver));
            }
            papszCreateOptions = CSLSetNameValue(papszCreateOptions, "SERVER_DRIVER", nullptr);
            GDALPipeWrite(p, poDriver != nullptr);
            if( poDriver != nullptr )
            {
                GDALClientDataset* l_poSrcDS = new GDALClientDataset(p);
                if( !l_poSrcDS->Init(nullptr, GA_ReadOnly, nullptr) )
                {
                    delete l_poSrcDS;
                    CPLFree(pszFilename);
                    CSLDestroy(papszCreateOptions);
                    break;
                }
                l_poSrcDS->AttachAsyncProgress(&asyncp);

                poDS = poDriver->CreateCopy(pszFilename, l_poSrcDS,
                                            bStrict, papszCreateOptions,
                                            RunAsyncProgress, &asyncp);
                //fprintf(stderr, "INSTR_CreateCopy: poDS = %p\n", poDS);

                int bProgressRet = l_poSrcDS->ProcessAsyncProgress();
                delete l_poSrcDS;

                if( !bProgressRet && poDS != nullptr )
                {
                    delete poDS;
                    poDS = nullptr;
                }

                if( !GDALEmitEXIT(p, (poDS != nullptr) ? INSTR_EXIT : INSTR_EXIT_FAIL) )
                    break;
            }
            else
                CPLError(CE_Failure, CPLE_AppDefined, "Cannot find driver %s",
                         (pszDriver) ? pszDriver : "(unknown)");

            CPLFree(pszFilename);
            CSLDestroy(papszCreateOptions);
        }
        else if( instr == INSTR_QuietDelete )
        {
            char* pszFilename = nullptr;
            char* pszCWD = nullptr;

            if( !GDALPipeRead(p, &pszFilename) ||
                pszFilename == nullptr ||
                !GDALPipeRead(p, &pszCWD) )
            {
                CPLFree(pszFilename);
                CPLFree(pszCWD);
                break;
            }

            if( pszCWD != nullptr )
            {
                if( pszFilename )
                {
                    if( CPLIsFilenameRelative(pszFilename) )
                        MyChdir(pszCWD);
                }
                CPLFree(pszCWD);
            }

            GDALDriver::QuietDelete(pszFilename);

            GDALEmitEndOfJunkMarker(p);
            CPLFree(pszFilename);
        }
        else if( instr == INSTR_AddBand )
        {
            if( poDS == nullptr )
                break;
            int nType;
            char** papszOptions = nullptr;
            if( !GDALPipeRead(p, &nType) ||
                !GDALPipeRead(p, &papszOptions) )
                break;
            CPLErr eErr = poDS->AddBand(static_cast<GDALDataType>(nType), papszOptions);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr == CE_None )
            {
                int nBandCount = poDS->GetRasterCount();
                GDALPipeWrite(p, poSrvInstance->aBands, poDS->GetRasterBand(nBandCount));
            }
            CSLDestroy(papszOptions);
        }
        else if( instr == INSTR_GetGeoTransform )
        {
            if( poDS == nullptr )
                break;
            double adfGeoTransform[6];
            CPLErr eErr = poDS->GetGeoTransform(adfGeoTransform);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr != CE_Failure )
            {
                GDALPipeWrite(p, 6 * sizeof(double), adfGeoTransform);
            }
        }
        else if( instr == INSTR_SetGeoTransform )
        {
            if( poDS == nullptr )
                break;
            double adfGeoTransform[6];
            if( !GDALPipeRead(p, 6 * sizeof(double), adfGeoTransform) )
                break;
            CPLErr eErr = poDS->SetGeoTransform(adfGeoTransform);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_GetProjectionRef )
        {
            if( poDS == nullptr )
                break;
            const char* pszVal = poDS->GetProjectionRef();
            //GDALPipeWrite(p, strlen("some_junk\xDE"), "some_junk\xDE");
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, pszVal);
        }
        else if( instr == INSTR_SetProjection )
        {
            if( poDS == nullptr )
                break;
            char* pszProjection = nullptr;
            if( !GDALPipeRead(p, &pszProjection) )
                break;
            CPLErr eErr = poDS->SetProjection(pszProjection);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(pszProjection);
        }
        else if( instr == INSTR_GetGCPCount )
        {
            if( poDS == nullptr )
                break;
            int nGCPCount = poDS->GetGCPCount();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, nGCPCount );
        }
        else if( instr == INSTR_GetGCPProjection )
        {
            if( poDS == nullptr )
                break;
            const char* pszVal = poDS->GetGCPProjection();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, pszVal);
        }
        else if( instr == INSTR_GetGCPs )
        {
            if( poDS == nullptr )
                break;
            int nGCPCount = poDS->GetGCPCount();
            const GDAL_GCP* pasGCPs = poDS->GetGCPs();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, nGCPCount, pasGCPs);
        }
        else if( instr == INSTR_SetGCPs )
        {
            if( poDS == nullptr )
                break;
            int nGCPCount;
            GDAL_GCP* pasGCPs = nullptr;
            if( !GDALPipeRead(p, &nGCPCount, &pasGCPs) )
                break;
            char* pszGCPProjection = nullptr;
            if( !GDALPipeRead(p, &pszGCPProjection) )
            {
                GDALDeinitGCPs(nGCPCount, pasGCPs);
                CPLFree(pasGCPs);
                break;
            }
            CPLErr eErr = poDS->SetGCPs(nGCPCount, pasGCPs, pszGCPProjection);
            GDALDeinitGCPs(nGCPCount, pasGCPs);
            CPLFree(pasGCPs);
            CPLFree(pszGCPProjection);

            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr );
        }
        else if( instr == INSTR_GetFileList )
        {
            if( poDS == nullptr )
                break;
            char** papszFileList = poDS->GetFileList();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, papszFileList);
            CSLDestroy(papszFileList);
        }
        else if( instr == INSTR_FlushCache )
        {
            if( poDS )
                poDS->FlushCache();
            GDALEmitEndOfJunkMarker(p);
        }
        /*else if( instr == INSTR_SetDescription )
        {
            if( poDS == nullptr )
                break;
            char* pszDescription = nullptr;
            if( !GDALPipeRead(p, &pszDescription) )
                break;
            poDS->SetDescription(pszDescription);
            CPLFree(pszDescription);
            GDALEmitEndOfJunkMarker(p);
        }*/
        else if( instr == INSTR_GetMetadata )
        {
            if( poDS == nullptr )
                break;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &pszDomain) )
                break;
            char** papszMD = poDS->GetMetadata(pszDomain);
            GDALEmitEndOfJunkMarker(p);
            CPLFree(pszDomain);
            GDALPipeWrite(p, papszMD);
        }
        else if( instr == INSTR_GetMetadataItem )
        {
            if( poDS == nullptr )
                break;
            char* pszName = nullptr;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &pszName) ||
                !GDALPipeRead(p, &pszDomain) )
            {
                CPLFree(pszName);
                CPLFree(pszDomain);
                break;
            }
            const char* pszVal = poDS->GetMetadataItem(pszName, pszDomain);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, pszVal);
            CPLFree(pszName);
            CPLFree(pszDomain);
        }
        else if( instr == INSTR_SetMetadata )
        {
            if( poDS == nullptr )
                break;
            char** papszMetadata = nullptr;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &papszMetadata) ||
                !GDALPipeRead(p, &pszDomain) )
            {
                CSLDestroy(papszMetadata);
                CPLFree(pszDomain);
                break;
            }
            CPLErr eErr = poDS->SetMetadata(papszMetadata, pszDomain);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CSLDestroy(papszMetadata);
            CPLFree(pszDomain);
        }
        else if( instr == INSTR_SetMetadataItem )
        {
            if( poDS == nullptr )
                break;
            char* pszName = nullptr;
            char* pszValue = nullptr;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &pszName) ||
                !GDALPipeRead(p, &pszValue) ||
                !GDALPipeRead(p, &pszDomain) )
            {
                CPLFree(pszName);
                CPLFree(pszValue);
                CPLFree(pszDomain);
                break;
            }
            CPLErr eErr = poDS->SetMetadataItem(pszName, pszValue, pszDomain);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(pszName);
            CPLFree(pszValue);
            CPLFree(pszDomain);
        }
        else if( instr == INSTR_IBuildOverviews )
        {
            if( poDS == nullptr )
                break;
            char* pszResampling = nullptr;
            int nOverviews;
            int* panOverviewList = nullptr;
            int nListBands;
            int* panBandList = nullptr;
            if( !GDALPipeRead(p, &pszResampling) ||
                !GDALPipeRead(p, &nOverviews) ||
                !GDALPipeRead(p, nOverviews, &panOverviewList) ||
                !GDALPipeRead(p, &nListBands) ||
                !GDALPipeRead(p, nListBands, &panBandList) )
            {
                CPLFree(pszResampling);
                CPLFree(panOverviewList);
                CPLFree(panBandList);
                break;
            }

            CPLErr eErr = poDS->BuildOverviews(pszResampling,
                                               nOverviews, panOverviewList,
                                               nListBands, panBandList,
                                               RunSyncProgress, p);

            CPLFree(pszResampling);
            CPLFree(panOverviewList);
            CPLFree(panBandList);

            if( !GDALEmitEXIT(p, (eErr != CE_Failure) ? INSTR_EXIT : INSTR_EXIT_FAIL) )
                break;
        }
        else if( instr == INSTR_AdviseRead )
        {
            if( poDS == nullptr )
                break;
            int nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize;
            int nDT;
            int nBandCount;
            int *panBandList = nullptr;
            char** papszOptions = nullptr;
            int nLength = 0;
            if( !GDALPipeRead(p, &nXOff) ||
                !GDALPipeRead(p, &nYOff) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBufXSize) ||
                !GDALPipeRead(p, &nBufYSize) ||
                !GDALPipeRead(p, &nDT) ||
                !GDALPipeRead(p, &nBandCount) ||
                !GDALPipeRead(p, &nLength) )
            {
                break;
            }

            /* panBandList can be NULL, hence the following test */
            /* to check if we have band numbers to actually read */
            if( nLength != 0 )
            {
                if( nLength != static_cast<int>(sizeof(int)) * nBandCount )
                {
                    break;
                }

                panBandList = static_cast<int*>(VSIMalloc(nLength));
                if( panBandList == nullptr )
                    break;

                if( !GDALPipeRead_nolength(p, nLength, static_cast<void*>(panBandList)) )
                {
                    VSIFree(panBandList);
                    break;
                }
            }

            if (!GDALPipeRead(p, &papszOptions) )
            {
                CPLFree(panBandList);
                CSLDestroy(papszOptions);
                break;
            }

            CPLErr eErr = poDS->AdviseRead(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
                                           static_cast<GDALDataType>(nDT),
                                           nBandCount, panBandList,
                                           papszOptions);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(panBandList);
            CSLDestroy(papszOptions);
        }
        else if( instr == INSTR_IRasterIO_Read )
        {
            if( poDS == nullptr )
                break;
            int nXOff, nYOff, nXSize, nYSize;
            int nBufXSize, nBufYSize;
            GDALDataType eBufType;
            int nBufType;
            int nBandCount;
            GSpacing nPixelSpace, nLineSpace, nBandSpace;
            int* panBandMap = nullptr;
            if( !GDALPipeRead(p, &nXOff) ||
                !GDALPipeRead(p, &nYOff) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBufXSize) ||
                !GDALPipeRead(p, &nBufYSize) ||
                !GDALPipeRead(p, &nBufType) ||
                !GDALPipeRead(p, &nBandCount) ||
                !GDALPipeRead(p, nBandCount, &panBandMap) ||
                !GDALPipeRead(p, &nPixelSpace) ||
                !GDALPipeRead(p, &nLineSpace) ||
                !GDALPipeRead(p, &nBandSpace) )
            {
                CPLFree(panBandMap);
                break;
            }
            /* Note: only combinations of nPixelSpace, nLineSpace and
               nBandSpace that lead to compatible band-interleaved or pixel-
               interleaved buffers are valid. Other combinations will lead
               to segfaults. */
            eBufType = static_cast<GDALDataType>(nBufType);
            const int nSize = nBufXSize * nBufYSize * nBandCount *
                GDALGetDataTypeSizeBytes(eBufType);
            if( nSize > nBufferSize )
            {
                nBufferSize = nSize;
                pBuffer = CPLRealloc(pBuffer, nSize);
            }

            CPLErr eErr = poDS->RasterIO(GF_Read,
                                         nXOff, nYOff, nXSize, nYSize,
                                         pBuffer, nBufXSize, nBufYSize,
                                         eBufType,
                                         nBandCount, panBandMap,
                                         nPixelSpace, nLineSpace, nBandSpace,
                                         nullptr);
            CPLFree(panBandMap);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr != CE_Failure )
                GDALPipeWrite(p, nSize, pBuffer);
        }
        else if( instr == INSTR_IRasterIO_Write )
        {
            if( poDS == nullptr )
                break;
            int nXOff, nYOff, nXSize, nYSize;
            int nBufXSize, nBufYSize;
            GDALDataType eBufType;
            int nBufType;
            int nBandCount;
            GSpacing nPixelSpace, nLineSpace, nBandSpace;
            int* panBandMap = nullptr;
            if( !GDALPipeRead(p, &nXOff) ||
                !GDALPipeRead(p, &nYOff) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBufXSize) ||
                !GDALPipeRead(p, &nBufYSize) ||
                !GDALPipeRead(p, &nBufType) ||
                !GDALPipeRead(p, &nBandCount) ||
                !GDALPipeRead(p, nBandCount, &panBandMap) ||
                !GDALPipeRead(p, &nPixelSpace) ||
                !GDALPipeRead(p, &nLineSpace) ||
                !GDALPipeRead(p, &nBandSpace) )
            {
                CPLFree(panBandMap);
                break;
            }
            /* Note: only combinations of nPixelSpace, nLineSpace and
               nBandSpace that lead to compatible band-interleaved or pixel-
               interleaved buffers are valid. Other combinations will lead
               to segfaults. */
            eBufType = static_cast<GDALDataType>(nBufType);
            const int nExpectedSize = nBufXSize * nBufYSize * nBandCount *
                GDALGetDataTypeSizeBytes(eBufType);
            int nSize;
            if( !GDALPipeRead(p, &nSize) || nSize != nExpectedSize )
            {
                CPLFree(panBandMap);
                break;
            }
            if( nSize > nBufferSize )
            {
                nBufferSize = nSize;
                pBuffer = CPLRealloc(pBuffer, nSize);
            }
            if( !GDALPipeRead_nolength(p, nSize, pBuffer) )
            {
                CPLFree(panBandMap);
                break;
            }

            CPLErr eErr = poDS->RasterIO(GF_Write,
                                         nXOff, nYOff, nXSize, nYSize,
                                         pBuffer, nBufXSize, nBufYSize,
                                         eBufType,
                                         nBandCount, panBandMap,
                                         nPixelSpace, nLineSpace, nBandSpace,
                                         nullptr);
            CPLFree(panBandMap);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_CreateMaskBand )
        {
            if( poDS == nullptr )
                break;
            int nFlags;
            if( !GDALPipeRead(p, &nFlags) )
                break;
            CPLErr eErr = poDS->CreateMaskBand(nFlags);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr > INSTR_Band_First && instr < INSTR_Band_End )
        {
            int iBand;
            if( !GDALPipeRead(p, &iBand) )
                break;
            if( iBand < 0 || iBand >= static_cast<int>(poSrvInstance->aBands.size()) )
                break;
            poBand = poSrvInstance->aBands[iBand];
        }
        else
            break;

        if( instr == INSTR_Band_FlushCache )
        {
            CPLErr eErr = poBand->FlushCache();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_GetCategoryNames )
        {
            char** papszCategoryNames = poBand->GetCategoryNames();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, papszCategoryNames);
        }
        else if( instr == INSTR_Band_SetCategoryNames )
        {
            char** papszCategoryNames = nullptr;
            if( !GDALPipeRead(p, &papszCategoryNames) )
                break;
            CPLErr eErr = poBand->SetCategoryNames(papszCategoryNames);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CSLDestroy(papszCategoryNames);
        }
        else if( instr == INSTR_Band_SetDescription )
        {
            char* pszDescription = nullptr;
            if( !GDALPipeRead(p, &pszDescription) )
                break;
            poBand->SetDescription(pszDescription);
            CPLFree(pszDescription);
            GDALEmitEndOfJunkMarker(p);
        }
        else if( instr == INSTR_Band_GetMetadata )
        {
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &pszDomain) )
                break;
            char** papszMD = poBand->GetMetadata(pszDomain);
            GDALEmitEndOfJunkMarker(p);
            CPLFree(pszDomain);
            GDALPipeWrite(p, papszMD);
        }
        else if( instr == INSTR_Band_GetMetadataItem )
        {
            char* pszName = nullptr;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &pszName) ||
                !GDALPipeRead(p, &pszDomain) )
            {
                CPLFree(pszName);
                CPLFree(pszDomain);
                break;
            }
            const char* pszVal = poBand->GetMetadataItem(pszName, pszDomain);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, pszVal);
            CPLFree(pszName);
            CPLFree(pszDomain);
        }
        else if( instr == INSTR_Band_SetMetadata )
        {
            char** papszMetadata = nullptr;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &papszMetadata) ||
                !GDALPipeRead(p, &pszDomain) )
            {
                CSLDestroy(papszMetadata);
                CPLFree(pszDomain);
                break;
            }
            CPLErr eErr = poBand->SetMetadata(papszMetadata, pszDomain);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CSLDestroy(papszMetadata);
            CPLFree(pszDomain);
        }
        else if( instr == INSTR_Band_SetMetadataItem )
        {
            char* pszName = nullptr;
            char* pszValue = nullptr;
            char* pszDomain = nullptr;
            if( !GDALPipeRead(p, &pszName) ||
                !GDALPipeRead(p, &pszValue) ||
                !GDALPipeRead(p, &pszDomain) )
            {
                CPLFree(pszName);
                CPLFree(pszValue);
                CPLFree(pszDomain);
                break;
            }
            CPLErr eErr = poBand->SetMetadataItem(pszName, pszValue, pszDomain);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(pszName);
            CPLFree(pszValue);
            CPLFree(pszDomain);
        }
        else if( instr == INSTR_Band_GetColorInterpretation )
        {
            GDALColorInterp eInterp = poBand->GetColorInterpretation();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eInterp);
        }
        else if( instr == INSTR_Band_SetColorInterpretation )
        {
            int nVal;
            if( !GDALPipeRead(p, &nVal ) )
                break;
            CPLErr eErr = poBand->SetColorInterpretation(static_cast<GDALColorInterp>(nVal));
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_GetNoDataValue )
        {
            int bSuccess;
            double dfVal = poBand->GetNoDataValue(&bSuccess);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, bSuccess);
            GDALPipeWrite(p, dfVal);
        }
        else if( instr == INSTR_Band_GetMinimum )
        {
            int bSuccess;
            double dfVal = poBand->GetMinimum(&bSuccess);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, bSuccess);
            GDALPipeWrite(p, dfVal);
        }
        else if( instr == INSTR_Band_GetMaximum )
        {
            int bSuccess;
            double dfVal = poBand->GetMaximum(&bSuccess);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, bSuccess);
            GDALPipeWrite(p, dfVal);
        }
        else if( instr == INSTR_Band_GetScale )
        {
            int bSuccess;
            double dfVal = poBand->GetScale(&bSuccess);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, bSuccess);
            GDALPipeWrite(p, dfVal);
        }
        else if( instr == INSTR_Band_GetOffset )
        {
            int bSuccess;
            double dfVal = poBand->GetOffset(&bSuccess);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, bSuccess);
            GDALPipeWrite(p, dfVal);
        }
        else if( instr == INSTR_Band_SetNoDataValue )
        {
            double dfVal;
            if( !GDALPipeRead(p, &dfVal ) )
                break;
            CPLErr eErr = poBand->SetNoDataValue(dfVal);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_DeleteNoDataValue )
        {
            CPLErr eErr = poBand->DeleteNoDataValue();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_SetOffset )
        {
            double dfVal;
            if( !GDALPipeRead(p, &dfVal ) )
                break;
            CPLErr eErr = poBand->SetOffset(dfVal);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_SetScale )
        {
            double dfVal;
            if( !GDALPipeRead(p, &dfVal ) )
                break;
            CPLErr eErr = poBand->SetScale(dfVal);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_IReadBlock )
        {
            int nBlockXOff, nBlockYOff;
            if( !GDALPipeRead(p, &nBlockXOff) ||
                !GDALPipeRead(p, &nBlockYOff) )
                break;
            int nBlockXSize, nBlockYSize;
            poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
            const int nSize = nBlockXSize * nBlockYSize *
                GDALGetDataTypeSizeBytes(poBand->GetRasterDataType());
            if( nSize > nBufferSize )
            {
                nBufferSize = nSize;
                pBuffer = CPLRealloc(pBuffer, nSize);
            }
            CPLErr eErr = poBand->ReadBlock(nBlockXOff, nBlockYOff, pBuffer);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            GDALPipeWrite(p, nSize, pBuffer);
        }
        else if( instr == INSTR_Band_IWriteBlock )
        {
            int nBlockXOff, nBlockYOff, nSize;
            if( !GDALPipeRead(p, &nBlockXOff) ||
                !GDALPipeRead(p, &nBlockYOff) ||
                !GDALPipeRead(p, &nSize) )
                break;
            int nBlockXSize, nBlockYSize;
            poBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
            const int nExpectedSize = nBlockXSize * nBlockYSize *
                GDALGetDataTypeSizeBytes(poBand->GetRasterDataType());
            if( nExpectedSize != nSize )
                break;
            if( nSize > nBufferSize )
            {
                nBufferSize = nSize;
                pBuffer = CPLRealloc(pBuffer, nSize);
            }
            if( !GDALPipeRead_nolength(p, nSize, pBuffer) )
                break;

            CPLErr eErr = poBand->WriteBlock(nBlockXOff, nBlockYOff, pBuffer);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_IRasterIO_Read )
        {
            int nXOff, nYOff, nXSize, nYSize;
            int nBufXSize, nBufYSize;
            GDALDataType eBufType;
            int nBufType;
            if( !GDALPipeRead(p, &nXOff) ||
                !GDALPipeRead(p, &nYOff) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBufXSize) ||
                !GDALPipeRead(p, &nBufYSize) ||
                !GDALPipeRead(p, &nBufType) )
                break;
            eBufType = static_cast<GDALDataType>(nBufType);
            const int nSize = nBufXSize * nBufYSize *
                GDALGetDataTypeSizeBytes(eBufType);
            if( nSize > nBufferSize )
            {
                nBufferSize = nSize;
                pBuffer = CPLRealloc(pBuffer, nSize);
            }

            CPLErr eErr = poBand->RasterIO(GF_Read,
                                           nXOff, nYOff, nXSize, nYSize,
                                           pBuffer, nBufXSize, nBufYSize,
                                           eBufType, 0, 0, nullptr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            GDALPipeWrite(p, nSize, pBuffer);
        }
        else if( instr == INSTR_Band_IRasterIO_Write )
        {
            int nXOff, nYOff, nXSize, nYSize;
            int nBufXSize, nBufYSize;
            GDALDataType eBufType;
            int nBufType;
            if( !GDALPipeRead(p, &nXOff) ||
                !GDALPipeRead(p, &nYOff) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBufXSize) ||
                !GDALPipeRead(p, &nBufYSize) ||
                !GDALPipeRead(p, &nBufType) )
                break;
            eBufType = static_cast<GDALDataType>(nBufType);
            const int nExpectedSize = nBufXSize * nBufYSize *
                GDALGetDataTypeSizeBytes(eBufType);
            int nSize;
            if( !GDALPipeRead(p, &nSize) )
                break;
            if( nSize != nExpectedSize )
                break;
            if( nSize > nBufferSize )
            {
                nBufferSize = nSize;
                pBuffer = CPLRealloc(pBuffer, nSize);
            }
            if( !GDALPipeRead_nolength(p, nSize, pBuffer) )
                break;

            CPLErr eErr = poBand->RasterIO(GF_Write,
                                           nXOff, nYOff, nXSize, nYSize,
                                           pBuffer, nBufXSize, nBufYSize,
                                           eBufType, 0, 0, nullptr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_GetStatistics )
        {
            int bApproxOK, bForce;
            if( !GDALPipeRead(p, &bApproxOK) ||
                !GDALPipeRead(p, &bForce) )
                break;
            double dfMin = 0.0;
            double dfMax = 0.0;
            double dfMean = 0.0;
            double dfStdDev = 0.0;
            CPLErr eErr = poBand->GetStatistics(bApproxOK, bForce,
                                                &dfMin, &dfMax, &dfMean, &dfStdDev);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr == CE_None )
            {
                GDALPipeWrite(p, dfMin);
                GDALPipeWrite(p, dfMax);
                GDALPipeWrite(p, dfMean);
                GDALPipeWrite(p, dfStdDev);
            }
        }
        else if( instr == INSTR_Band_ComputeStatistics )
        {
            int bApproxOK;
            if( !GDALPipeRead(p, &bApproxOK) )
                break;
            double dfMin = 0.0;
            double dfMax = 0.0;
            double dfMean = 0.0;
            double dfStdDev = 0.0;
            CPLErr eErr = poBand->ComputeStatistics(bApproxOK,
                                                    &dfMin, &dfMax, &dfMean, &dfStdDev,
                                                    nullptr, nullptr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr != CE_Failure )
            {
                GDALPipeWrite(p, dfMin);
                GDALPipeWrite(p, dfMax);
                GDALPipeWrite(p, dfMean);
                GDALPipeWrite(p, dfStdDev);
            }
        }
        else if( instr == INSTR_Band_SetStatistics )
        {
            double dfMin, dfMax, dfMean, dfStdDev;
            if( !GDALPipeRead(p, &dfMin) ||
                !GDALPipeRead(p, &dfMax) ||
                !GDALPipeRead(p, &dfMean) ||
                !GDALPipeRead(p, &dfStdDev) )
                break;
            CPLErr eErr = poBand->SetStatistics(dfMin, dfMax, dfMean, dfStdDev);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_ComputeRasterMinMax )
        {
            int bApproxOK;
            if( !GDALPipeRead(p, &bApproxOK) )
                break;
            double adfMinMax[2];
            CPLErr eErr = poBand->ComputeRasterMinMax(bApproxOK, adfMinMax);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr != CE_Failure )
            {
                GDALPipeWrite(p, adfMinMax[0]);
                GDALPipeWrite(p, adfMinMax[1]);
            }
        }
        else if( instr == INSTR_Band_GetHistogram )
        {
            double dfMin, dfMax;
            int nBuckets, bIncludeOutOfRange, bApproxOK;
            if( !GDALPipeRead(p, &dfMin) ||
                !GDALPipeRead(p, &dfMax) ||
                !GDALPipeRead(p, &nBuckets) ||
                !GDALPipeRead(p, &bIncludeOutOfRange) ||
                !GDALPipeRead(p, &bApproxOK) )
                break;
            GUIntBig* panHistogram = static_cast<GUIntBig*>(
                VSIMalloc2(sizeof(GUIntBig), nBuckets));
            if( panHistogram == nullptr )
                break;
            CPLErr eErr = poBand->GetHistogram(dfMin, dfMax,
                                     nBuckets, panHistogram,
                                     bIncludeOutOfRange, bApproxOK, nullptr, nullptr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr != CE_Failure )
            {
                GDALPipeWrite(p, nBuckets * static_cast<int>(sizeof(GUIntBig)), panHistogram);
            }
            CPLFree(panHistogram);
        }
        else if( instr == INSTR_Band_GetDefaultHistogram )
        {
            double dfMin, dfMax;
            int nBuckets;
            int bForce;
            if( !GDALPipeRead(p, &bForce) )
                break;
            GUIntBig* panHistogram = nullptr;
            CPLErr eErr = poBand->GetDefaultHistogram(&dfMin, &dfMax,
                                                      &nBuckets, &panHistogram,
                                                      bForce, nullptr, nullptr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            if( eErr != CE_Failure )
            {
                GDALPipeWrite(p, dfMin);
                GDALPipeWrite(p, dfMax);
                GDALPipeWrite(p, nBuckets);
                GDALPipeWrite(p, nBuckets * static_cast<int>(sizeof(GUIntBig)) , panHistogram);
            }
            CPLFree(panHistogram);
        }
        else if( instr == INSTR_Band_SetDefaultHistogram )
        {
            double dfMin, dfMax;
            int nBuckets;
            GUIntBig* panHistogram = nullptr;
            if( !GDALPipeRead(p, &dfMin) ||
                !GDALPipeRead(p, &dfMax) ||
                !GDALPipeRead(p, &nBuckets) ||
                !GDALPipeRead(p, nBuckets, &panHistogram) )
            {
                CPLFree(panHistogram);
                break;
            }
            CPLErr eErr = poBand->SetDefaultHistogram(dfMin, dfMax,
                                                      nBuckets, panHistogram);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(panHistogram);
        }
        else if( instr == INSTR_Band_HasArbitraryOverviews )
        {
            int nVal = poBand->HasArbitraryOverviews();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, nVal);
        }
        else if( instr == INSTR_Band_GetOverviewCount )
        {
            int nVal = poBand->GetOverviewCount();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, nVal);
        }
        else if( instr == INSTR_Band_GetOverview )
        {
            int iOvr;
            if( !GDALPipeRead(p, &iOvr) )
                break;
            GDALRasterBand* poOvrBand = poBand->GetOverview(iOvr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, poSrvInstance->aBands, poOvrBand);
        }
        else if( instr == INSTR_Band_GetMaskBand )
        {
            GDALRasterBand* poMaskBand = poBand->GetMaskBand();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, poSrvInstance->aBands, poMaskBand);
        }
        else if( instr == INSTR_Band_GetMaskFlags )
        {
            int nVal = poBand->GetMaskFlags();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, nVal);
        }
        else if( instr == INSTR_Band_CreateMaskBand )
        {
            int nFlags;
            if( !GDALPipeRead(p, &nFlags) )
                break;
            CPLErr eErr = poBand->CreateMaskBand(nFlags);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_Fill )
        {
            double dfReal, dfImag;
            if( !GDALPipeRead(p, &dfReal) ||
                !GDALPipeRead(p, &dfImag) )
                break;
            CPLErr eErr = poBand->Fill(dfReal, dfImag);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_GetColorTable )
        {
            GDALColorTable* poColorTable = poBand->GetColorTable();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, poColorTable);
        }
        else if( instr == INSTR_Band_SetColorTable )
        {
            GDALColorTable* poColorTable = nullptr;
            if( !GDALPipeRead(p, &poColorTable) )
                break;
            CPLErr eErr = poBand->SetColorTable(poColorTable);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            delete poColorTable;
        }
        else if( instr == INSTR_Band_GetUnitType )
        {
            const char* pszVal = poBand->GetUnitType();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, pszVal);
        }
        else if( instr == INSTR_Band_SetUnitType )
        {
            char* pszUnitType = nullptr;
            if( !GDALPipeRead(p, &pszUnitType) )
                break;
            CPLErr eErr = poBand->SetUnitType(pszUnitType);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(pszUnitType);
        }
        else if( instr == INSTR_Band_BuildOverviews )
        {
            char* pszResampling = nullptr;
            int nOverviews;
            int* panOverviewList = nullptr;
            if( !GDALPipeRead(p, &pszResampling) ||
                !GDALPipeRead(p, &nOverviews) ||
                !GDALPipeRead(p, nOverviews, &panOverviewList) )
            {
                CPLFree(pszResampling);
                CPLFree(panOverviewList);
                break;
            }
            CPLErr eErr = poBand->BuildOverviews(pszResampling, nOverviews,
                                                 panOverviewList, nullptr, nullptr);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CPLFree(pszResampling);
            CPLFree(panOverviewList);
        }
        else if( instr == INSTR_Band_GetDefaultRAT )
        {
            const GDALRasterAttributeTable* poRAT = poBand->GetDefaultRAT();
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, poRAT);
        }
        else if( instr == INSTR_Band_SetDefaultRAT )
        {
            GDALRasterAttributeTable* poRAT = nullptr;
            if( !GDALPipeRead(p, &poRAT) )
                break;
            CPLErr eErr = poBand->SetDefaultRAT(poRAT);
            delete poRAT;

            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
        }
        else if( instr == INSTR_Band_AdviseRead )
        {
            int nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize;
            int nDT;
            char** papszOptions = nullptr;
            if( !GDALPipeRead(p, &nXOff) ||
                !GDALPipeRead(p, &nYOff) ||
                !GDALPipeRead(p, &nXSize) ||
                !GDALPipeRead(p, &nYSize) ||
                !GDALPipeRead(p, &nBufXSize) ||
                !GDALPipeRead(p, &nBufYSize) ||
                !GDALPipeRead(p, &nDT) ||
                !GDALPipeRead(p, &papszOptions) )
                break;
            CPLErr eErr = poBand->AdviseRead(nXOff, nYOff, nXSize, nYSize, nBufXSize, nBufYSize,
                                             static_cast<GDALDataType>(nDT), papszOptions);
            GDALEmitEndOfJunkMarker(p);
            GDALPipeWrite(p, eErr);
            CSLDestroy(papszOptions);
        }

        if( poSrcDS == nullptr )
        {
            GDALPipeWrite(p, static_cast<int>(aoErrors.size()));
            for( const auto& oError: aoErrors )
            {
                GDALPipeWrite(p, oError.eErr);
                GDALPipeWrite(p, oError.nErrNo);
                GDALPipeWrite(p, oError.osErrorMsg);
            }
            aoErrors.resize(0);
        }
        else
            GDALPipeWrite(p, 0);
    }

    if( !bIterateForever )
        GDALPipeFlushBuffer(p);

    if( poSrcDS == nullptr )
        CPLPopErrorHandler();

    CPLSetThreadLocalConfigOption("GDAL_API_PROXY", pszOldValDup);
    CPLFree(pszOldValDup);

    // fprintf(stderr, "[%d] finished = %d\n", (int)getpid(), nRet);
    //fprintf(stderr, "End of loop: poSrcDS = %p, poDS = %p\n", poSrcDS, poDS);

    if( poSrcDS == nullptr )
        poSrvInstance->poDS = poDS;
    else
        poSrvInstance->aBands.resize(0);
    poSrvInstance->pBuffer = pBuffer;
    poSrvInstance->nBufferSize = nBufferSize;

    CPLFree(asyncp.pszProgressMsg);
    if( asyncp.hMutex )
        CPLDestroyMutex(asyncp.hMutex);

    return nRet;
}

/************************************************************************/
/*                         GDALServerLoop()                             */
/************************************************************************/

int GDALServerLoop(CPL_FILE_HANDLE fin, CPL_FILE_HANDLE fout)
{
#ifndef WIN32
    unsetenv("CPL_SHOW_MEM_STATS");
#endif
    CPLSetConfigOption("GDAL_API_PROXY", "NO");

    GDALPipe* p = GDALPipeBuild(fin, fout);

    int nRet = GDALServerLoop(p, nullptr, nullptr, nullptr);

    GDALPipeFree(p);

    return nRet;
}

/************************************************************************/
/*                      GDALServerLoopSocket()                          */
/************************************************************************/

int GDALServerLoopSocket(const CPL_SOCKET& nSocket)
{
#ifndef WIN32
    unsetenv("CPL_SHOW_MEM_STATS");
#endif
    CPLSetConfigOption("GDAL_API_PROXY", "NO");

    GDALPipe* p = GDALPipeBuild(nSocket);

    int nRet = GDALServerLoop(p, nullptr, nullptr, nullptr);

    GDALPipeFree(p);

    return nRet;
}

/************************************************************************/
/*                        GDALClientDataset()                           */
/************************************************************************/

GDALClientDataset::GDALClientDataset(GDALServerSpawnedProcess* sspIn)
{
    ssp = sspIn;
    p = ssp->p;
}

/************************************************************************/
/*                        GDALClientDataset()                           */
/************************************************************************/

GDALClientDataset::GDALClientDataset(GDALPipe* pIn)
{
    ssp = nullptr;
    p = pIn;
}
/************************************************************************/
/*                       ~GDALClientDataset()                           */
/************************************************************************/

GDALClientDataset::~GDALClientDataset()
{
    FlushCache();

    ProcessAsyncProgress();

    std::map<CPLString, char**>::iterator oIter = aoMapMetadata.begin();
    for( ; oIter != aoMapMetadata.end(); ++oIter )
        CSLDestroy(oIter->second);

    std::map< std::pair<CPLString,CPLString>, char*>::iterator oIterItem =
        aoMapMetadataItem.begin();
    for( ; oIterItem != aoMapMetadataItem.end(); ++oIterItem )
        CPLFree(oIterItem->second);

    if( nGCPCount > 0 )
    {
        GDALDeinitGCPs(nGCPCount, pasGCPs);
        CPLFree(pasGCPs);
    }

    if( ssp != nullptr )
        GDALServerSpawnAsyncFinish(ssp);
    if( bFreeDriver )
        delete poDriver;
}

/************************************************************************/
/*                       ProcessAsyncProgress()                         */
/************************************************************************/

int GDALClientDataset::ProcessAsyncProgress()
{
    if( !async ) return TRUE;
    CPLMutexHolderD(&(async->hMutex));
    if( !async->bUpdated ) return async->bRet;
    async->bUpdated = false;
    if( !GDALPipeWrite(p, INSTR_Progress) ||
        !GDALPipeWrite(p, async->dfComplete) ||
        !GDALPipeWrite(p, async->pszProgressMsg) )
        return TRUE;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return TRUE;

    int bRet = TRUE;
    if( !GDALPipeRead(p, &bRet) )
        return TRUE;
    async->bRet = bRet;
    GDALConsumeErrors(p);
    return bRet;
}

/************************************************************************/
/*                          IBuildOverviews()                           */
/************************************************************************/

CPLErr GDALClientDataset::IBuildOverviews( const char *pszResampling,
                                           int nOverviews, int *panOverviewList,
                                           int nListBands, int *panBandList,
                                           GDALProgressFunc pfnProgress,
                                           void * pProgressData )
{
    if( !SupportsInstr(INSTR_IBuildOverviews) )
        return GDALPamDataset::IBuildOverviews(pszResampling, nOverviews, panOverviewList,
                                               nListBands, panBandList,
                                               pfnProgress, pProgressData);

    CLIENT_ENTER();
    if( nOverviews < 0 || nOverviews > 1000 ||
        nListBands < 0 || nListBands > GetRasterCount() )
        return CE_Failure;

    GDALPipeWriteConfigOption(p, "BIGTIFF_OVERVIEW");
    GDALPipeWriteConfigOption(p, "COMPRESS_OVERVIEW");
    GDALPipeWriteConfigOption(p, "PREDICTOR_OVERVIEW");
    GDALPipeWriteConfigOption(p, "JPEG_QUALITY_OVERVIEW");
    GDALPipeWriteConfigOption(p, "PHOTOMETRIC_OVERVIEW");
    GDALPipeWriteConfigOption(p, "USE_RRD");
    GDALPipeWriteConfigOption(p, "HFA_USE_RRD");
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_OVR_BLOCKSIZE");
    GDALPipeWriteConfigOption(p, "GTIFF_DONT_WRITE_BLOCKS");

    if( !GDALPipeWrite(p, INSTR_IBuildOverviews) ||
        !GDALPipeWrite(p, pszResampling) ||
        !GDALPipeWrite(p, nOverviews) ||
        !GDALPipeWrite(p, nOverviews * static_cast<int>(sizeof(int)), panOverviewList) ||
        !GDALPipeWrite(p, nListBands) ||
        !GDALPipeWrite(p, nListBands * static_cast<int>(sizeof(int)), panBandList) )
        return CE_Failure;

    if( GDALServerLoop(p, nullptr, pfnProgress, pProgressData) != 0 )
    {
        GDALConsumeErrors(p);
        return CE_Failure;
    }

    GDALConsumeErrors(p);

    for(int i=0; i<nBands;i++)
        (cpl::down_cast<GDALClientRasterBand*>(papoBands[i]))->ClearOverviewCache();

    return CE_None;
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr GDALClientDataset::IRasterIO( GDALRWFlag eRWFlag,
                                 int nXOff, int nYOff, int nXSize, int nYSize,
                                 void * pData, int nBufXSize, int nBufYSize,
                                 GDALDataType eBufType,
                                 int nBandCount, int *panBandMap,
                                 GSpacing nPixelSpace, GSpacing nLineSpace, GSpacing nBandSpace,
                                 GDALRasterIOExtraArg* psExtraArg)
{
    if( !SupportsInstr(( eRWFlag == GF_Read ) ? INSTR_IRasterIO_Read : INSTR_IRasterIO_Write ) )
        return GDALPamDataset::IRasterIO( eRWFlag,
                                          nXOff, nYOff, nXSize, nYSize,
                                          pData, nBufXSize, nBufYSize,
                                          eBufType,
                                          nBandCount, panBandMap,
                                          nPixelSpace, nLineSpace, nBandSpace,
                                          psExtraArg );

    CLIENT_ENTER();
    CPLErr eRet = CE_Failure;

    ProcessAsyncProgress();

    const int nDataTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    int bDirectCopy;
    if( nPixelSpace == nDataTypeSize &&
        nLineSpace == static_cast<GSpacing>(nBufXSize) * nDataTypeSize &&
        (nBandSpace == nBufYSize * nLineSpace ||
            (nBandSpace == 0 && nBandCount == 1)) )
    {
        bDirectCopy = TRUE;
    }
    else if( nBandCount > 1 &&
                nPixelSpace == static_cast<GSpacing>(nBandCount) * nDataTypeSize &&
                nLineSpace == nBufXSize * nPixelSpace &&
                nBandSpace == nBandCount )
    {
        bDirectCopy = TRUE;
    }
    else
        bDirectCopy = FALSE;

    if( eRWFlag == GF_Write )
    {
        for(int i=0;i<nBands;i++)
            cpl::down_cast<GDALClientRasterBand*>(GetRasterBand(i+1))->InvalidateCachedLines();
    }

    if( !GDALPipeWrite(p, ( eRWFlag == GF_Read ) ? INSTR_IRasterIO_Read : INSTR_IRasterIO_Write ) ||
        !GDALPipeWrite(p, nXOff) ||
        !GDALPipeWrite(p, nYOff) ||
        !GDALPipeWrite(p, nXSize) ||
        !GDALPipeWrite(p, nYSize) ||
        !GDALPipeWrite(p, nBufXSize) ||
        !GDALPipeWrite(p, nBufYSize) ||
        !GDALPipeWrite(p, eBufType) ||
        !GDALPipeWrite(p, nBandCount) ||
        !GDALPipeWrite(p, nBandCount * static_cast<int>(sizeof(int)), panBandMap) )
        return CE_Failure;

    if( bDirectCopy )
    {
        if( !GDALPipeWrite(p, nPixelSpace) ||
            !GDALPipeWrite(p, nLineSpace) ||
            !GDALPipeWrite(p, nBandSpace) )
            return CE_Failure;
    }
    else
    {
        if( !GDALPipeWrite(p, nPixelSpace * 0) ||
            !GDALPipeWrite(p, nLineSpace * 0) ||
            !GDALPipeWrite(p, nBandSpace * 0) )
            return CE_Failure;
    }

    if( eRWFlag == GF_Read )
    {
        if( !GDALSkipUntilEndOfJunkMarker(p) )
            return CE_Failure;

        if( !GDALPipeRead(p, &eRet) )
            return eRet;
        if( eRet != CE_Failure )
        {
            int nSize;
            if( !GDALPipeRead(p, &nSize) )
                return CE_Failure;
            GIntBig nExpectedSize = static_cast<GIntBig>(nBufXSize) * nBufYSize * nBandCount * nDataTypeSize;
            if( nSize != nExpectedSize )
                return CE_Failure;
            if( bDirectCopy )
            {
                if( !GDALPipeRead_nolength(p, nSize, pData) )
                    return CE_Failure;
            }
            else
            {
                GByte* pBuf = static_cast<GByte*>(VSIMalloc(nSize));
                if( pBuf == nullptr )
                    return CE_Failure;
                if( !GDALPipeRead_nolength(p, nSize, pBuf) )
                {
                    VSIFree(pBuf);
                    return CE_Failure;
                }
                for(int iBand=0;iBand<nBandCount;iBand++)
                {
                    for(int j=0;j<nBufYSize;j++)
                    {
                        GDALCopyWords( pBuf + (iBand * nBufYSize + j) * nBufXSize * nDataTypeSize,
                                       eBufType, nDataTypeSize,
                                       static_cast<GByte*>(pData) + iBand * nBandSpace + j * nLineSpace,
                                       eBufType, static_cast<int>(nPixelSpace),
                                       nBufXSize);
                    }
                }
                VSIFree(pBuf);
            }
        }
    }
    else
    {
        GIntBig nSizeBig = static_cast<GIntBig>(nBufXSize) * nBufYSize * nBandCount * nDataTypeSize;
        if( !CPL_INT64_FITS_ON_INT32(nSizeBig) )
            return CE_Failure;
        int nSize = static_cast<int>(nSizeBig);
        if( bDirectCopy  )
        {
            if( !GDALPipeWrite(p, nSize, pData) )
                return CE_Failure;
        }
        else
        {
            GByte* pBuf = static_cast<GByte*>(VSIMalloc(nSize));
            if( pBuf == nullptr )
                return CE_Failure;
            for(int iBand=0;iBand<nBandCount;iBand++)
            {
                for(int j=0;j<nBufYSize;j++)
                {
                    GDALCopyWords( static_cast<const GByte*>(pData) + iBand * nBandSpace + j * nLineSpace,
                                   eBufType, static_cast<int>(nPixelSpace),
                                   pBuf + (iBand * nBufYSize + j) * nBufXSize * nDataTypeSize,
                                   eBufType, nDataTypeSize,
                                   nBufXSize );
                }
            }
            if( !GDALPipeWrite(p, nSize, pBuf) )
            {
                VSIFree(pBuf);
                return CE_Failure;
            }
            VSIFree(pBuf);
        }

        if( !GDALSkipUntilEndOfJunkMarker(p) )
            return CE_Failure;
        if( !GDALPipeRead(p, &eRet) )
            return eRet;
    }

    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr GDALClientDataset::GetGeoTransform( double * padfTransform )
{
    if( !SupportsInstr(INSTR_GetGeoTransform) )
        return GDALPamDataset::GetGeoTransform(padfTransform);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_GetGeoTransform) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;

    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet != CE_Failure )
    {
        if( !GDALPipeRead(p, 6 * sizeof(double), padfTransform) )
            return CE_Failure;
    }
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                          SetGeoTransform()                           */
/************************************************************************/

CPLErr GDALClientDataset::SetGeoTransform( double * padfTransform )
{
    if( !SupportsInstr(INSTR_SetGeoTransform) )
        return GDALPamDataset::SetGeoTransform(padfTransform);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_SetGeoTransform) ||
        !GDALPipeWrite(p, 6 * sizeof(double), padfTransform) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                          GetProjectionRef()                          */
/************************************************************************/

const char* GDALClientDataset::GetProjectionRef()
{
    if( !SupportsInstr(INSTR_GetProjectionRef) )
        return GDALPamDataset::GetProjectionRef();

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_GetProjectionRef) )
        return osProjection;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return osProjection;

    char* pszStr = nullptr;
    if( !GDALPipeRead(p, &pszStr) )
        return osProjection;
    GDALConsumeErrors(p);
    if( pszStr == nullptr )
        return nullptr;
    osProjection = pszStr;
    CPLFree(pszStr);
    return osProjection;
}

/************************************************************************/
/*                           SetProjection()                            */
/************************************************************************/

CPLErr GDALClientDataset::SetProjection(const char* pszProjection)
{
    if( !SupportsInstr(INSTR_SetProjection) )
        return GDALPamDataset::SetProjection(pszProjection);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_SetProjection) ||
        !GDALPipeWrite(p, pszProjection))
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                            GetGCPCount()                             */
/************************************************************************/

int GDALClientDataset::GetGCPCount()
{
    if( !SupportsInstr(INSTR_GetGCPCount) )
        return GDALPamDataset::GetGCPCount();

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_GetGCPCount) )
        return 0;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return 0;

    int l_nGCPCount;
    if( !GDALPipeRead(p, &l_nGCPCount) )
        return 0;
    GDALConsumeErrors(p);
    return l_nGCPCount;
}

/************************************************************************/
/*                          GetGCPProjection()                          */
/************************************************************************/

const char * GDALClientDataset::GetGCPProjection()
{
    if( !SupportsInstr(INSTR_GetGCPProjection) )
        return GDALPamDataset::GetGCPProjection();

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_GetGCPProjection) )
        return osGCPProjection;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return osGCPProjection;

    char* pszStr = nullptr;
    if( !GDALPipeRead(p, &pszStr) )
        return osGCPProjection;
    GDALConsumeErrors(p);
    if( pszStr == nullptr )
        return nullptr;
    osGCPProjection = pszStr;
    CPLFree(pszStr);
    return osGCPProjection;
}

/************************************************************************/
/*                               GetGCPs()                              */
/************************************************************************/

const GDAL_GCP * GDALClientDataset::GetGCPs()
{
    if( !SupportsInstr(INSTR_GetGCPs) )
        return GDALPamDataset::GetGCPs();

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_GetGCPs) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    if( nGCPCount > 0 )
    {
        GDALDeinitGCPs(nGCPCount, pasGCPs);
        CPLFree(pasGCPs);
        pasGCPs = nullptr;
    }
    nGCPCount = 0;

    if( !GDALPipeRead(p, &nGCPCount, &pasGCPs) )
        return nullptr;

    GDALConsumeErrors(p);
    return pasGCPs;
}

/************************************************************************/
/*                               SetGCPs()                              */
/************************************************************************/

CPLErr GDALClientDataset::SetGCPs( int nGCPCountIn, const GDAL_GCP *pasGCPList,
                                   const char *pszGCPProjection )
{
    if( !SupportsInstr(INSTR_SetGCPs) )
        return GDALPamDataset::SetGCPs(nGCPCountIn, pasGCPList, pszGCPProjection);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_SetGCPs) ||
        !GDALPipeWrite(p, nGCPCountIn, pasGCPList) ||
        !GDALPipeWrite(p, pszGCPProjection) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                            GetFileList()                             */
/************************************************************************/

char** GDALClientDataset::GetFileList()
{
    if( !SupportsInstr(INSTR_GetFileList) )
        return GDALPamDataset::GetFileList();

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_GetFileList) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    char** papszFileList = nullptr;
    if( !GDALPipeRead(p, &papszFileList) )
        return nullptr;
    GDALConsumeErrors(p);

    /* If server is Windows and client is Unix, then replace backslashes */
    /* by slashes */
#ifndef WIN32
    char** papszIter = papszFileList;
    while( papszIter != nullptr && *papszIter != nullptr )
    {
        char* pszIter = *papszIter;
        char* pszBackSlash;
        while( (pszBackSlash = strchr(pszIter, '\\')) != nullptr )
        {
            *pszBackSlash = '/';
            pszIter = pszBackSlash + 1;
        }
        papszIter ++;
    }
#endif

    return papszFileList;
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char** GDALClientDataset::GetMetadata( const char * pszDomain )
{
    if( !SupportsInstr(INSTR_GetMetadata) )
        return GDALPamDataset::GetMetadata(pszDomain);

    CLIENT_ENTER();
    if( pszDomain == nullptr )
        pszDomain = "";
    std::map<CPLString, char**>::iterator oIter = aoMapMetadata.find(CPLString(pszDomain));
    if( oIter != aoMapMetadata.end() )
    {
        CSLDestroy(oIter->second);
        aoMapMetadata.erase(oIter);
    }
    if( !GDALPipeWrite(p, INSTR_GetMetadata) ||
        !GDALPipeWrite(p, pszDomain) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    char** papszMD = nullptr;
    if( !GDALPipeRead(p, &papszMD) )
        return nullptr;
    aoMapMetadata[pszDomain] = papszMD;
    GDALConsumeErrors(p);
    return papszMD;
}

/************************************************************************/
/*                        SetDescription()                              */
/************************************************************************/

/*void GDALClientDataset::SetDescription( const char * pszDescription )
{
    sDescription = pszDescription;
    if( !GDALPipeWrite(p, INSTR_SetDescription) ||
        !GDALPipeWrite(p, pszDescription) ||
        !GDALSkipUntilEndOfJunkMarker(p))
        return;
    GDALConsumeErrors(p);
}*/

/************************************************************************/
/*                        GetMetadataItem()                             */
/************************************************************************/

const char* GDALClientDataset::GetMetadataItem( const char * pszName,
                                                const char * pszDomain )
{
    if( !SupportsInstr(INSTR_GetMetadataItem) )
        return GDALPamDataset::GetMetadataItem(pszName, pszDomain);

    CLIENT_ENTER();
    if( pszDomain == nullptr )
        pszDomain = "";
    std::pair<CPLString,CPLString> oPair =
        std::pair<CPLString,CPLString> (CPLString(pszDomain), CPLString(pszName));
    std::map< std::pair<CPLString,CPLString>, char*>::iterator oIter =
        aoMapMetadataItem.find(oPair);
    if( oIter != aoMapMetadataItem.end() )
    {
        CPLFree(oIter->second);
        aoMapMetadataItem.erase(oIter);
    }
    if( !GDALPipeWrite(p, INSTR_GetMetadataItem) ||
        !GDALPipeWrite(p, pszName) ||
        !GDALPipeWrite(p, pszDomain) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    char* pszItem = nullptr;
    if( !GDALPipeRead(p, &pszItem) )
        return nullptr;
    aoMapMetadataItem[oPair] = pszItem;
    GDALConsumeErrors(p);
    return pszItem;
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

CPLErr GDALClientDataset::SetMetadata( char ** papszMetadata,
                                       const char * pszDomain )
{
    if( !SupportsInstr(INSTR_SetMetadata) )
        return GDALPamDataset::SetMetadata(papszMetadata, pszDomain);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_SetMetadata) ||
        !GDALPipeWrite(p, papszMetadata) ||
        !GDALPipeWrite(p, pszDomain) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

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

CPLErr GDALClientDataset::SetMetadataItem( const char * pszName,
                                           const char * pszValue,
                                           const char * pszDomain )
{
    if( !SupportsInstr(INSTR_SetMetadataItem) )
        return GDALPamDataset::SetMetadataItem(pszName, pszValue, pszDomain);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_SetMetadataItem) ||
        !GDALPipeWrite(p, pszName) ||
        !GDALPipeWrite(p, pszValue) ||
        !GDALPipeWrite(p, pszDomain) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                             FlushCache()                             */
/************************************************************************/

void GDALClientDataset::FlushCache()
{
    if( !SupportsInstr(INSTR_FlushCache) )
    {
        GDALPamDataset::FlushCache();
        return;
    }

    for(int i=0;i<nBands;i++)
        cpl::down_cast<GDALClientRasterBand*>(GetRasterBand(i+1))->InvalidateCachedLines();

    CLIENT_ENTER();
    SetPamFlags(0);
    GDALPamDataset::FlushCache();
    if( !GDALPipeWrite(p, INSTR_FlushCache) ||
        !GDALSkipUntilEndOfJunkMarker(p) )
        return;
    GDALConsumeErrors(p);
}

/************************************************************************/
/*                              AddBand()                               */
/************************************************************************/

CPLErr GDALClientDataset::AddBand( GDALDataType eType,
                                   char **papszOptions )
{
    if( !SupportsInstr(INSTR_AddBand) )
        return GDALPamDataset::AddBand(eType, papszOptions);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_AddBand) ||
        !GDALPipeWrite(p, eType) ||
        !GDALPipeWrite(p, papszOptions) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;
    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet == CE_None )
    {
        GDALRasterBand* poBand = nullptr;
        if( !GDALPipeRead(p, this, &poBand, abyCaps) )
            return CE_Failure;
        SetBand(GetRasterCount() + 1, poBand);
    }
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                             AdviseRead()                             */
/************************************************************************/

CPLErr GDALClientDataset::AdviseRead( int nXOff, int nYOff, int nXSize, int nYSize,
                                  int nBufXSize, int nBufYSize,
                                  GDALDataType eDT,
                                  int nBandCount, int *panBandList,
                                  char **papszOptions )
{
    if( !SupportsInstr(INSTR_AdviseRead) )
        return GDALPamDataset::AdviseRead(nXOff, nYOff, nXSize, nYSize,
                                          nBufXSize, nBufYSize, eDT, nBandCount, panBandList,
                                          papszOptions);

    CLIENT_ENTER();
    if( !GDALPipeWrite(p, INSTR_AdviseRead) ||
        !GDALPipeWrite(p, nXOff) ||
        !GDALPipeWrite(p, nYOff) ||
        !GDALPipeWrite(p, nXSize) ||
        !GDALPipeWrite(p, nYSize) ||
        !GDALPipeWrite(p, nBufXSize) ||
        !GDALPipeWrite(p, nBufYSize) ||
        !GDALPipeWrite(p, eDT) ||
        !GDALPipeWrite(p, nBandCount) ||
        !GDALPipeWrite(p, panBandList ? nBandCount * static_cast<int>(sizeof(int)) : 0, panBandList) ||
        !GDALPipeWrite(p, papszOptions) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                           CreateMaskBand()                           */
/************************************************************************/

CPLErr GDALClientDataset::CreateMaskBand( int nFlagsIn )
{
    if( !SupportsInstr(INSTR_CreateMaskBand) )
        return GDALPamDataset::CreateMaskBand(nFlagsIn);

    CLIENT_ENTER();
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_INTERNAL_MASK_TO_8BIT", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_INTERNAL_MASK", bRecycleChild);
    if( !GDALPipeWrite(p, INSTR_CreateMaskBand) ||
        !GDALPipeWrite(p, nFlagsIn) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                       GDALClientRasterBand()                         */
/************************************************************************/

GDALClientRasterBand::GDALClientRasterBand(GDALPipe* pIn, int iSrvBandIn,
                                           GDALClientDataset* poDSIn,
                                           int nBandIn, GDALAccess eAccessIn,
                                           int nRasterXSizeIn, int nRasterYSizeIn,
                                           GDALDataType eDataTypeIn,
                                           int nBlockXSizeIn, int nBlockYSizeIn,
                                           GByte abyCapsIn[16]):
    p(pIn),
    iSrvBand(iSrvBandIn)
{
    poDS = poDSIn;
    nBand = nBandIn;
    eAccess = eAccessIn;
    nRasterXSize = nRasterXSizeIn;
    nRasterYSize = nRasterYSizeIn;
    eDataType = eDataTypeIn;
    nBlockXSize = nBlockXSizeIn;
    nBlockYSize = nBlockYSizeIn;
    memcpy(abyCaps, abyCapsIn, sizeof(abyCaps));
    bEnableLineCaching = CPLTestBool(CPLGetConfigOption(
        "GDAL_API_PROXY_LINE_CACHING", "YES"));
}

/************************************************************************/
/*                         ~GDALClientRasterBand()                      */
/************************************************************************/

GDALClientRasterBand::~GDALClientRasterBand()
{
    CSLDestroy(papszCategoryNames);
    delete poColorTable;
    CPLFree(pszUnitType);
    delete poMaskBand;
    delete poRAT;
    CPLFree(pabyCachedLines);

    for( auto oIter: aMapOvrBands )
        delete oIter.second;

    for( auto oIter: aoMapMetadataItem )
        CPLFree(oIter.second);

    for( auto oIter: aoMapMetadata )
        CSLDestroy(oIter.second);

    for( auto& poOldMaskBand: apoOldMaskBands )
        delete poOldMaskBand;
}

/************************************************************************/
/*                       CreateFakeMaskBand()                           */
/************************************************************************/

GDALRasterBand* GDALClientRasterBand::CreateFakeMaskBand()
{
    if( poMaskBand == nullptr )
        poMaskBand = new GDALAllValidMaskBand(this);
    return poMaskBand;
}

/************************************************************************/
/*                             WriteInstr()                             */
/************************************************************************/

int GDALClientRasterBand::WriteInstr(InstrEnum instr)
{
    return GDALPipeWrite(p, instr) &&
           GDALPipeWrite(p, iSrvBand);
}

/************************************************************************/
/*                             FlushCache()                             */
/************************************************************************/

CPLErr GDALClientRasterBand::FlushCache()
{
    if( !SupportsInstr(INSTR_Band_FlushCache) )
        return GDALPamRasterBand::FlushCache();

    InvalidateCachedLines();

    CLIENT_ENTER();
    CPLErr eErr = GDALPamRasterBand::FlushCache();
    if( eErr == CE_None )
    {
        if( !WriteInstr(INSTR_Band_FlushCache) )
            return CE_Failure;
        return CPLErrOnlyRet(p);
    }
    return eErr;
}
/************************************************************************/
/*                          GetCategoryNames()                          */
/************************************************************************/

char ** GDALClientRasterBand::GetCategoryNames()
{
    if( !SupportsInstr(INSTR_Band_GetCategoryNames) )
        return GDALPamRasterBand::GetCategoryNames();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetCategoryNames) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    CSLDestroy(papszCategoryNames);
    papszCategoryNames = nullptr;
    if( !GDALPipeRead(p, &papszCategoryNames) )
        return nullptr;
    GDALConsumeErrors(p);
    return papszCategoryNames;
}

/************************************************************************/
/*                          SetCategoryNames()                          */
/************************************************************************/

CPLErr GDALClientRasterBand::SetCategoryNames( char ** papszCategoryNamesIn )
{
    if( !SupportsInstr(INSTR_Band_SetCategoryNames) )
        return GDALPamRasterBand::SetCategoryNames(papszCategoryNamesIn);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetCategoryNames) ||
        !GDALPipeWrite(p, papszCategoryNamesIn) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                        SetDescription()                              */
/************************************************************************/

void GDALClientRasterBand::SetDescription( const char * pszDescription )
{
    if( !SupportsInstr(INSTR_Band_SetDescription) )
    {
        GDALPamRasterBand::SetDescription(pszDescription);
        return;
    }

    CLIENT_ENTER();
    sDescription = pszDescription;
    if( !WriteInstr(INSTR_Band_SetDescription) ||
        !GDALPipeWrite(p, pszDescription) ||
        !GDALSkipUntilEndOfJunkMarker(p))
        return;
    GDALConsumeErrors(p);
}

/************************************************************************/
/*                            GetMetadata()                             */
/************************************************************************/

char** GDALClientRasterBand::GetMetadata( const char * pszDomain )
{
    if( !SupportsInstr(INSTR_Band_GetMetadata) )
        return GDALPamRasterBand::GetMetadata(pszDomain);

    CLIENT_ENTER();
    if( pszDomain == nullptr )
        pszDomain = "";
    std::map<CPLString, char**>::iterator oIter = aoMapMetadata.find(CPLString(pszDomain));
    if( oIter != aoMapMetadata.end() )
    {
        CSLDestroy(oIter->second);
        aoMapMetadata.erase(oIter);
    }
    if( !WriteInstr(INSTR_Band_GetMetadata) ||
        !GDALPipeWrite(p, pszDomain) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    char** papszMD = nullptr;
    if( !GDALPipeRead(p, &papszMD) )
        return nullptr;
    aoMapMetadata[pszDomain] = papszMD;
    GDALConsumeErrors(p);
    return papszMD;
}

/************************************************************************/
/*                        GetMetadataItem()                             */
/************************************************************************/

const char* GDALClientRasterBand::GetMetadataItem( const char * pszName,
                                                   const char * pszDomain )
{
    if( !SupportsInstr(INSTR_Band_GetMetadataItem) )
        return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);

    CLIENT_ENTER();
    if( pszDomain == nullptr )
        pszDomain = "";
    std::pair<CPLString,CPLString> oPair =
        std::pair<CPLString,CPLString> (CPLString(pszDomain), CPLString(pszName));
    std::map< std::pair<CPLString,CPLString>, char*>::iterator oIter =
        aoMapMetadataItem.find(oPair);
    if( oIter != aoMapMetadataItem.end() )
    {
        CPLFree(oIter->second);
        aoMapMetadataItem.erase(oIter);
    }
    if( !WriteInstr(INSTR_Band_GetMetadataItem) ||
        !GDALPipeWrite(p, pszName) ||
        !GDALPipeWrite(p, pszDomain) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    char* pszItem = nullptr;
    if( !GDALPipeRead(p, &pszItem) )
        return nullptr;
    aoMapMetadataItem[oPair] = pszItem;
    GDALConsumeErrors(p);
    return pszItem;
}

/************************************************************************/
/*                            SetMetadata()                             */
/************************************************************************/

CPLErr GDALClientRasterBand::SetMetadata( char ** papszMetadata,
                                   const char * pszDomain )
{
    if( !SupportsInstr(INSTR_Band_SetMetadata) )
        return GDALPamRasterBand::SetMetadata(papszMetadata, pszDomain);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetMetadata) ||
        !GDALPipeWrite(p, papszMetadata) ||
        !GDALPipeWrite(p, pszDomain) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

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

CPLErr GDALClientRasterBand::SetMetadataItem( const char * pszName,
                                          const char * pszValue,
                                          const char * pszDomain )
{
    if( !SupportsInstr(INSTR_Band_SetMetadataItem) )
        return GDALPamRasterBand::SetMetadataItem(pszName, pszValue, pszDomain);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetMetadataItem) ||
        !GDALPipeWrite(p, pszName) ||
        !GDALPipeWrite(p, pszValue) ||
        !GDALPipeWrite(p, pszDomain) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                       GetColorInterpretation()                       */
/************************************************************************/

GDALColorInterp GDALClientRasterBand::GetColorInterpretation()
{
    if( !SupportsInstr(INSTR_Band_GetColorInterpretation) )
        return GDALPamRasterBand::GetColorInterpretation();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetColorInterpretation) )
        return GCI_Undefined;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return GCI_Undefined;

    int nInt;
    if( !GDALPipeRead(p, &nInt) )
        return GCI_Undefined;
    GDALConsumeErrors(p);
    return static_cast<GDALColorInterp>(nInt);
}

/************************************************************************/
/*                       SetColorInterpretation()                       */
/************************************************************************/

CPLErr GDALClientRasterBand::SetColorInterpretation(GDALColorInterp eInterp)
{
    if( !SupportsInstr(INSTR_Band_SetColorInterpretation) )
        return GDALPamRasterBand::SetColorInterpretation(eInterp);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetColorInterpretation) ||
        !GDALPipeWrite(p, eInterp) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                           GetStatistics()                            */
/************************************************************************/

CPLErr GDALClientRasterBand::GetStatistics( int bApproxOK, int bForce,
                                            double *pdfMin, double *pdfMax,
                                            double *pdfMean, double *pdfStdDev )
{
    if( !SupportsInstr(INSTR_Band_GetStatistics) )
        return GDALPamRasterBand::GetStatistics(
            bApproxOK, bForce, pdfMin, pdfMax, pdfMean, pdfStdDev);

    CLIENT_ENTER();
    if( !bApproxOK && CPLTestBool(CPLGetConfigOption("GDAL_API_PROXY_FORCE_APPROX", "NO")) )
        bApproxOK = TRUE;
    CPLErr eDefaultRet = CE_Failure;
    if( CPLTestBool(CPLGetConfigOption("QGIS_HACK", "NO")) )
    {
        if( pdfMin ) *pdfMin = 0;
        if( pdfMax ) *pdfMax = 255;
        if( pdfMean ) *pdfMean = 0;
        if( pdfStdDev ) *pdfStdDev = 0;
        eDefaultRet = CE_None;
    }
    if( !WriteInstr( INSTR_Band_GetStatistics) ||
        !GDALPipeWrite(p, bApproxOK) ||
        !GDALPipeWrite(p, bForce) )
        return eDefaultRet;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return eDefaultRet;

    CPLErr eRet = eDefaultRet;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet == CE_None )
    {
        double dfMin, dfMax, dfMean, dfStdDev;
        if( !GDALPipeRead(p, &dfMin) ||
            !GDALPipeRead(p, &dfMax) ||
            !GDALPipeRead(p, &dfMean) ||
            !GDALPipeRead(p, &dfStdDev) )
            return eDefaultRet;
        if( pdfMin ) *pdfMin = dfMin;
        if( pdfMax ) *pdfMax = dfMax;
        if( pdfMean ) *pdfMean = dfMean;
        if( pdfStdDev ) *pdfStdDev = dfStdDev;
    }
    else if( eDefaultRet == CE_None )
        eRet = eDefaultRet;
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                         ComputeStatistics()                          */
/************************************************************************/

CPLErr GDALClientRasterBand::ComputeStatistics( int bApproxOK,
                                                double *pdfMin,
                                                double *pdfMax,
                                                double *pdfMean,
                                                double *pdfStdDev,
                                                GDALProgressFunc pfnProgress,
                                                void *pProgressData )
{
    if( !SupportsInstr(INSTR_Band_ComputeStatistics) )
        return GDALPamRasterBand::ComputeStatistics(
            bApproxOK, pdfMin, pdfMax, pdfMean, pdfStdDev, pfnProgress, pProgressData);

    CLIENT_ENTER();
    if( !bApproxOK && CPLTestBool(CPLGetConfigOption("GDAL_API_PROXY_FORCE_APPROX", "NO")) )
        bApproxOK = TRUE;
    if( !WriteInstr(INSTR_Band_ComputeStatistics) ||
        !GDALPipeWrite(p, bApproxOK) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;

    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet != CE_Failure )
    {
        double dfMin, dfMax, dfMean, dfStdDev;
        if( !GDALPipeRead(p, &dfMin) ||
            !GDALPipeRead(p, &dfMax) ||
            !GDALPipeRead(p, &dfMean) ||
            !GDALPipeRead(p, &dfStdDev) )
            return CE_Failure;
        if( pdfMin ) *pdfMin = dfMin;
        if( pdfMax ) *pdfMax = dfMax;
        if( pdfMean ) *pdfMean = dfMean;
        if( pdfStdDev ) *pdfStdDev = dfStdDev;
    }
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                           SetStatistics()                            */
/************************************************************************/

CPLErr GDALClientRasterBand::SetStatistics( double dfMin, double dfMax,
                                        double dfMean, double dfStdDev )
{
    if( !SupportsInstr(INSTR_Band_SetStatistics) )
        return GDALPamRasterBand::SetStatistics(dfMin, dfMax, dfMean, dfStdDev);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetStatistics) ||
        !GDALPipeWrite(p, dfMin) ||
        !GDALPipeWrite(p, dfMax) ||
        !GDALPipeWrite(p, dfMean) ||
        !GDALPipeWrite(p, dfStdDev) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                        ComputeRasterMinMax()                         */
/************************************************************************/

CPLErr GDALClientRasterBand::ComputeRasterMinMax( int bApproxOK,
                                                  double* padfMinMax )
{
    if( !SupportsInstr(INSTR_Band_ComputeRasterMinMax) )
        return GDALPamRasterBand::ComputeRasterMinMax(bApproxOK, padfMinMax);

    CLIENT_ENTER();
    if( !bApproxOK && CPLTestBool(CPLGetConfigOption("GDAL_API_PROXY_FORCE_APPROX", "NO")) )
        bApproxOK = TRUE;
    if( !WriteInstr(INSTR_Band_ComputeRasterMinMax) ||
        !GDALPipeWrite(p, bApproxOK) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;

    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet != CE_Failure )
    {
        if( !GDALPipeRead(p, padfMinMax + 0) ||
            !GDALPipeRead(p, padfMinMax + 1) )
            return CE_Failure;
    }
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                            GetHistogram()                            */
/************************************************************************/

CPLErr GDALClientRasterBand::GetHistogram( double dfMin, double dfMax,
                                           int nBuckets, GUIntBig *panHistogram,
                                           int bIncludeOutOfRange,
                                           int bApproxOK,
                                           GDALProgressFunc pfnProgress,
                                           void *pProgressData )
{
    if( !SupportsInstr(INSTR_Band_GetHistogram) )
        return GDALPamRasterBand::GetHistogram(
            dfMin, dfMax, nBuckets, panHistogram, bIncludeOutOfRange, bApproxOK, pfnProgress, pProgressData);

    CLIENT_ENTER();
    if( !bApproxOK && CPLTestBool(CPLGetConfigOption("GDAL_API_PROXY_FORCE_APPROX", "NO")) )
        bApproxOK = TRUE;
    CPLErr eDefaultRet = CE_Failure;
    if( CPLTestBool(CPLGetConfigOption("QGIS_HACK", "NO")) )
    {
        memset(panHistogram, 0, sizeof(GUIntBig) * nBuckets);
        eDefaultRet = CE_None;
    }
    if( !WriteInstr(INSTR_Band_GetHistogram) ||
        !GDALPipeWrite(p, dfMin) ||
        !GDALPipeWrite(p, dfMax) ||
        !GDALPipeWrite(p, nBuckets) ||
        !GDALPipeWrite(p, bIncludeOutOfRange) ||
        !GDALPipeWrite(p, bApproxOK) )
        return eDefaultRet;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return eDefaultRet;

    CPLErr eRet = eDefaultRet;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet != CE_Failure )
    {
        int nSize;
        if( !GDALPipeRead(p, &nSize) ||
            nSize != nBuckets * static_cast<int>(sizeof(GUIntBig)) ||
            !GDALPipeRead_nolength(p, nSize, panHistogram) )
            return eDefaultRet;
    }
    else if( eDefaultRet == CE_None )
        eRet = eDefaultRet;
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                        GetDefaultHistogram()                         */
/************************************************************************/

CPLErr GDALClientRasterBand::GetDefaultHistogram( double *pdfMin,
                                                  double *pdfMax,
                                                  int *pnBuckets,
                                                  GUIntBig ** ppanHistogram,
                                                  int bForce,
                                                  GDALProgressFunc pfnProgress,
                                                  void *pProgressData )
{
    if( !SupportsInstr(INSTR_Band_GetDefaultHistogram) )
        return GDALPamRasterBand::GetDefaultHistogram(
            pdfMin, pdfMax, pnBuckets, ppanHistogram, bForce, pfnProgress, pProgressData);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetDefaultHistogram) ||
        !GDALPipeWrite(p, bForce) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;
    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    if( eRet != CE_Failure )
    {
        double dfMin, dfMax;
        int nBuckets, nSize;
        if( !GDALPipeRead(p, &dfMin) ||
            !GDALPipeRead(p, &dfMax) ||
            !GDALPipeRead(p, &nBuckets) ||
            !GDALPipeRead(p, &nSize) )
            return CE_Failure;
        if( nSize != nBuckets * static_cast<int>(sizeof(GUIntBig)) )
            return CE_Failure;
        if( pdfMin ) *pdfMin = dfMin;
        if( pdfMax ) *pdfMax = dfMax;
        if( pnBuckets ) *pnBuckets = nBuckets;
        if( ppanHistogram )
        {
            *ppanHistogram = static_cast<GUIntBig*>(VSIMalloc(nSize));
            if( *ppanHistogram == nullptr )
                return CE_Failure;
            if( !GDALPipeRead_nolength(p, nSize, *ppanHistogram) )
                return CE_Failure;
        }
        else
        {
            GUIntBig *panHistogram = static_cast<GUIntBig*>(VSIMalloc(nSize));
            if( panHistogram == nullptr )
                return CE_Failure;
            if( !GDALPipeRead_nolength(p, nSize, panHistogram) )
            {
                CPLFree(panHistogram);
                return CE_Failure;
            }
            CPLFree(panHistogram);
        }
    }
    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                        SetDefaultHistogram()                         */
/************************************************************************/

CPLErr GDALClientRasterBand::SetDefaultHistogram( double dfMin, double dfMax,
                                              int nBuckets, GUIntBig *panHistogram )
{
    if( !SupportsInstr(INSTR_Band_SetDefaultHistogram) )
        return GDALPamRasterBand::SetDefaultHistogram(dfMin, dfMax, nBuckets, panHistogram);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetDefaultHistogram) ||
        !GDALPipeWrite(p, dfMin) ||
        !GDALPipeWrite(p, dfMax) ||
        !GDALPipeWrite(p, nBuckets) ||
        !GDALPipeWrite(p, nBuckets * static_cast<int>(sizeof(GUIntBig)), panHistogram) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                            IReadBlock()                              */
/************************************************************************/

CPLErr GDALClientRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void* pImage)
{
    if( !SupportsInstr(INSTR_Band_IReadBlock) )
        return CE_Failure;

    CLIENT_ENTER();
    if( poDS != nullptr )
        cpl::down_cast<GDALClientDataset*>(poDS)->ProcessAsyncProgress();

    if( !WriteInstr(INSTR_Band_IReadBlock) ||
        !GDALPipeWrite(p, nBlockXOff) ||
        !GDALPipeWrite(p, nBlockYOff) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;

    CPLErr eRet = CE_Failure;
    if( !GDALPipeRead(p, &eRet) )
        return eRet;
    int nSize;
    if( !GDALPipeRead(p, &nSize) ||
        nSize != nBlockXSize * nBlockYSize * GDALGetDataTypeSizeBytes(eDataType) ||
        !GDALPipeRead_nolength(p, nSize, pImage) )
        return CE_Failure;

    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr GDALClientRasterBand::IWriteBlock(int nBlockXOff, int nBlockYOff, void* pImage)
{
    if( !SupportsInstr(INSTR_Band_IWriteBlock) )
        return CE_Failure;

    InvalidateCachedLines();

    CLIENT_ENTER();
    const int nSize =
        nBlockXSize * nBlockYSize * GDALGetDataTypeSizeBytes(eDataType);
    if( !WriteInstr(INSTR_Band_IWriteBlock) ||
        !GDALPipeWrite(p, nBlockXOff) ||
        !GDALPipeWrite(p, nBlockYOff) ||
        !GDALPipeWrite(p, nSize, pImage) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                      IRasterIO_read_internal()                       */
/************************************************************************/

CPLErr GDALClientRasterBand::IRasterIO_read_internal(
                                    int nXOff, int nYOff, int nXSize, int nYSize,
                                    void * pData, int nBufXSize, int nBufYSize,
                                    GDALDataType eBufType,
                                    GSpacing nPixelSpace, GSpacing nLineSpace)
{
    CPLErr eRet = CE_Failure;

    if( !WriteInstr(INSTR_Band_IRasterIO_Read) ||
        !GDALPipeWrite(p, nXOff) ||
        !GDALPipeWrite(p, nYOff) ||
        !GDALPipeWrite(p, nXSize) ||
        !GDALPipeWrite(p, nYSize) ||
        !GDALPipeWrite(p, nBufXSize) ||
        !GDALPipeWrite(p, nBufYSize) ||
        !GDALPipeWrite(p, eBufType) )
        return CE_Failure;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CE_Failure;

    if( !GDALPipeRead(p, &eRet) )
        return eRet;

    int nSize;
    if( !GDALPipeRead(p, &nSize) )
        return CE_Failure;
    const int nDataTypeSize = GDALGetDataTypeSizeBytes(eBufType);
    GIntBig nExpectedSize = static_cast<GIntBig>(nBufXSize) * nBufYSize * nDataTypeSize;
    if( nSize != nExpectedSize )
        return CE_Failure;
    if( nPixelSpace == nDataTypeSize &&
        nLineSpace == static_cast<GSpacing>(nBufXSize) * nDataTypeSize )
    {
        if( !GDALPipeRead_nolength(p, nSize, pData) )
            return CE_Failure;
    }
    else
    {
        GByte* pBuf = static_cast<GByte*>(VSIMalloc(nSize));
        if( pBuf == nullptr )
            return CE_Failure;
        if( !GDALPipeRead_nolength(p, nSize, pBuf) )
        {
            VSIFree(pBuf);
            return CE_Failure;
        }
        for(int j=0;j<nBufYSize;j++)
        {
            GDALCopyWords( pBuf + j * nBufXSize * nDataTypeSize,
                            eBufType, nDataTypeSize,
                            static_cast<GByte*>(pData) + j * nLineSpace,
                            eBufType, static_cast<int>(nPixelSpace),
                            nBufXSize );
        }
        VSIFree(pBuf);
    }

    GDALConsumeErrors(p);
    return eRet;
}

/************************************************************************/
/*                       InvalidateCachedLines()                        */
/************************************************************************/

void GDALClientRasterBand::InvalidateCachedLines()
{
    nSuccessiveLinesRead = 0;
    nCachedYStart = -1;
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr GDALClientRasterBand::IRasterIO( GDALRWFlag eRWFlag,
                                    int nXOff, int nYOff, int nXSize, int nYSize,
                                    void * pData, int nBufXSize, int nBufYSize,
                                    GDALDataType eBufType,
                                    GSpacing nPixelSpace, GSpacing nLineSpace,
                                    GDALRasterIOExtraArg* psExtraArg )
{
    if( !SupportsInstr( (eRWFlag == GF_Read) ? INSTR_Band_IRasterIO_Read : INSTR_Band_IRasterIO_Write) )
        return GDALPamRasterBand::IRasterIO( eRWFlag,
                                             nXOff, nYOff, nXSize, nYSize,
                                             pData, nBufXSize, nBufYSize,
                                             eBufType,
                                             nPixelSpace, nLineSpace, psExtraArg );

    CLIENT_ENTER();
    CPLErr eRet = CE_Failure;

    if( poDS != nullptr )
        cpl::down_cast<GDALClientDataset*>(poDS)->ProcessAsyncProgress();

    if( eRWFlag == GF_Read )
    {
        /*if( GetAccess() == GA_Update )
            FlushCache();*/

        /* Detect scanline reading pattern and read several rows in advance */
        /* to save a few client/server roundtrips */
        if( bEnableLineCaching &&
            nXOff == 0 && nXSize == nRasterXSize && nYSize == 1 &&
            nBufXSize == nXSize && nBufYSize == nYSize )
        {
            const int nBufTypeSize = GDALGetDataTypeSizeBytes(eBufType);

            /* Is the current line already cached ? */
            if( nCachedYStart >= 0 &&
                nYOff >= nCachedYStart && nYOff < nCachedYStart + nCachedLines &&
                eBufType == eCachedBufType )
            {
                nSuccessiveLinesRead ++;

                const int nCachedBufTypeSize =
                    GDALGetDataTypeSizeBytes(eCachedBufType);
                GDALCopyWords(pabyCachedLines + (nYOff - nCachedYStart) * nXSize * nCachedBufTypeSize,
                              eCachedBufType, nCachedBufTypeSize,
                              pData, eBufType, static_cast<int>(nPixelSpace),
                              nXSize);
                nLastYOff = nYOff;
                eLastBufType = eBufType;
                return CE_None;
            }

            if( nYOff == nLastYOff + 1 &&
                eBufType == eLastBufType )
            {
                nSuccessiveLinesRead ++;
                if( nSuccessiveLinesRead >= 2 )
                {
                    if( pabyCachedLines == nullptr )
                    {
                        nCachedLines = 10 * 1024 * 1024 / (nXSize * nBufTypeSize);
                        if( nCachedLines > 1 )
                            pabyCachedLines = static_cast<GByte*>(VSIMalloc(
                                nCachedLines * nXSize * nBufTypeSize));
                    }
                    if( pabyCachedLines != nullptr )
                    {
                        int nLinesToRead = nCachedLines;
                        if( nYOff + nLinesToRead > nRasterYSize )
                            nLinesToRead = nRasterYSize - nYOff;
                        eRet = IRasterIO_read_internal( nXOff, nYOff, nXSize, nLinesToRead,
                                                        pabyCachedLines, nXSize, nLinesToRead,
                                                        eBufType,
                                                        nBufTypeSize,
                                                        static_cast<GSpacing>(nBufTypeSize) * nXSize );
                        if( eRet == CE_None )
                        {
                            eCachedBufType = eBufType;
                            nCachedYStart = nYOff;

                            const int nCachedBufTypeSize =
                                  GDALGetDataTypeSizeBytes(eCachedBufType);
                            GDALCopyWords(pabyCachedLines + (nYOff - nCachedYStart) * nXSize * nCachedBufTypeSize,
                                        eCachedBufType, nCachedBufTypeSize,
                                        pData, eBufType, static_cast<int>(nPixelSpace),
                                        nXSize);
                            nLastYOff = nYOff;
                            eLastBufType = eBufType;

                            return CE_None;
                        }
                        else
                            InvalidateCachedLines();
                    }
                }
            }
            else
                InvalidateCachedLines();
        }
        else
            InvalidateCachedLines();

        nLastYOff = nYOff;
        eLastBufType = eBufType;

        return IRasterIO_read_internal( nXOff, nYOff, nXSize, nYSize,
                                        pData, nBufXSize, nBufYSize,
                                        eBufType,
                                        nPixelSpace, nLineSpace );
    }
    else
    {
        InvalidateCachedLines();

        if( !WriteInstr(INSTR_Band_IRasterIO_Write) ||
            !GDALPipeWrite(p, nXOff) ||
            !GDALPipeWrite(p, nYOff) ||
            !GDALPipeWrite(p, nXSize) ||
            !GDALPipeWrite(p, nYSize) ||
            !GDALPipeWrite(p, nBufXSize) ||
            !GDALPipeWrite(p, nBufYSize) ||
            !GDALPipeWrite(p, eBufType) )
            return CE_Failure;

        const int nDataTypeSize = GDALGetDataTypeSizeBytes(eBufType);
        GIntBig nSizeBig = static_cast<GIntBig>(nBufXSize) * nBufYSize * nDataTypeSize;
        int nSize = static_cast<int>(nSizeBig);
        if( nSizeBig != nSize )
            return CE_Failure;
        if( nPixelSpace == nDataTypeSize &&
            nLineSpace == static_cast<GSpacing>(nBufXSize) * nDataTypeSize )
        {
            if( !GDALPipeWrite(p, nSize, pData) )
                return CE_Failure;
        }
        else
        {
            GByte* pBuf = static_cast<GByte*>(VSIMalloc(nSize));
            if( pBuf == nullptr )
                return CE_Failure;
            for(int j=0;j<nBufYSize;j++)
            {
                GDALCopyWords( static_cast<const GByte*>(pData) + j * nLineSpace,
                               eBufType, static_cast<int>(nPixelSpace),
                               pBuf + j * nBufXSize * nDataTypeSize,
                               eBufType, nDataTypeSize,
                               nBufXSize );
            }
            if( !GDALPipeWrite(p, nSize, pBuf) )
            {
                VSIFree(pBuf);
                return CE_Failure;
            }
            VSIFree(pBuf);
        }

        if( !GDALSkipUntilEndOfJunkMarker(p) )
            return CE_Failure;
        if( !GDALPipeRead(p, &eRet) )
            return eRet;

        GDALConsumeErrors(p);
        return eRet;
    }
}

/************************************************************************/
/*                       HasArbitraryOverviews()                        */
/************************************************************************/

int GDALClientRasterBand::HasArbitraryOverviews()
{
    if( !SupportsInstr(INSTR_Band_HasArbitraryOverviews) )
        return GDALPamRasterBand::HasArbitraryOverviews();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_HasArbitraryOverviews) )
        return 0;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return 0;

    int nInt;
    if( !GDALPipeRead(p, &nInt) )
        return 0;
    GDALConsumeErrors(p);
    return nInt;
}

/************************************************************************/
/*                          GetOverviewCount()                          */
/************************************************************************/

int GDALClientRasterBand::GetOverviewCount()
{
    if( !SupportsInstr(INSTR_Band_GetOverviewCount) )
        return GDALPamRasterBand::GetOverviewCount();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetOverviewCount) )
        return 0;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return 0;

    int nInt;
    if( !GDALPipeRead(p, &nInt) )
        return 0;
    GDALConsumeErrors(p);
    return nInt;
}

/************************************************************************/
/*                             GetDouble()                              */
/************************************************************************/

double GDALClientRasterBand::GetDouble( InstrEnum instr, int *pbSuccess )
{
    if( pbSuccess ) *pbSuccess = FALSE;
    if( !WriteInstr( instr) )
        return 0;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return 0;

    int bSuccess;
    double dfRet;
    if( !GDALPipeRead(p, &bSuccess) ||
        !GDALPipeRead(p, &dfRet) )
        return 0;
    if( pbSuccess )
        *pbSuccess = bSuccess;
    GDALConsumeErrors(p);
    return dfRet;
}

/************************************************************************/
/*                           GetNoDataValue()                           */
/************************************************************************/

double GDALClientRasterBand::GetNoDataValue( int *pbSuccess )
{
    if( !SupportsInstr(INSTR_Band_GetNoDataValue) )
        return GDALPamRasterBand::GetNoDataValue(pbSuccess);

    CLIENT_ENTER();
    return GetDouble(INSTR_Band_GetNoDataValue, pbSuccess);
}

/************************************************************************/
/*                             GetMaximum()                             */
/************************************************************************/

double GDALClientRasterBand::GetMaximum( int *pbSuccess )
{
    if( !SupportsInstr(INSTR_Band_GetMaximum) )
        return GDALPamRasterBand::GetMaximum(pbSuccess);

    CLIENT_ENTER();
    return GetDouble(INSTR_Band_GetMaximum, pbSuccess);
}

/************************************************************************/
/*                             GetMinimum()                             */
/************************************************************************/

double GDALClientRasterBand::GetMinimum( int *pbSuccess )
{
    if( !SupportsInstr(INSTR_Band_GetMinimum) )
        return GDALPamRasterBand::GetMinimum(pbSuccess);

    CLIENT_ENTER();
    return GetDouble(INSTR_Band_GetMinimum, pbSuccess);
}

/************************************************************************/
/*                             GetOffset()                              */
/************************************************************************/

double GDALClientRasterBand::GetOffset( int *pbSuccess )
{
    if( !SupportsInstr(INSTR_Band_GetOffset) )
        return GDALPamRasterBand::GetOffset(pbSuccess);

    CLIENT_ENTER();
    return GetDouble(INSTR_Band_GetOffset, pbSuccess);
}

/************************************************************************/
/*                              GetScale()                              */
/************************************************************************/

double GDALClientRasterBand::GetScale( int *pbSuccess )
{
    if( !SupportsInstr(INSTR_Band_GetScale) )
        return GDALPamRasterBand::GetScale(pbSuccess);

    CLIENT_ENTER();
    return GetDouble(INSTR_Band_GetScale, pbSuccess);
}

/************************************************************************/
/*                           GetColorTable()                            */
/************************************************************************/

GDALColorTable *GDALClientRasterBand::GetColorTable()
{
    if( !SupportsInstr(INSTR_Band_GetColorTable) )
        return GDALPamRasterBand::GetColorTable();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetColorTable) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    GDALColorTable* poNewColorTable = nullptr;
    if( !GDALPipeRead(p, &poNewColorTable) )
        return nullptr;

    if( poNewColorTable != nullptr && poColorTable != nullptr )
    {
        *poColorTable = *poNewColorTable;
        delete poNewColorTable;
    }
    else if( poNewColorTable != nullptr && poColorTable == nullptr )
    {
        poColorTable = poNewColorTable;
    }
    else if( poColorTable != nullptr )
    {
        delete poColorTable;
        poColorTable = nullptr;
    }

    GDALConsumeErrors(p);
    return poColorTable;
}

/************************************************************************/
/*                            GetUnitType()                             */
/************************************************************************/

const char *GDALClientRasterBand::GetUnitType()
{
    if( !SupportsInstr(INSTR_Band_GetUnitType) )
        return GDALPamRasterBand::GetUnitType();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetUnitType) )
        return "";
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return "";

    CPLFree(pszUnitType);
    pszUnitType = nullptr;
    if( !GDALPipeRead(p, &pszUnitType) )
        return "";
    GDALConsumeErrors(p);
    return pszUnitType ? pszUnitType : "";
}

/************************************************************************/
/*                            SetUnitType()                             */
/************************************************************************/

CPLErr GDALClientRasterBand::SetUnitType( const char * pszUnit )
{
    if( !SupportsInstr(INSTR_Band_SetUnitType) )
        return GDALPamRasterBand::SetUnitType(pszUnit);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetUnitType) ||
        !GDALPipeWrite(p, pszUnit) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}
/************************************************************************/
/*                           SetColorTable()                            */
/************************************************************************/

CPLErr GDALClientRasterBand::SetColorTable(GDALColorTable* poColorTableIn)
{
    if( !SupportsInstr(INSTR_Band_SetColorTable) )
        return GDALPamRasterBand::SetColorTable(poColorTableIn);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetColorTable) )
        return CE_Failure;
    if( !GDALPipeWrite(p, poColorTableIn) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                              SetDouble()                             */
/************************************************************************/

CPLErr GDALClientRasterBand::SetDouble( InstrEnum instr, double dfVal )
{
    if( !WriteInstr(instr) ||
        !GDALPipeWrite(p, dfVal) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                           SetNoDataValue()                           */
/************************************************************************/

CPLErr GDALClientRasterBand::SetNoDataValue( double dfVal )
{
    if( !SupportsInstr(INSTR_Band_SetNoDataValue) )
        return GDALPamRasterBand::SetNoDataValue(dfVal);

    CLIENT_ENTER();
    return SetDouble(INSTR_Band_SetNoDataValue, dfVal);
}

/************************************************************************/
/*                         DeleteNoDataValue()                          */
/************************************************************************/

CPLErr GDALClientRasterBand::DeleteNoDataValue()
{
    if( !SupportsInstr(INSTR_Band_DeleteNoDataValue) )
        return GDALPamRasterBand::DeleteNoDataValue();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_DeleteNoDataValue) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                             SetScale()                               */
/************************************************************************/

CPLErr GDALClientRasterBand::SetScale( double dfVal )
{
    if( !SupportsInstr(INSTR_Band_SetScale) )
        return GDALPamRasterBand::SetScale(dfVal);

    CLIENT_ENTER();
    return SetDouble(INSTR_Band_SetScale, dfVal);
}

/************************************************************************/
/*                             SetOffset()                              */
/************************************************************************/

CPLErr GDALClientRasterBand::SetOffset( double dfVal )
{
    if( !SupportsInstr(INSTR_Band_SetOffset) )
        return GDALPamRasterBand::SetOffset(dfVal);

    CLIENT_ENTER();
    return SetDouble(INSTR_Band_SetOffset, dfVal);
}

/************************************************************************/
/*                            GetOverview()                             */
/************************************************************************/

GDALRasterBand *GDALClientRasterBand::GetOverview(int iOverview)
{
    if( !SupportsInstr(INSTR_Band_GetOverview) )
        return GDALPamRasterBand::GetOverview(iOverview);

    CLIENT_ENTER();
    std::map<int, GDALRasterBand*>::iterator oIter =
        aMapOvrBandsCurrent.find(iOverview);
    if( oIter != aMapOvrBandsCurrent.end() )
        return oIter->second;

    if( !WriteInstr(INSTR_Band_GetOverview) ||
        !GDALPipeWrite(p, iOverview) )
        return nullptr;

    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    GDALRasterBand* poBand = nullptr;
    if( !GDALPipeRead(p, static_cast<GDALClientDataset*>(nullptr), &poBand, abyCaps) )
        return nullptr;

    GDALConsumeErrors(p);

    aMapOvrBands[iOverview] = poBand;
    aMapOvrBandsCurrent[iOverview] = poBand;
    return poBand;
}

/************************************************************************/
/*                            GetMaskBand()                             */
/************************************************************************/

GDALRasterBand *GDALClientRasterBand::GetMaskBand()
{
    if( !SupportsInstr(INSTR_Band_GetMaskBand) )
        return GDALPamRasterBand::GetMaskBand();

    CLIENT_ENTER();
    if( poMaskBand )
        return poMaskBand;

    if( !WriteInstr(INSTR_Band_GetMaskBand) )
        return CreateFakeMaskBand();

    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return CreateFakeMaskBand();

    GDALRasterBand* poBand = nullptr;
    if( !GDALPipeRead(p, static_cast<GDALClientDataset*>(nullptr), &poBand, abyCaps) )
        return CreateFakeMaskBand();

    GDALConsumeErrors(p);

    poMaskBand = poBand;
    return poMaskBand;
}

/************************************************************************/
/*                            GetMaskFlags()                            */
/************************************************************************/

int GDALClientRasterBand::GetMaskFlags()
{
    if( !SupportsInstr(INSTR_Band_GetMaskFlags) )
        return GDALPamRasterBand::GetMaskFlags();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetMaskFlags) )
        return 0;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return 0;
    int l_nFlags;
    if( !GDALPipeRead(p, &l_nFlags) )
        return 0;
    GDALConsumeErrors(p);
    return l_nFlags;
}

/************************************************************************/
/*                           CreateMaskBand()                           */
/************************************************************************/

CPLErr GDALClientRasterBand::CreateMaskBand( int nFlagsIn )
{
    if( !SupportsInstr(INSTR_Band_CreateMaskBand) )
        return GDALPamRasterBand::CreateMaskBand(nFlagsIn);

    CLIENT_ENTER();
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_INTERNAL_MASK_TO_8BIT", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_INTERNAL_MASK", bRecycleChild);
    if( !WriteInstr(INSTR_Band_CreateMaskBand) ||
        !GDALPipeWrite(p, nFlagsIn) )
        return CE_Failure;
    CPLErr eErr = CPLErrOnlyRet(p);
    if( eErr == CE_None && poMaskBand != nullptr )
    {
        apoOldMaskBands.push_back(poMaskBand);
        poMaskBand = nullptr;
    }
    return eErr;
}

/************************************************************************/
/*                                Fill()                                */
/************************************************************************/

CPLErr GDALClientRasterBand::Fill(double dfRealValue, double dfImaginaryValue)
{
    if( !SupportsInstr(INSTR_Band_Fill) )
        return GDALPamRasterBand::Fill(dfRealValue, dfImaginaryValue);

    InvalidateCachedLines();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_Fill) ||
        !GDALPipeWrite(p, dfRealValue) ||
        !GDALPipeWrite(p, dfImaginaryValue) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                           BuildOverviews()                           */
/************************************************************************/

CPLErr GDALClientRasterBand::BuildOverviews( const char * pszResampling,
                                             int nOverviews,
                                             int * panOverviewList,
                                             GDALProgressFunc pfnProgress,
                                             void * pProgressData )
{
    if( !SupportsInstr(INSTR_Band_BuildOverviews) )
        return GDALPamRasterBand::BuildOverviews(pszResampling, nOverviews, panOverviewList,
                                                 pfnProgress, pProgressData);

    InvalidateCachedLines();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_BuildOverviews) ||
        !GDALPipeWrite(p, pszResampling) ||
        !GDALPipeWrite(p, nOverviews) ||
        !GDALPipeWrite(p, nOverviews * static_cast<int>(sizeof(int)), panOverviewList) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                           GetDefaultRAT()                            */
/************************************************************************/

GDALRasterAttributeTable *GDALClientRasterBand::GetDefaultRAT()
{
    if( !SupportsInstr(INSTR_Band_GetDefaultRAT) )
        return GDALPamRasterBand::GetDefaultRAT();

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_GetDefaultRAT) )
        return nullptr;
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return nullptr;

    GDALRasterAttributeTable* poNewRAT = nullptr;
    if( !GDALPipeRead(p, &poNewRAT) )
        return nullptr;

    if( poNewRAT != nullptr && poRAT != nullptr )
    {
        *poRAT = *poNewRAT;
        delete poNewRAT;
    }
    else if( poNewRAT != nullptr && poRAT == nullptr )
    {
        poRAT = poNewRAT;
    }
    else if( poRAT != nullptr )
    {
        delete poRAT;
        poRAT = nullptr;
    }

    GDALConsumeErrors(p);
    return poRAT;
}

/************************************************************************/
/*                           SetDefaultRAT()                            */
/************************************************************************/

CPLErr GDALClientRasterBand::SetDefaultRAT( const GDALRasterAttributeTable * poRATIn )
{
    if( !SupportsInstr(INSTR_Band_SetDefaultRAT) )
        return GDALPamRasterBand::SetDefaultRAT(poRATIn);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_SetDefaultRAT) ||
        !GDALPipeWrite(p, poRATIn) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                             AdviseRead()                             */
/************************************************************************/

CPLErr GDALClientRasterBand::AdviseRead( int nXOff, int nYOff, int nXSize, int nYSize,
                                     int nBufXSize, int nBufYSize,
                                     GDALDataType eDT, char **papszOptions )
{
    if( !SupportsInstr(INSTR_Band_AdviseRead) )
        return GDALPamRasterBand::AdviseRead(nXOff, nYOff, nXSize, nYSize,
                                             nBufXSize, nBufYSize,
                                             eDT, papszOptions);

    CLIENT_ENTER();
    if( !WriteInstr(INSTR_Band_AdviseRead) ||
        !GDALPipeWrite(p, nXOff) ||
        !GDALPipeWrite(p, nYOff) ||
        !GDALPipeWrite(p, nXSize) ||
        !GDALPipeWrite(p, nYSize) ||
        !GDALPipeWrite(p, nBufXSize) ||
        !GDALPipeWrite(p, nBufYSize) ||
        !GDALPipeWrite(p, eDT) ||
        !GDALPipeWrite(p, papszOptions) )
        return CE_Failure;
    return CPLErrOnlyRet(p);
}

/************************************************************************/
/*                        CreateAndConnect()                            */
/************************************************************************/

GDALClientDataset* GDALClientDataset::CreateAndConnect()
{
    GDALServerSpawnedProcess* l_ssp = GDALServerSpawnAsync();
    if( l_ssp == nullptr )
        return nullptr;
    return new GDALClientDataset(l_ssp);
}

/************************************************************************/
/*                                Init()                                */
/************************************************************************/

int GDALClientDataset::Init(const char* pszFilename, GDALAccess eAccessIn,
                            char** papszOpenOptionsIn)
{
    // FIXME find a way of transmitting the relevant config options to the forked Open() ?
    GDALPipeWriteConfigOption(p, "GTIFF_POINT_GEO_IGNORE", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_OVR_BLOCKSIZE", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_INTERNAL_MASK_TO_8BIT", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GTIFF_LINEAR_UNITS", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GTIFF_IGNORE_READ_ERRORS", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_RENDERING_OPTIONS", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_DPI", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_LIB", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_LAYERS", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_LAYERS_OFF", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_JPEG_TO_RGB", bRecycleChild);
    GDALPipeWriteConfigOption(p, "RPFTOC_FORCE_RGBA", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_NETCDF_BOTTOMUP", bRecycleChild);
    GDALPipeWriteConfigOption(p, "OGR_SQLITE_SYNCHRONOUS", bRecycleChild);

    char* pszCWD = CPLGetCurrentDir();

    if( !GDALPipeWrite(p, INSTR_Open) ||
        !GDALPipeWrite(p, eAccessIn) ||
        !GDALPipeWrite(p, pszFilename) ||
        !GDALPipeWrite(p, pszCWD) ||
        !GDALPipeWrite(p, papszOpenOptionsIn))
    {
        CPLFree(pszCWD);
        return FALSE;
    }
    CPLFree(pszCWD);
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return FALSE;
    int bRet = FALSE;
    if( !GDALPipeRead(p, &bRet) )
        return FALSE;

    if( bRet == FALSE )
    {
        GDALConsumeErrors(p);
        return FALSE;
    }

    if( !GDALPipeRead(p, sizeof(abyCaps), abyCaps) )
        return FALSE;

    eAccess = eAccessIn;

    char* pszDescription = nullptr;
    if( !GDALPipeRead(p, &pszDescription) )
        return FALSE;
    if( pszDescription != nullptr )
        SetDescription(pszDescription);
    CPLFree(pszDescription);

    char* pszDriverName = nullptr;
    if( !GDALPipeRead(p, &pszDriverName) )
        return FALSE;

    if( pszDriverName != nullptr )
    {
        bFreeDriver = true;
        poDriver = new GDALDriver();
        poDriver->SetDescription(pszDriverName);
        CPLFree(pszDriverName);
        pszDriverName = nullptr;

        while(true)
        {
            char *pszKey = nullptr;
            char *pszVal = nullptr;
            if( !GDALPipeRead(p, &pszKey) )
                return FALSE;
            if( pszKey == nullptr )
                break;
            if( !GDALPipeRead(p, &pszVal) )
            {
                CPLFree(pszKey);
                CPLFree(pszVal);
                return FALSE;
            }
            poDriver->SetMetadataItem( pszKey, pszVal );
            CPLFree(pszKey);
            CPLFree(pszVal);
        }
    }
    CPLFree(pszDriverName);

    int bAllSame;
    if( !GDALPipeRead(p, &nRasterXSize) ||
        !GDALPipeRead(p, &nRasterYSize) ||
        !GDALPipeRead(p, &nBands) ||
        !GDALPipeRead(p, &bAllSame) )
        return FALSE;

    for(int i=0;i<nBands;i++)
    {
        GDALRasterBand* poBand = nullptr;
        if( i > 0 && bAllSame )
        {
            GDALClientRasterBand* poFirstBand = cpl::down_cast<GDALClientRasterBand*>(GetRasterBand(1));
            int nBlockXSize, nBlockYSize;
            poFirstBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
            poBand = new GDALClientRasterBand(p, poFirstBand->GetSrvBand() + i,
                                              this, i + 1, poFirstBand->GetAccess(),
                                              poFirstBand->GetXSize(),
                                              poFirstBand->GetYSize(),
                                              poFirstBand->GetRasterDataType(),
                                              nBlockXSize, nBlockYSize,
                                              abyCaps);
        }
        else
        {
            if( !GDALPipeRead(p, this, &poBand, abyCaps) )
                return FALSE;
            if( poBand == nullptr )
                return FALSE;
        }

        SetBand(i+1, poBand);
    }

    GDALConsumeErrors(p);

    return TRUE;
}

/************************************************************************/
/*                     GDALClientDatasetGetFilename()                   */
/************************************************************************/

static int IsSeparateExecutable()
{
#ifdef WIN32
    return TRUE;
#else
    const char* pszSpawnServer = CPLGetConfigOption("GDAL_API_PROXY_SERVER", "NO");
    if( EQUAL(pszSpawnServer, "NO") || EQUAL(pszSpawnServer, "OFF") ||
        EQUAL(pszSpawnServer, "FALSE")  || EQUAL(pszSpawnServer, "0") )
        return FALSE;
    else
        return TRUE;
#endif
}

const char* GDALClientDatasetGetFilename(const char* pszFilename)
{
    const char* pszSpawn;
    if( STARTS_WITH_CI(pszFilename, "API_PROXY:") )
    {
        pszFilename += strlen("API_PROXY:");
        pszSpawn = "YES";
    }
    else
    {
        pszSpawn = CPLGetConfigOption("GDAL_API_PROXY", "NO");
        if( EQUAL(pszSpawn, "NO") || EQUAL(pszSpawn, "OFF") ||
            EQUAL(pszSpawn, "FALSE") || EQUAL(pszSpawn, "0") )
        {
            return nullptr;
        }
    }

    /* Those datasets cannot work in a multi-process context */
     /* /vsistdin/ and /vsistdout/ can work on Unix in the fork() only context (i.e. GDAL_API_PROXY_SERVER undefined) */
     /* since the forked process will inherit the same descriptors as the parent */

    if( STARTS_WITH_CI(pszFilename, "MEM:::") ||
        strstr(pszFilename, "/vsimem/") != nullptr ||
        strstr(pszFilename, "/vsimem\\") != nullptr ||
        (strstr(pszFilename, "/vsistdout/") != nullptr && IsSeparateExecutable()) ||
        (strstr(pszFilename, "/vsistdin/") != nullptr && IsSeparateExecutable()) ||
        STARTS_WITH_CI(pszFilename, "NUMPY:::") )
        return nullptr;

    if( !(EQUAL(pszSpawn, "YES") || EQUAL(pszSpawn, "ON") ||
          EQUAL(pszSpawn, "TRUE") || EQUAL(pszSpawn, "1")) )
    {
        CPLString osExt(CPLGetExtension(pszFilename));

        /* If the file extension is listed in the GDAL_API_PROXY, then */
        /* we have a match */
        char** papszTokens = CSLTokenizeString2( pszSpawn, " ,", CSLT_HONOURSTRINGS );
        if( CSLFindString(papszTokens, osExt) >= 0 )
        {
            CSLDestroy(papszTokens);
            return pszFilename;
        }

        /* Otherwise let's suppose that driver names are listed in GDAL_API_PROXY */
        /* and check if the file extension matches the extension declared by the */
        /* driver */
        char** papszIter = papszTokens;
        while( *papszIter != nullptr )
        {
            GDALDriverH hDriver = GDALGetDriverByName(*papszIter);
            if( hDriver != nullptr )
            {
                const char* pszDriverExt =
                    GDALGetMetadataItem(hDriver, GDAL_DMD_EXTENSION, nullptr);
                if( pszDriverExt != nullptr && EQUAL(pszDriverExt, osExt) )
                {
                    CSLDestroy(papszTokens);
                    return pszFilename;
                }
            }
            papszIter++;
        }
        CSLDestroy(papszTokens);
        return nullptr;
    }

    return pszFilename;
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *GDALClientDataset::Open( GDALOpenInfo * poOpenInfo )
{
    const char* pszFilename =
        GDALClientDatasetGetFilename(poOpenInfo->pszFilename);
    if( pszFilename == nullptr )
        return nullptr;

    CLIENT_ENTER();

    GDALClientDataset* poDS = CreateAndConnect();
    if( poDS == nullptr )
        return nullptr;

    CPLErrorReset();
    if( !poDS->Init(pszFilename, poOpenInfo->eAccess,
                    poOpenInfo->papszOpenOptions) )
    {
        if( CPLGetLastErrorType() == 0 )
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Could not open %s",
                    pszFilename);
        }
        delete poDS;
        return nullptr;
    }
    if( poDS != nullptr )
        CPLErrorReset();

    return poDS;
}

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/

int GDALClientDataset::Identify( GDALOpenInfo * poOpenInfo )
{
    const char* pszFilename =
        GDALClientDatasetGetFilename(poOpenInfo->pszFilename);
    if( pszFilename == nullptr )
        return FALSE;

    CLIENT_ENTER();

    GDALServerSpawnedProcess* l_ssp = GDALServerSpawnAsync();
    if( l_ssp == nullptr )
        return FALSE;

    char* pszCWD = CPLGetCurrentDir();

    GDALPipe* l_p = l_ssp->p;
    if( !GDALPipeWrite(l_p, INSTR_Identify) ||
        !GDALPipeWrite(l_p, pszFilename) ||
        !GDALPipeWrite(l_p, pszCWD) ||
        !GDALSkipUntilEndOfJunkMarker(l_p) )
    {
        GDALServerSpawnAsyncFinish(l_ssp);
        CPLFree(pszCWD);
        return FALSE;
    }

    CPLFree(pszCWD);

    int bRet;
    if( !GDALPipeRead(l_p, &bRet) )
    {
        GDALServerSpawnAsyncFinish(l_ssp);
        return FALSE;
    }

    GDALServerSpawnAsyncFinish(l_ssp);
    return bRet;
}

/************************************************************************/
/*                     GDALClientDatasetQuietDelete()                   */
/************************************************************************/

static int GDALClientDatasetQuietDelete(GDALPipe* p,
                                    const char* pszFilename)
{
    char* pszCWD = CPLGetCurrentDir();
    if( !GDALPipeWrite(p, INSTR_QuietDelete) ||
        !GDALPipeWrite(p, pszFilename) ||
        !GDALPipeWrite(p, pszCWD) ||
        !GDALSkipUntilEndOfJunkMarker(p) )
    {
        CPLFree(pszCWD);
        return FALSE;
    }
    CPLFree(pszCWD);
    GDALConsumeErrors(p);
    return TRUE;
}

/************************************************************************/
/*                          mCreateCopy()                               */
/************************************************************************/

int GDALClientDataset::mCreateCopy( const char* pszFilename,
                                    GDALDataset* poSrcDS,
                                    int bStrict, char** papszOptions,
                                    GDALProgressFunc pfnProgress,
                                    void * pProgressData )
{
    /*if( !SupportsInstr(INSTR_CreateCopy) )
    {
        CPLError(CE_Failure, CPLE_NotSupported, "CreateCopy() not supported by server");
        return FALSE;
    }*/

    const char* pszServerDriver =
        CSLFetchNameValue(papszOptions, "SERVER_DRIVER");
    if( pszServerDriver == nullptr )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Creation options should contain a SERVER_DRIVER item");
        return FALSE;
    }

    if( !CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false) )
    {
        if( !GDALClientDatasetQuietDelete(p, pszFilename) )
            return FALSE;
    }

    GDALPipeWriteConfigOption(p, "GTIFF_POINT_GEO_IGNORE", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GTIFF_DELETE_ON_ERROR", bRecycleChild);
    GDALPipeWriteConfigOption(p, "ESRI_XML_PAM", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_TIFF_INTERNAL_MASK_TO_8BIT", bRecycleChild);
    GDALPipeWriteConfigOption(p, "OGR_SQLITE_SYNCHRONOUS", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_WRITE_GEOREF_ON_IMAGE", bRecycleChild);
    GDALPipeWriteConfigOption(p, "GDAL_PDF_OGC_BP_WRITE_WKT", bRecycleChild);

    char* pszCWD = CPLGetCurrentDir();

    if( !GDALPipeWrite(p, INSTR_CreateCopy) ||
        !GDALPipeWrite(p, pszFilename) ||
        !GDALPipeWrite(p, poSrcDS->GetDescription()) ||
        !GDALPipeWrite(p, pszCWD) ||
        !GDALPipeWrite(p, bStrict) ||
        !GDALPipeWrite(p, papszOptions) )
    {
        CPLFree(pszCWD);
        return FALSE;
    }
    CPLFree(pszCWD);

    int bDriverOK;
    if( !GDALPipeRead(p, &bDriverOK) )
        return FALSE;

    if( !bDriverOK )
    {
        GDALConsumeErrors(p);
        return FALSE;
    }

    if( GDALServerLoop(p,
                       poSrcDS,
                       pfnProgress, pProgressData) != 0 )
    {
        GDALConsumeErrors(p);
        return FALSE;
    }

    GDALConsumeErrors(p);

    return Init(nullptr, GA_Update, nullptr);
}

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

GDALDataset *GDALClientDataset::CreateCopy( const char * pszFilename,
                                            GDALDataset * poSrcDS, int bStrict,
                                            char ** papszOptions,
                                            GDALProgressFunc pfnProgress,
                                            void * pProgressData )
{
    CLIENT_ENTER();

    GDALClientDataset* poDS = CreateAndConnect();
    if( poDS !=nullptr && !poDS->mCreateCopy(pszFilename, poSrcDS, bStrict,
                                          papszOptions,
                                          pfnProgress, pProgressData) )
    {
        delete poDS;
        return nullptr;
    }

    return poDS;
}

/************************************************************************/
/*                            mCreate()                                 */
/************************************************************************/

int GDALClientDataset::mCreate( const char * pszFilename,
                            int nXSize, int nYSize, int nBandsIn,
                            GDALDataType eType,
                            char ** papszOptions )
{
    /*if( !SupportsInstr(INSTR_Create) )
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Create() not supported by server");
        return FALSE;
    }*/

    const char* pszServerDriver =
        CSLFetchNameValue(papszOptions, "SERVER_DRIVER");
    if( pszServerDriver == nullptr )
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Creation options should contain a SERVER_DRIVER item");
        return FALSE;
    }

    if( !CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false) )
    {
        if( !GDALClientDatasetQuietDelete(p, pszFilename) )
            return FALSE;
    }

    GDALPipeWriteConfigOption(p,"GTIFF_POINT_GEO_IGNORE", bRecycleChild);
    GDALPipeWriteConfigOption(p,"GTIFF_DELETE_ON_ERROR", bRecycleChild);
    GDALPipeWriteConfigOption(p,"ESRI_XML_PAM", bRecycleChild);
    GDALPipeWriteConfigOption(p,"GTIFF_DONT_WRITE_BLOCKS", bRecycleChild);

    char* pszCWD = CPLGetCurrentDir();

    if( !GDALPipeWrite(p, INSTR_Create) ||
        !GDALPipeWrite(p, pszFilename) ||
        !GDALPipeWrite(p, pszCWD) ||
        !GDALPipeWrite(p, nXSize) ||
        !GDALPipeWrite(p, nYSize) ||
        !GDALPipeWrite(p, nBandsIn) ||
        !GDALPipeWrite(p, eType) ||
        !GDALPipeWrite(p, papszOptions) )
    {
        CPLFree(pszCWD);
        return FALSE;
    }
    CPLFree(pszCWD);
    if( !GDALSkipUntilEndOfJunkMarker(p) )
        return FALSE;
    int bOK;
    if( !GDALPipeRead(p, &bOK) )
        return FALSE;

    if( !bOK )
    {
        GDALConsumeErrors(p);
        return FALSE;
    }

    GDALConsumeErrors(p);

    return Init(nullptr, GA_Update, nullptr);
}

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

GDALDataset* GDALClientDataset::Create( const char * pszName,
                                    int nXSize, int nYSize, int nBandsIn,
                                    GDALDataType eType,
                                    char ** papszOptions )
{
    CLIENT_ENTER();

    GDALClientDataset* poDS = CreateAndConnect();
    if( poDS != nullptr && !poDS->mCreate(pszName, nXSize, nYSize, nBandsIn,
                                       eType, papszOptions) )
    {
        delete poDS;
        return nullptr;
    }

    return poDS;
}

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

CPLErr GDALClientDataset::Delete( const char * pszFilename )
{
    pszFilename =
        GDALClientDatasetGetFilename(pszFilename);
    if( pszFilename == nullptr )
        return CE_Failure;

    CLIENT_ENTER();

    GDALServerSpawnedProcess* l_ssp = GDALServerSpawnAsync();
    if( l_ssp == nullptr )
        return CE_Failure;

    GDALPipe* l_p = l_ssp->p;
    if( !GDALClientDatasetQuietDelete(l_p, pszFilename) )
    {
        GDALServerSpawnAsyncFinish(l_ssp);
        return CE_Failure;
    }

    GDALServerSpawnAsyncFinish(l_ssp);
    return CE_None;
}

/************************************************************************/
/*                      GDALUnloadAPIPROXYDriver()                      */
/************************************************************************/
static GDALDriver* poAPIPROXYDriver = nullptr;

static void GDALUnloadAPIPROXYDriver( GDALDriver* /* poDriver */ )
{
    if( bRecycleChild )
    {
        /* Kill all unused descriptors */
        bRecycleChild = false;
        for(int i=0;i<nMaxRecycled;i++)
        {
            if( aspRecycled[i] != nullptr )
            {
                GDALServerSpawnAsyncFinish(aspRecycled[i]);
                aspRecycled[i] = nullptr;
            }
        }
    }
    poAPIPROXYDriver = nullptr;
}

/************************************************************************/
/*                       GDALGetAPIPROXYDriver()                        */
/************************************************************************/

GDALDriver* GDALGetAPIPROXYDriver()
{
    // Call CPLGetConfigOption before holding DM mutex to avoid confusing
    // deadlock detectors that keep track of the order of lock acquisitions
    // and error out if the order is inverted.
    const char* pszConnPool =
        CPLGetConfigOption("GDAL_API_PROXY_CONN_POOL", "YES");

    CPLMutexHolderD(GDALGetphDMMutex());
    if( poAPIPROXYDriver != nullptr )
        return poAPIPROXYDriver;

#ifdef DEBUG_VERBOSE
    CPL_STATIC_ASSERT(
        INSTR_END + 1 == sizeof(apszInstr) / sizeof(apszInstr[0]));
#endif
    // If asserted, change
    // GDAL_CLIENT_SERVER_PROTOCOL_MAJOR / GDAL_CLIENT_SERVER_PROTOCOL_MINOR
    // cppcheck-suppress duplicateExpression
    CPL_STATIC_ASSERT(INSTR_END + 1 == 81);

    if( atoi(pszConnPool) > 0 )
    {
        bRecycleChild = true;
        nMaxRecycled = std::min(atoi(pszConnPool), MAX_RECYCLED);
    }
    else if( CPLTestBool(pszConnPool) )
    {
        bRecycleChild = true;
        nMaxRecycled = DEFAULT_RECYCLED;
    }
    memset(aspRecycled, 0, sizeof(aspRecycled));

    poAPIPROXYDriver = new GDALDriver();

    poAPIPROXYDriver->SetDescription("API_PROXY");
    poAPIPROXYDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poAPIPROXYDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "API_PROXY");

    poAPIPROXYDriver->pfnOpen = GDALClientDataset::Open;
    poAPIPROXYDriver->pfnIdentify = GDALClientDataset::Identify;
    poAPIPROXYDriver->pfnCreateCopy = GDALClientDataset::CreateCopy;
    poAPIPROXYDriver->pfnCreate = GDALClientDataset::Create;
    poAPIPROXYDriver->pfnDelete = GDALClientDataset::Delete;
    poAPIPROXYDriver->pfnUnloadDriver = GDALUnloadAPIPROXYDriver;

    return poAPIPROXYDriver;
}
