[Git][debian-gis-team/gdal-grass][master] 2 commits: Add upstream patch to fix FTBFS with GDAL 3.12.0. (closes: #1118730)

Bas Couwenberg (@sebastic) gitlab at salsa.debian.org
Sat Oct 25 10:54:59 BST 2025



Bas Couwenberg pushed to branch master at Debian GIS Project / gdal-grass


Commits:
2c9240ab by Bas Couwenberg at 2025-10-25T11:48:06+02:00
Add upstream patch to fix FTBFS with GDAL 3.12.0. (closes: #1118730)

- - - - -
d7651cab by Bas Couwenberg at 2025-10-25T11:50:40+02:00
Set distribution to unstable.

- - - - -


3 changed files:

- debian/changelog
- + debian/patches/gdal-3.12.patch
- + debian/patches/series


Changes:

=====================================
debian/changelog
=====================================
@@ -1,9 +1,11 @@
-libgdal-grass (1:1.0.4-2) UNRELEASED; urgency=medium
+libgdal-grass (1:1.0.4-2) unstable; urgency=medium
 
   * Update lintian overrides.
   * Drop Rules-Requires-Root: no, default since dpkg 1.22.13.
+  * Add upstream patch to fix FTBFS with GDAL 3.12.0.
+    (closes: #1118730)
 
- -- Bas Couwenberg <sebastic at debian.org>  Fri, 12 Sep 2025 17:36:50 +0200
+ -- Bas Couwenberg <sebastic at debian.org>  Sat, 25 Oct 2025 11:50:27 +0200
 
 libgdal-grass (1:1.0.4-1) unstable; urgency=medium
 


=====================================
debian/patches/gdal-3.12.patch
=====================================
@@ -0,0 +1,2282 @@
+Description: Fix FTBFS with GDAL 3.12.0.
+Author: Nicklas Larsson <n_larsson at yahoo.com>, Even Rouault <even.rouault at spatialys.com>
+Origin: https://github.com/OSGeo/gdal-grass/commits/main/
+Forwarded: not-needed
+
+--- a/grass.cpp
++++ b/grass.cpp
+@@ -30,7 +30,8 @@
+  * DEALINGS IN THE SOFTWARE.
+  ****************************************************************************/
+ 
+-#include <stdlib.h>
++#include <array>
++#include <cstring>
+ 
+ #include "cpl_string.h"
+ #include "gdal_frmts.h"
+@@ -51,20 +52,23 @@ extern "C"
+ #include <grass/gprojects.h>
+ #include <grass/gis.h>
+ 
+-    char *GPJ_grass_to_wkt(const struct Key_Value *, const struct Key_Value *,
+-                           int, int);
++    auto GPJ_grass_to_wkt(const struct Key_Value *, const struct Key_Value *,
++                          int, int) -> char *;
+ 
+     void GDALRegister_GRASS();
+ }
+ 
+-#define GRASS_MAX_COLORS 100000  // what is the right value
++enum
++{
++    BUFF_SIZE = 200,
++    GRASS_MAX_COLORS = 100000
++};
+ 
+ /************************************************************************/
+ /*                         Grass2CPLErrorHook()                         */
+ /************************************************************************/
+ 
+-static int Grass2CPLErrorHook(char *pszMessage, int bFatal)
+-
++static auto Grass2CPLErrorHook(char *pszMessage, int bFatal) -> int
+ {
+     if (!bFatal)
+         //CPLDebug( "GRASS", "%s", pszMessage );
+@@ -76,6 +80,19 @@ static int Grass2CPLErrorHook(char *pszM
+     return 0;
+ }
+ 
++struct GRASSRasterPath
++{
++    std::string gisdbase;
++    std::string location;
++    std::string mapset;
++    std::string element;
++    std::string name;
++
++    explicit GRASSRasterPath(const char *path);
++    auto isValid() -> bool;
++    auto isCellHD() -> bool;
++};
++
+ /************************************************************************/
+ /* ==================================================================== */
+ /*                              GRASSDataset                            */
+@@ -88,27 +105,34 @@ class GRASSDataset final : public GDALDa
+ {
+     friend class GRASSRasterBand;
+ 
+-    char *pszGisdbase;
+-    char *pszLocation; /* LOCATION_NAME */
+-    char *pszElement;  /* cellhd or group */
++    std::string osGisdbase;
++    std::string osLocation; /* LOCATION_NAME */
++    std::string osElement;  /* cellhd or group */
+ 
+-    struct Cell_head sCellInfo; /* raster region */
++    struct Cell_head sCellInfo
++    {
++    }; /* raster region */
+ 
+     OGRSpatialReference m_oSRS{};
+ 
+-    double adfGeoTransform[6];
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 12, 0)
++    GDALGeoTransform m_gt{};
++#else
++    std::array<double, 6> m_gt{0.0, 1.0, 0.0, 0.0, 0.0, 1.0};
++#endif
+ 
+   public:
+-    GRASSDataset();
+-    ~GRASSDataset() override;
++    explicit GRASSDataset(GRASSRasterPath &);
+ 
+-    const OGRSpatialReference *GetSpatialRef() const override;
+-    CPLErr GetGeoTransform(double *) override;
++    auto GetSpatialRef() const -> const OGRSpatialReference * override;
+ 
+-    static GDALDataset *Open(GDALOpenInfo *);
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 12, 0)
++    auto GetGeoTransform(GDALGeoTransform &) const -> CPLErr override;
++#else
++    auto GetGeoTransform(double *) -> CPLErr override;
++#endif
+ 
+-  private:
+-    static bool SplitPath(char *, char **, char **, char **, char **, char **);
++    static auto Open(GDALOpenInfo *) -> GDALDataset *;
+ };
+ 
+ /************************************************************************/
+@@ -121,74 +145,76 @@ class GRASSRasterBand final : public GDA
+ {
+     friend class GRASSDataset;
+ 
+-    char *pszCellName;
+-    char *pszMapset;
++    std::string osCellName;
++    std::string osMapset;
+     int hCell;
+     int nGRSType;      // GRASS raster type: CELL_TYPE, FCELL_TYPE, DCELL_TYPE
+     bool nativeNulls;  // use GRASS native NULL values
+ 
+-    struct Colors sGrassColors;
++    struct Colors sGrassColors
++    {
++    };
+     GDALColorTable *poCT;
+ 
+-    struct Cell_head sOpenWindow; /* the region when the raster was opened */
++    struct Cell_head sOpenWindow
++    {
++    }; /* the region when the raster was opened */
+ 
+     int bHaveMinMax;
+-    double dfCellMin;
+-    double dfCellMax;
++    double dfCellMin{0.0};
++    double dfCellMax{0.0};
+ 
+     double dfNoData;
+ 
+-    bool valid;
++    bool valid{false};
+ 
+   public:
+-    GRASSRasterBand(GRASSDataset *, int, const char *, const char *);
++    GRASSRasterBand(GRASSDataset *, int, std::string &, std::string &);
+     ~GRASSRasterBand() override;
+ 
+-    CPLErr IReadBlock(int, int, void *) override;
+-    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
+-                     GDALDataType, GSpacing nPixelSpace, GSpacing nLineSpace,
+-                     GDALRasterIOExtraArg *psExtraArg) override;
+-    GDALColorInterp GetColorInterpretation() override;
+-    GDALColorTable *GetColorTable() override;
+-    double GetMinimum(int *pbSuccess = NULL) override;
+-    double GetMaximum(int *pbSuccess = NULL) override;
+-    double GetNoDataValue(int *pbSuccess = NULL) override;
++    auto IReadBlock(int, int, void *) -> CPLErr override;
++    auto IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
++                   GDALDataType, GSpacing nPixelSpace, GSpacing nLineSpace,
++                   GDALRasterIOExtraArg *psExtraArg) -> CPLErr override;
++    auto GetColorInterpretation() -> GDALColorInterp override;
++    auto GetColorTable() -> GDALColorTable * override;
++    auto GetMinimum(int *pbSuccess = nullptr) -> double override;
++    auto GetMaximum(int *pbSuccess = nullptr) -> double override;
++    auto GetNoDataValue(int *pbSuccess = nullptr) -> double override;
+ 
+   private:
+     void SetWindow(struct Cell_head *);
+-    CPLErr ResetReading(struct Cell_head *);
++    auto ResetReading(struct Cell_head *) -> CPLErr;
+ };
+ 
+ /************************************************************************/
+ /*                          GRASSRasterBand()                           */
+ /************************************************************************/
+-
+ GRASSRasterBand::GRASSRasterBand(GRASSDataset *poDSIn, int nBandIn,
+-                                 const char *pszMapsetIn,
+-                                 const char *pszCellNameIn)
+-
++                                 std::string &pszMapsetIn,
++                                 std::string &pszCellNameIn)
++    : osCellName(pszCellNameIn), osMapset(pszMapsetIn),
++      nGRSType(Rast_map_type(osCellName.c_str(), osMapset.c_str()))
+ {
+-    struct Cell_head sCellInfo;
++    struct Cell_head sCellInfo
++    {
++    };
+ 
+     // Note: GISDBASE, LOCATION_NAME ans MAPSET was set in GRASSDataset::Open
+ 
+     this->poDS = poDSIn;
+     this->nBand = nBandIn;
+-    this->valid = false;
+ 
+-    this->pszCellName = G_store((char *)pszCellNameIn);
+-    this->pszMapset = G_store((char *)pszMapsetIn);
+-
+-    Rast_get_cellhd((char *)pszCellName, (char *)pszMapset, &sCellInfo);
+-    nGRSType = Rast_map_type((char *)pszCellName, (char *)pszMapset);
++    Rast_get_cellhd(osCellName.c_str(), osMapset.c_str(), &sCellInfo);
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Get min/max values.                                             */
+     /* -------------------------------------------------------------------- */
+-    struct FPRange sRange;
++    struct FPRange sRange
++    {
++    };
+ 
+-    if (Rast_read_fp_range((char *)pszCellName, (char *)pszMapset, &sRange) ==
+-        -1)
++    if (Rast_read_fp_range(osCellName.c_str(), osMapset.c_str(), &sRange) == -1)
+     {
+         bHaveMinMax = FALSE;
+     }
+@@ -242,7 +268,7 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+             }
+             else
+             {  // maximum is not known or full range is used
+-                CELL cval;
++                CELL cval = 0;
+                 this->eDataType = GDT_Int32;
+                 Rast_set_c_null_value(&cval, 1);
+                 dfNoData = (double)cval;
+@@ -251,7 +277,7 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+         }
+         else
+         {  // 3-4 bytes
+-            CELL cval;
++            CELL cval = 0;
+             this->eDataType = GDT_Int32;
+             Rast_set_c_null_value(&cval, 1);
+             dfNoData = (double)cval;
+@@ -260,7 +286,7 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+     }
+     else if (nGRSType == FCELL_TYPE)
+     {
+-        FCELL fval;
++        FCELL fval = NAN;
+         this->eDataType = GDT_Float32;
+         Rast_set_f_null_value(&fval, 1);
+         dfNoData = (double)fval;
+@@ -268,7 +294,7 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+     }
+     else if (nGRSType == DCELL_TYPE)
+     {
+-        DCELL dval;
++        DCELL dval = NAN;
+         this->eDataType = GDT_Float64;
+         Rast_set_d_null_value(&dval, 1);
+         dfNoData = (double)dval;
+@@ -281,18 +307,18 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+     Rast_set_window(&(poDSIn->sCellInfo));
+     // open the raster only for actual reading
+     hCell = -1;
+-    memcpy((void *)&sOpenWindow, (void *)&(poDSIn->sCellInfo),
+-           sizeof(struct Cell_head));
++    memcpy(static_cast<void *>(&sOpenWindow),
++           static_cast<void *>(&(poDSIn->sCellInfo)), sizeof(struct Cell_head));
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Do we have a color table?                                       */
+     /* -------------------------------------------------------------------- */
+-    poCT = NULL;
+-    if (Rast_read_colors((char *)pszCellName, (char *)pszMapset,
+-                         &sGrassColors) == 1)
++    poCT = nullptr;
++    if (Rast_read_colors(osCellName.c_str(), osMapset.c_str(), &sGrassColors) ==
++        1)
+     {
+-        int maxcolor;
+-        CELL min, max;
++        int maxcolor = 0;
++        CELL min = 0, max = 0;
+ 
+         Rast_get_c_color_range(&min, &max, &sGrassColors);
+ 
+@@ -332,15 +358,15 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+         poCT = new GDALColorTable();
+         for (int iColor = 0; iColor <= maxcolor; iColor++)
+         {
+-            int nRed, nGreen, nBlue;
++            int nRed = 0, nGreen = 0, nBlue = 0;
+             GDALColorEntry sColor;
+ 
+             if (Rast_get_c_color(&iColor, &nRed, &nGreen, &nBlue,
+                                  &sGrassColors))
+             {
+-                sColor.c1 = nRed;
+-                sColor.c2 = nGreen;
+-                sColor.c3 = nBlue;
++                sColor.c1 = (short)nRed;
++                sColor.c2 = (short)nGreen;
++                sColor.c3 = (short)nBlue;
+                 sColor.c4 = 255;
+ 
+                 poCT->SetColorEntry(iColor, &sColor);
+@@ -357,26 +383,28 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+         }
+ 
+         /* Create metadata entries for color table rules */
+-        char key[200], value[200];
++        std::array<char, BUFF_SIZE> value{};
++        std::array<char, BUFF_SIZE> key{};
+         int rcount = Rast_colors_count(&sGrassColors);
+ 
+-        snprintf(value, sizeof(value), "%d", rcount);
+-        this->SetMetadataItem("COLOR_TABLE_RULES_COUNT", value);
++        (void)std::snprintf(value.data(), BUFF_SIZE, "%d", rcount);
++        this->SetMetadataItem("COLOR_TABLE_RULES_COUNT", value.data());
+ 
+         /* Add the rules in reverse order */
+         for (int i = rcount - 1; i >= 0; i--)
+         {
+-            DCELL val1, val2;
+-            unsigned char r1, g1, b1, r2, g2, b2;
++            DCELL val1 = NAN, val2 = NAN;
++            unsigned char r1 = 0, g1 = 0, b1 = 0, r2 = 0, g2 = 0, b2 = 0;
+ 
+             Rast_get_fp_color_rule(&val1, &r1, &g1, &b1, &val2, &r2, &g2, &b2,
+                                    &sGrassColors, i);
+ 
+-            snprintf(key, sizeof(key), "COLOR_TABLE_RULE_RGB_%d",
+-                     rcount - i - 1);
+-            snprintf(value, sizeof(value), "%e %e %d %d %d %d %d %d", val1,
+-                     val2, r1, g1, b1, r2, g2, b2);
+-            this->SetMetadataItem(key, value);
++            (void)std::snprintf(key.data(), key.size(),
++                                "COLOR_TABLE_RULE_RGB_%d", rcount - i - 1);
++            (void)std::snprintf(value.data(), value.size(),
++                                "%e %e %d %d %d %d %d %d", val1, val2, r1, g1,
++                                b1, r2, g2, b2);
++            this->SetMetadataItem(key.data(), value.data());
+         }
+     }
+     else
+@@ -393,7 +421,7 @@ GRASSRasterBand::GRASSRasterBand(GRASSDa
+ 
+ GRASSRasterBand::~GRASSRasterBand()
+ {
+-    if (poCT != NULL)
++    if (poCT != nullptr)
+     {
+         Rast_free_colors(&sGrassColors);
+         delete poCT;
+@@ -401,12 +429,6 @@ GRASSRasterBand::~GRASSRasterBand()
+ 
+     if (hCell >= 0)
+         Rast_close(hCell);
+-
+-    if (pszCellName)
+-        G_free(pszCellName);
+-
+-    if (pszMapset)
+-        G_free(pszMapset);
+ }
+ 
+ /************************************************************************/
+@@ -430,11 +452,13 @@ void GRASSRasterBand::SetWindow(struct C
+     Rast_set_window(sNewWindow);
+ 
+     /* Set GRASS env to the current raster, don't open the raster */
+-    G_setenv_nogisrc("GISDBASE", ((GRASSDataset *)poDS)->pszGisdbase);
+-    G_setenv_nogisrc("LOCATION_NAME", ((GRASSDataset *)poDS)->pszLocation);
+-    G_setenv_nogisrc("MAPSET", pszMapset);
++    G_setenv_nogisrc("GISDBASE",
++                     (dynamic_cast<GRASSDataset *>(poDS))->osGisdbase.c_str());
++    G_setenv_nogisrc("LOCATION_NAME",
++                     (dynamic_cast<GRASSDataset *>(poDS))->osLocation.c_str());
++    G_setenv_nogisrc("MAPSET", osMapset.c_str());
+     G_reset_mapsets();
+-    G_add_mapset_to_search_path(pszMapset);
++    G_add_mapset_to_search_path(osMapset.c_str());
+ }
+ 
+ /************************************************************************/
+@@ -445,7 +469,7 @@ void GRASSRasterBand::SetWindow(struct C
+ /*                                                                      */
+ /* Returns CE_Failure if fails, otherwise CE_None                       */
+ /************************************************************************/
+-CPLErr GRASSRasterBand::ResetReading(struct Cell_head *sNewWindow)
++auto GRASSRasterBand::ResetReading(struct Cell_head *sNewWindow) -> CPLErr
+ {
+ 
+     /* Check if the window has changed */
+@@ -459,13 +483,15 @@ CPLErr GRASSRasterBand::ResetReading(str
+         sNewWindow->cols != sOpenWindow.cols)
+     {
+         SetWindow(sNewWindow);
+-        memcpy((void *)&sOpenWindow, (void *)sNewWindow,
+-               sizeof(struct Cell_head));
++        memcpy(static_cast<void *>(&sOpenWindow),
++               static_cast<void *>(sNewWindow), sizeof(struct Cell_head));
+     }
+     else
+     {
+         /* The windows are identical, check current window */
+-        struct Cell_head sCurrentWindow;
++        struct Cell_head sCurrentWindow
++        {
++        };
+ 
+         Rast_get_window(&sCurrentWindow);
+ 
+@@ -490,25 +516,26 @@ CPLErr GRASSRasterBand::ResetReading(str
+ /*                                                                      */
+ /************************************************************************/
+ 
+-CPLErr GRASSRasterBand::IReadBlock(int /*nBlockXOff*/, int nBlockYOff,
+-                                   void *pImage)
+-
++auto GRASSRasterBand::IReadBlock(int /*nBlockXOff*/, int nBlockYOff,
++                                 void *pImage) -> CPLErr
+ {
+     if (!this->valid)
+         return CE_Failure;
+ 
+     // Reset window because IRasterIO could be previously called.
+-    if (ResetReading(&(((GRASSDataset *)poDS)->sCellInfo)) != CE_None)
++    if (ResetReading(&((dynamic_cast<GRASSDataset *>(poDS))->sCellInfo)) !=
++        CE_None)
+     {
+         return CE_Failure;
+     }
+     // open for reading
+     if (hCell < 0)
+     {
+-        if ((hCell = Rast_open_old((char *)pszCellName, (char *)pszMapset)) < 0)
++        hCell = Rast_open_old(osCellName.c_str(), osMapset.c_str());
++        if (hCell < 0)
+         {
+             CPLError(CE_Failure, CPLE_AppDefined,
+-                     "GRASS: Cannot open raster '%s'", pszCellName);
++                     "GRASS: Cannot open raster '%s'", osCellName.c_str());
+             return CE_Failure;
+         }
+     }
+@@ -525,22 +552,23 @@ CPLErr GRASSRasterBand::IReadBlock(int /
+                 cbuf[col] = (CELL)dfNoData;
+         }
+ 
+-        GDALCopyWords((void *)cbuf, GDT_Int32, sizeof(CELL), pImage, eDataType,
+-                      GDALGetDataTypeSize(eDataType) / 8, nBlockXSize);
++        GDALCopyWords(static_cast<void *>(cbuf), GDT_Int32, sizeof(CELL),
++                      pImage, eDataType, GDALGetDataTypeSizeBytes(eDataType),
++                      nBlockXSize);
+ 
+         G_free(cbuf);
+     }
+     else if (eDataType == GDT_Int32)
+     {
+-        Rast_get_c_row(hCell, (CELL *)pImage, nBlockYOff);
++        Rast_get_c_row(hCell, static_cast<CELL *>(pImage), nBlockYOff);
+     }
+     else if (eDataType == GDT_Float32)
+     {
+-        Rast_get_f_row(hCell, (FCELL *)pImage, nBlockYOff);
++        Rast_get_f_row(hCell, static_cast<FCELL *>(pImage), nBlockYOff);
+     }
+     else if (eDataType == GDT_Float64)
+     {
+-        Rast_get_d_row(hCell, (DCELL *)pImage, nBlockYOff);
++        Rast_get_d_row(hCell, static_cast<DCELL *>(pImage), nBlockYOff);
+     }
+ 
+     // close to avoid confusion with other GRASS raster bands
+@@ -555,26 +583,28 @@ CPLErr GRASSRasterBand::IReadBlock(int /
+ /*                                                                      */
+ /************************************************************************/
+ 
+-CPLErr GRASSRasterBand::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*/)
++auto GRASSRasterBand::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*/) -> CPLErr
+ {
+     /* GRASS library does that, we have only calculate and reset the region in map units
+      * and if the region has changed, reopen the raster */
+ 
+     /* Calculate the region */
+-    struct Cell_head sWindow;
+-    struct Cell_head *psDsWindow;
++    struct Cell_head sWindow
++    {
++    };
++    struct Cell_head *psDsWindow = nullptr;
+ 
+     if (eRWFlag != GF_Read)
+         return CE_Failure;
+     if (!this->valid)
+         return CE_Failure;
+ 
+-    psDsWindow = &(((GRASSDataset *)poDS)->sCellInfo);
++    psDsWindow = &((dynamic_cast<GRASSDataset *>(poDS))->sCellInfo);
+ 
+     sWindow.north = psDsWindow->north - nYOff * psDsWindow->ns_res;
+     sWindow.south = sWindow.north - nYSize * psDsWindow->ns_res;
+@@ -596,23 +626,24 @@ CPLErr GRASSRasterBand::IRasterIO(GDALRW
+     // open for reading
+     if (hCell < 0)
+     {
+-        if ((hCell = Rast_open_old((char *)pszCellName, (char *)pszMapset)) < 0)
++        hCell = Rast_open_old(osCellName.c_str(), osMapset.c_str());
++        if (hCell < 0)
+         {
+             CPLError(CE_Failure, CPLE_AppDefined,
+-                     "GRASS: Cannot open raster '%s'", pszCellName);
++                     "GRASS: Cannot open raster '%s'", osCellName.c_str());
+             return CE_Failure;
+         }
+     }
+ 
+     /* Read Data */
+-    CELL *cbuf = NULL;
+-    FCELL *fbuf = NULL;
+-    DCELL *dbuf = NULL;
++    CELL *cbuf = nullptr;
++    FCELL *fbuf = nullptr;
++    DCELL *dbuf = nullptr;
+     bool direct = false;
+ 
+     /* Reset space if default (0) */
+     if (nPixelSpace == 0)
+-        nPixelSpace = GDALGetDataTypeSize(eBufType) / 8;
++        nPixelSpace = GDALGetDataTypeSizeBytes(eBufType);
+ 
+     if (nLineSpace == 0)
+         nLineSpace = nBufXSize * nPixelSpace;
+@@ -640,55 +671,58 @@ CPLErr GRASSRasterBand::IRasterIO(GDALRW
+ 
+     for (int row = 0; row < nBufYSize; row++)
+     {
+-        char *pnt = (char *)pData + row * nLineSpace;
++        char *pnt = static_cast<char *>(pData) + row * nLineSpace;
+ 
+         if (nGRSType == CELL_TYPE)
+         {
+             if (direct)
+             {
+-                Rast_get_c_row(hCell, (CELL *)pnt, row);
++                Rast_get_c_row(hCell, reinterpret_cast<CELL *>(pnt), row);
+             }
+             else
+             {
+                 Rast_get_c_row(hCell, cbuf, row);
+ 
+-                /* Reset NULLs */
++                /* Reset nullptrs */
+                 for (int col = 0; col < nBufXSize; col++)
+                 {
+                     if (Rast_is_c_null_value(&(cbuf[col])))
+                         cbuf[col] = (CELL)dfNoData;
+                 }
+ 
+-                GDALCopyWords((void *)cbuf, GDT_Int32, sizeof(CELL),
+-                              (void *)pnt, eBufType, nPixelSpace, nBufXSize);
++                GDALCopyWords(static_cast<void *>(cbuf), GDT_Int32,
++                              sizeof(CELL), static_cast<void *>(pnt), eBufType,
++                              (int)nPixelSpace, nBufXSize);
+             }
+         }
+         else if (nGRSType == FCELL_TYPE)
+         {
+             if (direct)
+             {
+-                Rast_get_f_row(hCell, (FCELL *)pnt, row);
++                Rast_get_f_row(hCell, reinterpret_cast<FCELL *>(pnt), row);
+             }
+             else
+             {
+                 Rast_get_f_row(hCell, fbuf, row);
+ 
+-                GDALCopyWords((void *)fbuf, GDT_Float32, sizeof(FCELL),
+-                              (void *)pnt, eBufType, nPixelSpace, nBufXSize);
++                GDALCopyWords(static_cast<void *>(fbuf), GDT_Float32,
++                              sizeof(FCELL), static_cast<void *>(pnt), eBufType,
++                              (int)nPixelSpace, nBufXSize);
+             }
+         }
+         else if (nGRSType == DCELL_TYPE)
+         {
+             if (direct)
+             {
+-                Rast_get_d_row(hCell, (DCELL *)pnt, row);
++                Rast_get_d_row(hCell, reinterpret_cast<DCELL *>(pnt), row);
+             }
+             else
+             {
+                 Rast_get_d_row(hCell, dbuf, row);
+ 
+-                GDALCopyWords((void *)dbuf, GDT_Float64, sizeof(DCELL),
+-                              (void *)pnt, eBufType, nPixelSpace, nBufXSize);
++                GDALCopyWords(static_cast<void *>(dbuf), GDT_Float64,
++                              sizeof(DCELL), static_cast<void *>(pnt), eBufType,
++                              (int)nPixelSpace, nBufXSize);
+             }
+         }
+     }
+@@ -711,10 +745,9 @@ CPLErr GRASSRasterBand::IRasterIO(GDALRW
+ /*                       GetColorInterpretation()                       */
+ /************************************************************************/
+ 
+-GDALColorInterp GRASSRasterBand::GetColorInterpretation()
+-
++auto GRASSRasterBand::GetColorInterpretation() -> GDALColorInterp
+ {
+-    if (poCT != NULL)
++    if (poCT != nullptr)
+         return GCI_PaletteIndex;
+     else
+         return GCI_GrayIndex;
+@@ -724,8 +757,7 @@ GDALColorInterp GRASSRasterBand::GetColo
+ /*                           GetColorTable()                            */
+ /************************************************************************/
+ 
+-GDALColorTable *GRASSRasterBand::GetColorTable()
+-
++auto GRASSRasterBand::GetColorTable() -> GDALColorTable *
+ {
+     return poCT;
+ }
+@@ -734,8 +766,7 @@ GDALColorTable *GRASSRasterBand::GetColo
+ /*                             GetMinimum()                             */
+ /************************************************************************/
+ 
+-double GRASSRasterBand::GetMinimum(int *pbSuccess)
+-
++auto GRASSRasterBand::GetMinimum(int *pbSuccess) -> double
+ {
+     if (pbSuccess)
+         *pbSuccess = bHaveMinMax;
+@@ -753,8 +784,7 @@ double GRASSRasterBand::GetMinimum(int *
+ /*                             GetMaximum()                             */
+ /************************************************************************/
+ 
+-double GRASSRasterBand::GetMaximum(int *pbSuccess)
+-
++auto GRASSRasterBand::GetMaximum(int *pbSuccess) -> double
+ {
+     if (pbSuccess)
+         *pbSuccess = bHaveMinMax;
+@@ -762,9 +792,8 @@ double GRASSRasterBand::GetMaximum(int *
+     if (bHaveMinMax)
+         return dfCellMax;
+ 
+-    else if (eDataType == GDT_Float32 || eDataType == GDT_Float64)
+-        return 4294967295.0;
+-    else if (eDataType == GDT_UInt32)
++    else if (eDataType == GDT_Float32 || eDataType == GDT_Float64 ||
++             eDataType == GDT_UInt32)
+         return 4294967295.0;
+     else if (eDataType == GDT_UInt16)
+         return 65535;
+@@ -776,8 +805,7 @@ double GRASSRasterBand::GetMaximum(int *
+ /*                           GetNoDataValue()                           */
+ /************************************************************************/
+ 
+-double GRASSRasterBand::GetNoDataValue(int *pbSuccess)
+-
++auto GRASSRasterBand::GetNoDataValue(int *pbSuccess) -> double
+ {
+     if (pbSuccess)
+         *pbSuccess = TRUE;
+@@ -795,43 +823,18 @@ double GRASSRasterBand::GetNoDataValue(i
+ /*                            GRASSDataset()                            */
+ /************************************************************************/
+ 
+-GRASSDataset::GRASSDataset()
++GRASSDataset::GRASSDataset(GRASSRasterPath &gpath)
++    : osGisdbase(gpath.gisdbase), osLocation(gpath.location),
++      osElement(gpath.element)
+ {
+     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
+-
+-    adfGeoTransform[0] = 0.0;
+-    adfGeoTransform[1] = 1.0;
+-    adfGeoTransform[2] = 0.0;
+-    adfGeoTransform[3] = 0.0;
+-    adfGeoTransform[4] = 0.0;
+-    adfGeoTransform[5] = 1.0;
+-    pszGisdbase = NULL;
+-    pszLocation = NULL;
+-    pszElement = NULL;
+-}
+-
+-/************************************************************************/
+-/*                           ~GRASSDataset()                            */
+-/************************************************************************/
+-
+-GRASSDataset::~GRASSDataset()
+-{
+-
+-    if (pszGisdbase)
+-        G_free(pszGisdbase);
+-
+-    if (pszLocation)
+-        G_free(pszLocation);
+-
+-    if (pszElement)
+-        G_free(pszElement);
+ }
+ 
+ /************************************************************************/
+ /*                          GetSpatialRef()                             */
+ /************************************************************************/
+ 
+-const OGRSpatialReference *GRASSDataset::GetSpatialRef() const
++auto GRASSDataset::GetSpatialRef() const -> const OGRSpatialReference *
+ {
+     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
+ }
+@@ -840,88 +843,40 @@ const OGRSpatialReference *GRASSDataset:
+ /*                          GetGeoTransform()                           */
+ /************************************************************************/
+ 
+-CPLErr GRASSDataset::GetGeoTransform(double *padfGeoTransform)
++
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 12, 0)
++auto GRASSDataset::GetGeoTransform(GDALGeoTransform &gt) const -> CPLErr
+ {
+-    memcpy(padfGeoTransform, adfGeoTransform, sizeof(double) * 6);
++    gt = m_gt;
+ 
+     return CE_None;
+ }
+-
+-/************************************************************************/
+-/*                            SplitPath()                               */
+-/* Split full path to cell or group to:                                 */
+-/*     gisdbase, location, mapset, element, name                        */
+-/* New string are allocated and should be freed when no longer needed.  */
+-/*                                                                      */
+-/* Returns: true - OK                                                   */
+-/*          false - failed                                              */
+-/************************************************************************/
+-bool GRASSDataset::SplitPath(char *path, char **gisdbase, char **location,
+-                             char **mapset, char **element, char **name)
++#else
++auto GRASSDataset::GetGeoTransform(double *padfGeoTransform) -> CPLErr
+ {
+-    char *p;
+-    char *ptr[5];
+-    char *tmp;
+-    int i = 0;
+-
+-    *gisdbase = NULL;
+-    *location = NULL;
+-    *mapset = NULL;
+-    *element = NULL;
+-    *name = NULL;
+-
+-    if (!path || strlen(path) == 0)
+-        return false;
+-
+-    tmp = G_store(path);
+-
+-    while ((p = strrchr(tmp, '/')) != NULL && i < 4)
+-    {
+-        *p = '\0';
+-
+-        if (strlen(p + 1) == 0) /* repeated '/' */
+-            continue;
+-
+-        ptr[i++] = p + 1;
+-    }
++    memcpy(padfGeoTransform, m_gt.data(), sizeof(double) * 6);
+ 
+-    /* Note: empty GISDBASE == 0 is not accepted (relative path) */
+-    if (i != 4)
+-    {
+-        G_free(tmp);
+-        return false;
+-    }
+-
+-    *gisdbase = G_store(tmp);
+-    *location = G_store(ptr[3]);
+-    *mapset = G_store(ptr[2]);
+-    *element = G_store(ptr[1]);
+-    *name = G_store(ptr[0]);
+-
+-    G_free(tmp);
+-    return true;
++    return CE_None;
+ }
++#endif
+ 
+ /************************************************************************/
+ /*                                Open()                                */
+ /************************************************************************/
+ 
+-typedef int (*GrassErrorHandler)(const char *, int);
+-
+-GDALDataset *GRASSDataset::Open(GDALOpenInfo *poOpenInfo)
++using GrassErrorHandler = auto(*)(const char *, int) -> int;
+ 
++auto GRASSDataset::Open(GDALOpenInfo *poOpenInfo) -> GDALDataset *
+ {
+-    char *pszGisdb = NULL, *pszLoc = NULL;
+-    char *pszMapset = NULL, *pszElem = NULL, *pszName = NULL;
+-    char **papszCells = NULL;
+-    char **papszMapsets = NULL;
++    char **papszCells = nullptr;
++    char **papszMapsets = nullptr;
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Does this even look like a grass file path?                     */
+     /* -------------------------------------------------------------------- */
+-    if (strstr(poOpenInfo->pszFilename, "/cellhd/") == NULL &&
+-        strstr(poOpenInfo->pszFilename, "/group/") == NULL)
+-        return NULL;
++    if (strstr(poOpenInfo->pszFilename, "/cellhd/") == nullptr &&
++        strstr(poOpenInfo->pszFilename, "/group/") == nullptr)
++        return nullptr;
+ 
+     /* Always init, if no rasters are opened G_no_gisinit resets the projection and
+      * rasters in different projection may be then opened */
+@@ -933,91 +888,83 @@ GDALDataset *GRASSDataset::Open(GDALOpen
+     G_no_gisinit();  // Doesn't check write permissions for mapset compare to G_gisinit
+ 
+     // Set error function
+-    G_set_error_routine((GrassErrorHandler)Grass2CPLErrorHook);
++    G_set_error_routine(GrassErrorHandler(Grass2CPLErrorHook));
+ 
+     // GISBASE is path to the directory where GRASS is installed,
+     if (!getenv("GISBASE"))
+     {
+-        static char *gisbaseEnv = NULL;
++        static char *gisbaseEnv = nullptr;
+         const char *gisbase = GRASS_GISBASE;
+         CPLError(CE_Warning, CPLE_AppDefined,
+                  "GRASS warning: GISBASE "
+                  "environment variable was not set, using:\n%s",
+                  gisbase);
+-        char buf[2000];
+-        snprintf(buf, sizeof(buf), "GISBASE=%s", gisbase);
+-        buf[sizeof(buf) - 1] = '\0';
++        std::array<char, BUFF_SIZE> buf{};
++        int res = std::snprintf(buf.data(), BUFF_SIZE, "GISBASE=%s", gisbase);
++        if (res >= BUFF_SIZE)
++        {
++            CPLError(
++                CE_Warning, CPLE_AppDefined,
++                "GRASS warning: GISBASE environment variable was too long.\n");
++            return nullptr;
++        }
+ 
+         CPLFree(gisbaseEnv);
+-        gisbaseEnv = CPLStrdup(buf);
++        gisbaseEnv = CPLStrdup(buf.data());
+         putenv(gisbaseEnv);
+     }
+ 
+-    if (!SplitPath(poOpenInfo->pszFilename, &pszGisdb, &pszLoc, &pszMapset,
+-                   &pszElem, &pszName))
+-    {
+-        return NULL;
+-    }
++    GRASSRasterPath gp = GRASSRasterPath(poOpenInfo->pszFilename);
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Check element name                                              */
+     /* -------------------------------------------------------------------- */
+-    if (strcmp(pszElem, "cellhd") != 0 && strcmp(pszElem, "group") != 0)
++    if (!gp.isValid())
+     {
+-        G_free(pszGisdb);
+-        G_free(pszLoc);
+-        G_free(pszMapset);
+-        G_free(pszElem);
+-        G_free(pszName);
+-        return NULL;
++        return nullptr;
+     }
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Set GRASS variables                                             */
+     /* -------------------------------------------------------------------- */
+ 
+-    G_setenv_nogisrc("GISDBASE", pszGisdb);
+-    G_setenv_nogisrc("LOCATION_NAME", pszLoc);
+-    G_setenv_nogisrc("MAPSET",
+-                     pszMapset);  // group is searched only in current mapset
++    G_setenv_nogisrc("GISDBASE", gp.gisdbase.c_str());
++    G_setenv_nogisrc("LOCATION_NAME", gp.location.c_str());
++    G_setenv_nogisrc(
++        "MAPSET",
++        gp.mapset.c_str());  // group is searched only in current mapset
+     G_reset_mapsets();
+-    G_add_mapset_to_search_path(pszMapset);
++    G_add_mapset_to_search_path(gp.mapset.c_str());
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Check if this is a valid grass cell.                            */
+     /* -------------------------------------------------------------------- */
+-    if (strcmp(pszElem, "cellhd") == 0)
++    if (gp.isCellHD())
+     {
+ 
+-        if (G_find_file2("cell", pszName, pszMapset) == NULL)
++        if (G_find_file2("cell", gp.name.c_str(), gp.mapset.c_str()) == nullptr)
+         {
+-            G_free(pszGisdb);
+-            G_free(pszLoc);
+-            G_free(pszMapset);
+-            G_free(pszElem);
+-            G_free(pszName);
+-            return NULL;
++            return nullptr;
+         }
+ 
+-        papszMapsets = CSLAddString(papszMapsets, pszMapset);
+-        papszCells = CSLAddString(papszCells, pszName);
++        papszMapsets = CSLAddString(papszMapsets, gp.mapset.c_str());
++        papszCells = CSLAddString(papszCells, gp.name.c_str());
+     }
++
+     /* -------------------------------------------------------------------- */
+     /*      Check if this is a valid GRASS imagery group.                   */
+     /* -------------------------------------------------------------------- */
+     else
+     {
+-        struct Ref ref;
++        struct Ref ref
++        {
++        };
+ 
+         I_init_group_ref(&ref);
+-        if (I_get_group_ref(pszName, &ref) == 0)
++        bool has_group_ref = I_get_group_ref(gp.name.c_str(), &ref);
++        if (!has_group_ref || ref.nfiles <= 0)
+         {
+-            G_free(pszGisdb);
+-            G_free(pszLoc);
+-            G_free(pszMapset);
+-            G_free(pszElem);
+-            G_free(pszName);
+-            return NULL;
++            return nullptr;
+         }
+ 
+         for (int iRef = 0; iRef < ref.nfiles; iRef++)
+@@ -1030,20 +977,18 @@ GDALDataset *GRASSDataset::Open(GDALOpen
+         I_free_group_ref(&ref);
+     }
+ 
+-    G_free(pszMapset);
+-    G_free(pszName);
+-
+     /* -------------------------------------------------------------------- */
+     /*      Create a corresponding GDALDataset.                             */
+     /* -------------------------------------------------------------------- */
+-    GRASSDataset *poDS = new GRASSDataset();
++    auto poDS = new GRASSDataset(gp);
+ 
+     /* notdef: should only allow read access to an existing cell, right? */
+     poDS->eAccess = poOpenInfo->eAccess;
+ 
+-    poDS->pszGisdbase = pszGisdb;
+-    poDS->pszLocation = pszLoc;
+-    poDS->pszElement = pszElem;
++    if (!papszCells)
++    {
++        return nullptr;
++    }
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Capture some information from the file that is of interest.     */
+@@ -1054,20 +999,19 @@ GDALDataset *GRASSDataset::Open(GDALOpen
+     poDS->nRasterXSize = poDS->sCellInfo.cols;
+     poDS->nRasterYSize = poDS->sCellInfo.rows;
+ 
+-    poDS->adfGeoTransform[0] = poDS->sCellInfo.west;
+-    poDS->adfGeoTransform[1] = poDS->sCellInfo.ew_res;
+-    poDS->adfGeoTransform[2] = 0.0;
+-    poDS->adfGeoTransform[3] = poDS->sCellInfo.north;
+-    poDS->adfGeoTransform[4] = 0.0;
+-    poDS->adfGeoTransform[5] = -1 * poDS->sCellInfo.ns_res;
++    poDS->m_gt[0] = poDS->sCellInfo.west;
++    poDS->m_gt[1] = poDS->sCellInfo.ew_res;
++    poDS->m_gt[2] = 0.0;
++    poDS->m_gt[3] = poDS->sCellInfo.north;
++    poDS->m_gt[4] = 0.0;
++    poDS->m_gt[5] = -1 * poDS->sCellInfo.ns_res;
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Try to get a projection definition.                             */
+     /* -------------------------------------------------------------------- */
+-    struct Key_Value *projinfo, *projunits;
++    struct Key_Value *projinfo = G_get_projinfo();
++    struct Key_Value *projunits = G_get_projunits();
+ 
+-    projinfo = G_get_projinfo();
+-    projunits = G_get_projunits();
+     char *pszWKT = GPJ_grass_to_wkt(projinfo, projunits, 0, 0);
+     if (projinfo)
+         G_free_key_value(projinfo);
+@@ -1080,10 +1024,11 @@ GDALDataset *GRASSDataset::Open(GDALOpen
+     /* -------------------------------------------------------------------- */
+     /*      Create band information objects.                                */
+     /* -------------------------------------------------------------------- */
+-    for (int iBand = 0; papszCells[iBand] != NULL; iBand++)
++    for (int iBand = 0; papszCells[iBand] != nullptr; iBand++)
+     {
+-        GRASSRasterBand *rb = new GRASSRasterBand(
+-            poDS, iBand + 1, papszMapsets[iBand], papszCells[iBand]);
++        std::string msets = std::string(papszMapsets[iBand]);
++        std::string cells = std::string(papszCells[iBand]);
++        auto rb = new GRASSRasterBand(poDS, iBand + 1, msets, cells);
+ 
+         if (!rb->valid)
+         {
+@@ -1091,7 +1036,7 @@ GDALDataset *GRASSDataset::Open(GDALOpen
+                      "GRASS: Cannot open raster band %d", iBand);
+             delete rb;
+             delete poDS;
+-            return NULL;
++            return nullptr;
+         }
+ 
+         poDS->SetBand(iBand + 1, rb);
+@@ -1109,13 +1054,68 @@ GDALDataset *GRASSDataset::Open(GDALOpen
+         CPLError(CE_Failure, CPLE_NotSupported,
+                  "The GRASS driver does not support update access to existing"
+                  " datasets.\n");
+-        return NULL;
++        return nullptr;
+     }
+ 
+     return poDS;
+ }
+ 
+ /************************************************************************/
++/*                          GRASSRasterPath                             */
++/************************************************************************/
++
++GRASSRasterPath::GRASSRasterPath(const char *path)
++{
++    if (!path || std::strlen(path) == 0)
++        return;
++
++    char *p = nullptr;
++    std::array<char *, 5> ptr{};
++    int i = 0;
++    auto tmp = std::unique_ptr<char[]>(new char[std::strlen(path) + 1]);
++
++    std::strcpy(tmp.get(), path);
++
++    while ((p = std::strrchr(tmp.get(), '/')) != nullptr && i < 4)
++    {
++        *p = '\0';
++
++        if (std::strlen(p + 1) == 0) /* repeated '/' */
++            continue;
++
++        ptr[i++] = p + 1;
++    }
++
++    /* Note: empty GISDBASE == 0 is not accepted (relative path) */
++    if (i != 4)
++    {
++        return;
++    }
++
++    gisdbase = std::string(tmp.get());
++    location = std::string(ptr[3]);
++    mapset = std::string(ptr[2]);
++    element = std::string(ptr[1]);
++    name = std::string(ptr[0]);
++
++    return;
++}
++
++auto GRASSRasterPath::isValid() -> bool
++{
++    if (name.empty() || (element != "cellhd" && element != "group"))
++    {
++        return false;
++    }
++    return true;
++}
++
++auto GRASSRasterPath::isCellHD() -> bool
++{
++    return element == "cellhd";
++}
++
++/************************************************************************/
+ /*                          GDALRegister_GRASS()                        */
+ /************************************************************************/
+ 
+@@ -1124,10 +1124,10 @@ void GDALRegister_GRASS()
+     if (!GDAL_CHECK_VERSION("GDAL/GRASS driver"))
+         return;
+ 
+-    if (GDALGetDriverByName("GRASS") != NULL)
++    if (GDALGetDriverByName("GRASS") != nullptr)
+         return;
+ 
+-    GDALDriver *poDriver = new GDALDriver();
++    auto poDriver = new GDALDriver();
+ 
+     poDriver->SetDescription("GRASS");
+     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
+--- a/ogrgrass.h
++++ b/ogrgrass.h
+@@ -1,5 +1,4 @@
+ /******************************************************************************
+- * $Id$
+  *
+  * Project:  OpenGIS Simple Features Reference Implementation
+  * Purpose:  Private definitions for OGR/GRASS driver.
+@@ -8,23 +7,8 @@
+  ******************************************************************************
+  * Copyright (c) 2005, Radim Blazek <radim.blazek at gmail.com>
+  *
+- * 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:
++ * SPDX-License-Identifier: MIT
+  *
+- * 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.
+  ****************************************************************************/
+ 
+ #ifndef OGRGRASS_H_INCLUDED
+@@ -52,35 +36,54 @@ class OGRGRASSLayer final : public OGRLa
+     virtual ~OGRGRASSLayer();
+ 
+     // Layer info
+-    OGRFeatureDefn *GetLayerDefn() override
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++    auto GetName() const -> const char * override
++#else
++    auto GetName() -> const char * override
++#endif
++    {
++        return osName.c_str();
++    }
++
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++    auto GetLayerDefn() const -> const OGRFeatureDefn * override
++#else
++    auto GetLayerDefn() -> OGRFeatureDefn * override
++#endif
+     {
+         return poFeatureDefn;
+     }
+-    GIntBig GetFeatureCount(int) override;
++    auto GetFeatureCount(int) -> GIntBig override;
+ 
+ #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
+-    OGRErr IGetExtent(int iGeomField, OGREnvelope *psExtent, bool bForce)
+-        override;
++    auto IGetExtent(int iGeomField, OGREnvelope *psExtent, bool bForce)
++        -> OGRErr override;
+ #else
+-    OGRErr GetExtent(OGREnvelope *psExtent, int bForce) override;
+-    virtual OGRErr GetExtent(int iGeomField, OGREnvelope *psExtent,
+-                             int bForce) override
++    auto GetExtent(OGREnvelope *psExtent, int bForce) -> OGRErr override;
++    virtual auto GetExtent(int iGeomField, OGREnvelope *psExtent, int bForce)
++        -> OGRErr override
+     {
+         return OGRLayer::GetExtent(iGeomField, psExtent, bForce);
+     }
+ #endif
+ 
+-    virtual OGRSpatialReference *GetSpatialRef() override;
+-    int TestCapability(const char *) override;
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++    auto GetSpatialRef() const -> const OGRSpatialReference * override;
++    auto TestCapability(const char *) const -> int override;
++#else
++    auto GetSpatialRef() -> OGRSpatialReference * override;
++    auto TestCapability(const char *) -> int override;
++#endif
+ 
+     // Reading
+     void ResetReading() override;
+-    virtual OGRErr SetNextByIndex(GIntBig nIndex) override;
+-    OGRFeature *GetNextFeature() override;
+-    OGRFeature *GetFeature(GIntBig nFeatureId) override;
++    virtual auto SetNextByIndex(GIntBig nIndex) -> OGRErr override;
++    auto GetNextFeature() -> OGRFeature * override;
++    auto GetFeature(GIntBig nFeatureId) -> OGRFeature * override;
+ 
+     // Filters
+-    virtual OGRErr SetAttributeFilter(const char *query) override;
++    virtual auto SetAttributeFilter(const char *query) -> OGRErr override;
++
+ #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
+     virtual OGRErr ISetSpatialFilter(int iGeomField,
+                                      const OGRGeometry *poGeom) override;
+@@ -93,12 +96,12 @@ class OGRGRASSLayer final : public OGRLa
+ #endif
+ 
+   private:
+-    char *pszName;
++    std::string osName;
+     OGRSpatialReference *poSRS;
+     OGRFeatureDefn *poFeatureDefn;
+     char *pszQuery;  // Attribute filter string
+ 
+-    int iNextId;
++    GIntBig iNextId;
+     int nTotalCount;
+     int iLayer;       // Layer number
+     int iLayerIndex;  // Layer index (in GRASS category index)
+@@ -123,21 +126,21 @@ class OGRGRASSLayer final : public OGRLa
+     struct line_pnts *poPoints;
+     struct line_cats *poCats;
+ 
+-    bool StartDbDriver();
+-    bool StopDbDriver();
++    auto StartDbDriver() -> bool;
++    auto StopDbDriver() -> bool;
+ 
+-    OGRGeometry *GetFeatureGeometry(long nFeatureId, int *cat);
+-    bool SetAttributes(OGRFeature *feature, dbTable *table);
++    auto GetFeatureGeometry(long nFeatureId, int *cat) -> OGRGeometry *;
++    auto SetAttributes(OGRFeature *feature, dbTable *table) -> bool;
+ 
+     // Features matching spatial filter for ALL features/elements in GRASS
+     char *paSpatialMatch;
+-    bool SetSpatialMatch();
++    auto SetSpatialMatch() -> bool;
+ 
+     // Features matching attribute filter for ALL features/elements in GRASS
+     char *paQueryMatch;
+-    bool OpenSequentialCursor();
+-    bool ResetSequentialCursor();
+-    bool SetQueryMatch();
++    auto OpenSequentialCursor() -> bool;
++    auto ResetSequentialCursor() -> bool;
++    auto SetQueryMatch() -> bool;
+ };
+ 
+ /************************************************************************/
+@@ -146,38 +149,50 @@ class OGRGRASSLayer final : public OGRLa
+ class OGRGRASSDataSource final : public OGRDataSource
+ {
+   public:
+-    OGRGRASSDataSource();
++    OGRGRASSDataSource() = default;
+     virtual ~OGRGRASSDataSource();
+ 
+-    bool Open(const char *, bool bUpdate, bool bTestOpen,
+-              bool bSingleNewFile = false);
++    auto Open(const char *, bool bUpdate, bool bTestOpen,
++              bool bSingleNewFile = false) -> bool;
+ 
+-    const char *GetName() override
++    auto GetName() -> const char * override
+     {
+-        return pszName;
++        return osName.c_str();
+     }
+-    int GetLayerCount() override
++
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++    auto GetLayerCount() const -> int override
++#else
++    auto GetLayerCount() -> int override
++#endif
+     {
+         return nLayers;
+     }
+-    OGRLayer *GetLayer(int) override;
+ 
+-    int TestCapability(const char *) override;
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++    auto GetLayer(int) const -> const OGRLayer * override;
++    auto TestCapability(const char *) const -> int override;
++#else
++    auto GetLayer(int) -> OGRLayer * override;
++    auto TestCapability(const char *) -> int override;
++#endif
+ 
+   private:
+-    OGRGRASSLayer **papoLayers;
+-    char *pszName;      // Date source name
+-    char *pszGisdbase;  // GISBASE
+-    char *pszLocation;  // location name
+-    char *pszMapset;    // mapset name
+-    char *pszMap;       // name of vector map
++    OGRGRASSLayer **papoLayers{nullptr};
++    std::string osName;      // Date source name
++    std::string osGisdbase;  // GISBASE
++    std::string osLocation;  // location name
++    std::string osMapset;    // mapset name
++    std::string osMap;       // name of vector map
+ 
+-    struct Map_info map;
+-    int nLayers;
++    struct Map_info map
++    {
++    };
++    int nLayers{0};
+ 
+-    bool bOpened;
++    bool bOpened{false};
+ 
+-    static bool SplitPath(char *, char **, char **, char **, char **);
++    auto SetPath(const char *) -> bool;
+ };
+ 
+ #endif /* ndef OGRGRASS_H_INCLUDED */
+--- a/ogrgrassdatasource.cpp
++++ b/ogrgrassdatasource.cpp
+@@ -8,25 +8,13 @@
+  * Copyright (c) 2005, Radim Blazek <radim.blazek at gmail.com>
+  * Copyright (c) 2008-2020, Even Rouault <even dot rouault at spatialys.com>
+  *
+- * 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:
++ * SPDX-License-Identifier: MIT
+  *
+- * 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 <array>
++#include <cstring>
++
+ #include "ogrgrass.h"
+ #include "cpl_conv.h"
+ #include "cpl_string.h"
+@@ -34,7 +22,7 @@
+ /************************************************************************/
+ /*                         Grass2CPLErrorHook()                         */
+ /************************************************************************/
+-static int Grass2OGRErrorHook(char *pszMessage, int bFatal)
++static auto Grass2CPLErrorHook(char *pszMessage, int bFatal) -> int
+ {
+     if (!bFatal)
+         CPLError(CE_Warning, CPLE_AppDefined, "GRASS warning: %s", pszMessage);
+@@ -46,21 +34,6 @@ static int Grass2OGRErrorHook(char *pszM
+ }
+ 
+ /************************************************************************/
+-/*                         OGRGRASSDataSource()                         */
+-/************************************************************************/
+-OGRGRASSDataSource::OGRGRASSDataSource()
+-{
+-    pszName = NULL;
+-    pszGisdbase = NULL;
+-    pszLocation = NULL;
+-    pszMapset = NULL;
+-    pszMap = NULL;
+-    papoLayers = NULL;
+-    nLayers = 0;
+-    bOpened = FALSE;
+-}
+-
+-/************************************************************************/
+ /*                        ~OGRGRASSDataSource()                         */
+ /************************************************************************/
+ OGRGRASSDataSource::~OGRGRASSDataSource()
+@@ -68,19 +41,6 @@ OGRGRASSDataSource::~OGRGRASSDataSource(
+     for (int i = 0; i < nLayers; i++)
+         delete papoLayers[i];
+ 
+-    if (pszName)
+-        CPLFree(pszName);
+-    if (papoLayers)
+-        CPLFree(papoLayers);
+-    if (pszGisdbase)
+-        G_free(pszGisdbase);
+-    if (pszLocation)
+-        G_free(pszLocation);
+-    if (pszMapset)
+-        G_free(pszMapset);
+-    if (pszMap)
+-        G_free(pszMap);
+-
+     if (bOpened)
+         Vect_close(&map);
+ }
+@@ -89,27 +49,25 @@ OGRGRASSDataSource::~OGRGRASSDataSource(
+ /*                                Open()                                */
+ /************************************************************************/
+ 
+-typedef int (*GrassErrorHandler)(const char *, int);
++using GrassErrorHandler = auto(*)(const char *, int) -> int;
+ 
+-bool OGRGRASSDataSource::Open(const char *pszNewName, bool /*bUpdate*/,
+-                              bool bTestOpen, bool /*bSingleNewFileIn*/)
++auto OGRGRASSDataSource::Open(const char *pszNewName, bool /*bUpdate*/,
++                              bool bTestOpen, bool /*bSingleNewFileIn*/) -> bool
+ {
+     VSIStatBuf stat;
+ 
+     CPLAssert(nLayers == 0);
+ 
+-    pszName = CPLStrdup(pszNewName);  // Released by destructor
+-
+     /* -------------------------------------------------------------------- */
+     /*      Do the given path contains 'vector' and 'head'?                 */
+     /* -------------------------------------------------------------------- */
+-    if (strstr(pszName, "vector") == nullptr ||
+-        strstr(pszName, "head") == nullptr)
++    if (std::strstr(pszNewName, "vector") == nullptr ||
++        std::strstr(pszNewName, "head") == nullptr)
+     {
+         if (!bTestOpen)
+         {
+             CPLError(CE_Failure, CPLE_AppDefined,
+-                     "%s is not GRASS vector, access failed.\n", pszName);
++                     "%s is not GRASS vector, access failed.\n", pszNewName);
+         }
+         return false;
+     }
+@@ -117,12 +75,12 @@ bool OGRGRASSDataSource::Open(const char
+     /* -------------------------------------------------------------------- */
+     /*      Is the given a regular file?                                    */
+     /* -------------------------------------------------------------------- */
+-    if (CPLStat(pszName, &stat) != 0 || !VSI_ISREG(stat.st_mode))
++    if (CPLStat(pszNewName, &stat) != 0 || !VSI_ISREG(stat.st_mode))
+     {
+         if (!bTestOpen)
+         {
+             CPLError(CE_Failure, CPLE_AppDefined,
+-                     "%s is not GRASS vector, access failed.\n", pszName);
++                     "%s is not GRASS vector, access failed.\n", pszNewName);
+         }
+ 
+         return false;
+@@ -131,21 +89,21 @@ bool OGRGRASSDataSource::Open(const char
+     /* -------------------------------------------------------------------- */
+     /*      Parse datasource name                                           */
+     /* -------------------------------------------------------------------- */
+-    if (!SplitPath(pszName, &pszGisdbase, &pszLocation, &pszMapset, &pszMap))
++    if (!SetPath(pszNewName))
+     {
+         if (!bTestOpen)
+         {
+             CPLError(CE_Failure, CPLE_AppDefined,
+                      "%s is not GRASS datasource name, access failed.\n",
+-                     pszName);
++                     pszNewName);
+         }
+         return false;
+     }
+ 
+-    CPLDebug("GRASS", "Gisdbase: %s", pszGisdbase);
+-    CPLDebug("GRASS", "Location: %s", pszLocation);
+-    CPLDebug("GRASS", "Mapset: %s", pszMapset);
+-    CPLDebug("GRASS", "Map: %s", pszMap);
++    CPLDebug("GRASS", "Gisdbase: %s", osGisdbase.c_str());
++    CPLDebug("GRASS", "Location: %s", osLocation.c_str());
++    CPLDebug("GRASS", "Mapset: %s", osMapset.c_str());
++    CPLDebug("GRASS", "Map: %s", osMap.c_str());
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Init GRASS library                                              */
+@@ -160,12 +118,11 @@ bool OGRGRASSDataSource::Open(const char
+                  "GRASS warning: GISBASE "
+                  "environment variable was not set, using:\n%s",
+                  gisbase);
+-        char buf[2000];
+-        snprintf(buf, sizeof(buf), "GISBASE=%s", gisbase);
+-        buf[sizeof(buf) - 1] = '\0';
++        std::array<char, 2000> buf{};
++        (void)snprintf(buf.data(), buf.size(), "GISBASE=%s", gisbase);
+ 
+         CPLFree(gisbaseEnv);
+-        gisbaseEnv = CPLStrdup(buf);
++        gisbaseEnv = CPLStrdup(buf.data());
+         putenv(gisbaseEnv);
+     }
+ 
+@@ -178,27 +135,27 @@ bool OGRGRASSDataSource::Open(const char
+     G_no_gisinit();
+ 
+     // Set error function
+-    G_set_error_routine((GrassErrorHandler)Grass2OGRErrorHook);
++    G_set_error_routine(GrassErrorHandler(Grass2CPLErrorHook));
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Set GRASS variables                                             */
+     /* -------------------------------------------------------------------- */
+-    G_setenv_nogisrc("GISDBASE", pszGisdbase);
+-    G_setenv_nogisrc("LOCATION_NAME", pszLocation);
+-    G_setenv_nogisrc("MAPSET", pszMapset);
++    G_setenv_nogisrc("GISDBASE", osGisdbase.c_str());
++    G_setenv_nogisrc("LOCATION_NAME", osLocation.c_str());
++    G_setenv_nogisrc("MAPSET", osMapset.c_str());
+     G_reset_mapsets();
+-    G_add_mapset_to_search_path(pszMapset);
++    G_add_mapset_to_search_path(osMapset.c_str());
+ 
+     /* -------------------------------------------------------------------- */
+     /*      Open GRASS vector map                                           */
+     /* -------------------------------------------------------------------- */
+     Vect_set_open_level(2);
+-    int level = Vect_open_old(&map, pszMap, pszMapset);
++    int level = Vect_open_old(&map, osMap.c_str(), osMapset.c_str());
+ 
+     if (level < 2)
+     {
+         CPLError(CE_Failure, CPLE_AppDefined,
+-                 "Cannot open GRASS vector %s on level 2.\n", pszName);
++                 "Cannot open GRASS vector %s on level 2.\n", osName.c_str());
+         return false;
+     }
+ 
+@@ -213,11 +170,11 @@ bool OGRGRASSDataSource::Open(const char
+     for (int i = 0; i < ncidx; i++)
+     {
+         // Create the layer object
+-        OGRGRASSLayer *poLayer = new OGRGRASSLayer(i, &map);
++        auto poLayer = new OGRGRASSLayer(i, &map);
+ 
+         // Add layer to data source layer list
+-        papoLayers = (OGRGRASSLayer **)CPLRealloc(
+-            papoLayers, sizeof(OGRGRASSLayer *) * (nLayers + 1));
++        papoLayers = reinterpret_cast<OGRGRASSLayer **>(
++            CPLRealloc(papoLayers, sizeof(OGRGRASSLayer *) * (nLayers + 1)));
+         papoLayers[nLayers++] = poLayer;
+     }
+ 
+@@ -229,7 +186,11 @@ bool OGRGRASSDataSource::Open(const char
+ /************************************************************************/
+ /*                           TestCapability()                           */
+ /************************************************************************/
+-int OGRGRASSDataSource::TestCapability(const char * /* pszCap*/)
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++auto OGRGRASSDataSource::TestCapability(const char * /* pszCap*/) const -> int
++#else
++auto OGRGRASSDataSource::TestCapability(const char * /* pszCap*/) -> int
++#endif
+ {
+     return FALSE;
+ }
+@@ -237,10 +198,14 @@ int OGRGRASSDataSource::TestCapability(c
+ /************************************************************************/
+ /*                              GetLayer()                              */
+ /************************************************************************/
+-OGRLayer *OGRGRASSDataSource::GetLayer(int iLayer)
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++auto OGRGRASSDataSource::GetLayer(int iLayer) const -> const OGRLayer *
++#else
++auto OGRGRASSDataSource::GetLayer(int iLayer) -> OGRLayer *
++#endif
+ {
+     if (iLayer < 0 || iLayer >= nLayers)
+-        return NULL;
++        return nullptr;
+     else
+         return papoLayers[iLayer];
+ }
+@@ -254,26 +219,25 @@ OGRLayer *OGRGRASSDataSource::GetLayer(i
+ /* Returns: true - OK                                                   */
+ /*          false - failed                                              */
+ /************************************************************************/
+-bool OGRGRASSDataSource::SplitPath(char *path, char **gisdbase, char **location,
+-                                   char **mapset, char **map)
++auto OGRGRASSDataSource::SetPath(const char *path) -> bool
+ {
+-    char *p, *ptr[5], *tmp;
+-    int i = 0;
+-
+-    CPLDebug("GRASS", "OGRGRASSDataSource::SplitPath");
++    CPLDebug("GRASS", "OGRGRASSDataSource::SetPath");
+ 
+-    *gisdbase = *location = *mapset = *map = NULL;
+-
+-    if (!path || strlen(path) == 0)
++    if (!path || std::strlen(path) == 0)
+         return false;
+ 
+-    tmp = G_store(path);
++    char *p = nullptr;
++    std::array<char *, 6> ptr{};
++    int i = 0;
++    auto tmp = std::unique_ptr<char[]>(new char[std::strlen(path) + 1]);
++
++    std::strcpy(tmp.get(), path);
+ 
+-    while ((p = strrchr(tmp, '/')) != NULL && i < 5)
++    while ((p = std::strrchr(tmp.get(), '/')) != nullptr && i < 5)
+     {
+         *p = '\0';
+ 
+-        if (strlen(p + 1) == 0) /* repeated '/' */
++        if (std::strlen(p + 1) == 0) /* repeated '/' */
+             continue;
+ 
+         ptr[i++] = p + 1;
+@@ -282,20 +246,19 @@ bool OGRGRASSDataSource::SplitPath(char
+     /* Note: empty GISDBASE == 0 is not accepted (relative path) */
+     if (i != 5)
+     {
+-        free(tmp);
+         return false;
+     }
+ 
+-    if (strcmp(ptr[0], "head") != 0 || strcmp(ptr[2], "vector") != 0)
++    if (std::strcmp(ptr[0], "head") != 0 || std::strcmp(ptr[2], "vector") != 0)
+     {
+         return false;
+     }
+ 
+-    *gisdbase = G_store(tmp);
+-    *location = G_store(ptr[4]);
+-    *mapset = G_store(ptr[3]);
+-    *map = G_store(ptr[1]);
++    osName = std::string(path);
++    osGisdbase = std::string(tmp.get());
++    osLocation = std::string(ptr[4]);
++    osMapset = std::string(ptr[3]);
++    osMap = std::string(ptr[1]);
+ 
+-    free(tmp);
+     return true;
+ }
+--- a/ogrgrasslayer.cpp
++++ b/ogrgrasslayer.cpp
+@@ -8,26 +8,13 @@
+  * Copyright (c) 2005, Radim Blazek <radim.blazek at gmail.com>
+  * Copyright (c) 2008-2020, Even Rouault <even dot rouault at spatialys.com>
+  *
+- * 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:
++ * SPDX-License-Identifier: MIT
+  *
+- * 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 <signal.h>
++#include <array>
++#include <csignal>
++
+ #include "ogrgrass.h"
+ #include "cpl_conv.h"
+ 
+@@ -35,36 +22,28 @@
+ /*                           OGRGRASSLayer()                            */
+ /************************************************************************/
+ OGRGRASSLayer::OGRGRASSLayer(int layerIndex, struct Map_info *map)
++    : poSRS(nullptr), pszQuery(nullptr), iNextId(0),
++      iLayer(Vect_cidx_get_field_number(map, layerIndex)),
++      iLayerIndex(layerIndex), poMap(map),
++      poLink(Vect_get_field(poMap, iLayer)), iCurrentCat(0),
++      poPoints(Vect_new_line_struct()), poCats(Vect_new_cats_struct()),
++      paSpatialMatch(nullptr), paQueryMatch(nullptr)
+ {
+     CPLDebug("GRASS", "OGRGRASSLayer::OGRGRASSLayer layerIndex = %d",
+              layerIndex);
+ 
+-    iLayerIndex = layerIndex;
+-    poMap = map;
+-    poSRS = NULL;
+-    iNextId = 0;
+-    poPoints = Vect_new_line_struct();
+-    poCats = Vect_new_cats_struct();
+-    pszQuery = NULL;
+-    paQueryMatch = NULL;
+-    paSpatialMatch = NULL;
+-    iCurrentCat = 0;
+-
+-    iLayer = Vect_cidx_get_field_number(poMap, iLayerIndex);
+     CPLDebug("GRASS", "iLayer = %d", iLayer);
+ 
+-    poLink = Vect_get_field(poMap, iLayer);  // May be NULL if not defined
++    // poLink may be NULL if not defined
+ 
+     // Layer name
+     if (poLink && poLink->name)
+     {
+-        pszName = CPLStrdup(poLink->name);
++        osName = std::string(poLink->name);
+     }
+     else
+     {
+-        char buf[20];
+-        snprintf(buf, sizeof(buf), "%d", iLayer);
+-        pszName = CPLStrdup(buf);
++        osName = std::to_string(iLayer);
+     }
+ 
+     // Because we don't represent centroids as any simple feature, we have to scan
+@@ -72,14 +51,14 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+     nTotalCount =
+         Vect_cidx_get_type_count(poMap, iLayer, GV_POINT | GV_LINES | GV_AREA);
+     CPLDebug("GRASS", "nTotalCount = %d", nTotalCount);
+-    paFeatureIndex = (int *)CPLMalloc(nTotalCount * sizeof(int));
++    paFeatureIndex = static_cast<int *>(CPLMalloc(nTotalCount * sizeof(int)));
+ 
+     int n =
+         Vect_cidx_get_type_count(poMap, iLayer, GV_POINTS | GV_LINES | GV_AREA);
+     int cnt = 0;
+     for (int i = 0; i < n; i++)
+     {
+-        int cat, type, id;
++        int cat = 0, type = 0, id = 0;
+ 
+         Vect_cidx_get_cat_by_index(poMap, iLayerIndex, i, &cat, &type, &id);
+ 
+@@ -88,7 +67,7 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+         paFeatureIndex[cnt++] = i;
+     }
+ 
+-    poFeatureDefn = new OGRFeatureDefn(pszName);
++    poFeatureDefn = new OGRFeatureDefn(osName.c_str());
+     SetDescription(poFeatureDefn->GetName());
+     poFeatureDefn->Reference();
+ 
+@@ -97,7 +76,7 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+     int types = 0;
+     for (int i = 0; i < nTypes; i++)
+     {
+-        int type, count;
++        int type = 0, count = 0;
+         Vect_cidx_get_type_count_by_index(poMap, iLayerIndex, i, &type, &count);
+         if (!(type & (GV_POINT | GV_LINES | GV_AREA)))
+             continue;
+@@ -126,11 +105,11 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+         poFeatureDefn->SetGeomType(eGeomType);
+ 
+     // Get attributes definition
+-    poDbString = (dbString *)CPLMalloc(sizeof(dbString));
+-    poCursor = (dbCursor *)CPLMalloc(sizeof(dbCursor));
++    poDbString = reinterpret_cast<dbString *>(CPLMalloc(sizeof(dbString)));
++    poCursor = reinterpret_cast<dbCursor *>(CPLMalloc(sizeof(dbCursor)));
+     bCursorOpened = FALSE;
+ 
+-    poDriver = NULL;
++    poDriver = nullptr;
+     bHaveAttributes = false;
+     db_init_string(poDbString);
+     if (poLink)
+@@ -138,7 +117,7 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+         if (StartDbDriver())
+         {
+             db_set_string(poDbString, poLink->table);
+-            dbTable *table = NULL;
++            dbTable *table = nullptr;
+             if (db_describe_table(poDriver, poDbString, &table) == DB_OK)
+             {
+                 nFields = db_get_table_number_of_columns(table);
+@@ -187,7 +166,7 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+                     CPLError(CE_Failure, CPLE_AppDefined,
+                              "Cannot find key field");
+                     db_close_database_shutdown_driver(poDriver);
+-                    poDriver = NULL;
++                    poDriver = nullptr;
+                 }
+             }
+             else
+@@ -196,7 +175,7 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+                          "Cannot describe table %s", poLink->table);
+             }
+             db_close_database_shutdown_driver(poDriver);
+-            poDriver = NULL;
++            poDriver = nullptr;
+         }
+     }
+ 
+@@ -209,14 +188,12 @@ OGRGRASSLayer::OGRGRASSLayer(int layerIn
+ 
+     if (getenv("GISBASE"))  // We have some projection info in GISBASE
+     {
+-        struct Key_Value *projinfo, *projunits;
+-
+         // Note: we do not have to reset GISDBASE and LOCATION_NAME because
+         // OGRGRASSLayer constructor is called from OGRGRASSDataSource::Open
+         // where those variables are set
+ 
+-        projinfo = G_get_projinfo();
+-        projunits = G_get_projunits();
++        struct Key_Value *projinfo = G_get_projinfo();
++        struct Key_Value *projunits = G_get_projunits();
+ 
+         char *srsWkt = GPJ_grass_to_wkt(projinfo, projunits, 0, 0);
+         if (srsWkt)
+@@ -246,8 +223,6 @@ OGRGRASSLayer::~OGRGRASSLayer()
+         StopDbDriver();
+     }
+ 
+-    if (pszName)
+-        CPLFree(pszName);
+     if (poFeatureDefn)
+         poFeatureDefn->Release();
+     if (poSRS)
+@@ -278,7 +253,7 @@ OGRGRASSLayer::~OGRGRASSLayer()
+ /************************************************************************/
+ /*                            StartDbDriver                             */
+ /************************************************************************/
+-bool OGRGRASSLayer::StartDbDriver()
++auto OGRGRASSLayer::StartDbDriver() -> bool
+ {
+     CPLDebug("GRASS", "StartDbDriver()");
+ 
+@@ -290,7 +265,7 @@ bool OGRGRASSLayer::StartDbDriver()
+     }
+     poDriver = db_start_driver_open_database(poLink->driver, poLink->database);
+ 
+-    if (poDriver == NULL)
++    if (poDriver == nullptr)
+     {
+         CPLError(CE_Failure, CPLE_AppDefined,
+                  "Cannot open database %s by driver %s, "
+@@ -306,7 +281,7 @@ bool OGRGRASSLayer::StartDbDriver()
+ /************************************************************************/
+ /*                            StopDbDriver                              */
+ /************************************************************************/
+-bool OGRGRASSLayer::StopDbDriver()
++auto OGRGRASSLayer::StopDbDriver() -> bool
+ {
+     if (!poDriver)
+     {
+@@ -358,9 +333,9 @@ void OGRGRASSLayer::ResetReading()
+ /*      If we already have an FID list, we can easily reposition        */
+ /*      ourselves in it.                                                */
+ /************************************************************************/
+-OGRErr OGRGRASSLayer::SetNextByIndex(GIntBig nIndex)
++auto OGRGRASSLayer::SetNextByIndex(GIntBig nIndex) -> OGRErr
+ {
+-    if (m_poFilterGeom != NULL || m_poAttrQuery != NULL)
++    if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr)
+     {
+         iNextId = 0;
+         int count = 0;
+@@ -373,7 +348,7 @@ OGRErr OGRGRASSLayer::SetNextByIndex(GIn
+                 break;
+ 
+             // Attributes
+-            if (pszQuery != NULL && !paQueryMatch[iNextId])
++            if (pszQuery != nullptr && !paQueryMatch[iNextId])
+             {
+                 iNextId++;
+                 continue;
+@@ -389,7 +364,7 @@ OGRErr OGRGRASSLayer::SetNextByIndex(GIn
+         }
+     }
+ 
+-    iNextId = nIndex;
++    iNextId = (int)nIndex;
+ 
+     return OGRERR_NONE;
+ }
+@@ -397,27 +372,27 @@ OGRErr OGRGRASSLayer::SetNextByIndex(GIn
+ /************************************************************************/
+ /*                           SetAttributeFilter                         */
+ /************************************************************************/
+-OGRErr OGRGRASSLayer::SetAttributeFilter(const char *query)
++auto OGRGRASSLayer::SetAttributeFilter(const char *query) -> OGRErr
+ {
+     CPLDebug("GRASS", "SetAttributeFilter: %s", query);
+ 
+-    if (query == NULL)
++    if (query == nullptr)
+     {
+         // Release old if any
+         if (pszQuery)
+         {
+             CPLFree(pszQuery);
+-            pszQuery = NULL;
++            pszQuery = nullptr;
+         }
+         if (paQueryMatch)
+         {
+             CPLFree(paQueryMatch);
+-            paQueryMatch = NULL;
++            paQueryMatch = nullptr;
+         }
+         return OGRERR_NONE;
+     }
+ 
+-    paQueryMatch = (char *)CPLMalloc(nTotalCount);
++    paQueryMatch = reinterpret_cast<char *>(CPLMalloc(nTotalCount));
+     memset(paQueryMatch, 0x0, nTotalCount);
+     pszQuery = CPLStrdup(query);
+ 
+@@ -448,16 +423,16 @@ OGRErr OGRGRASSLayer::SetAttributeFilter
+             else
+             {
+                 CPLFree(pszQuery);
+-                pszQuery = NULL;
++                pszQuery = nullptr;
+                 return OGRERR_FAILURE;
+             }
+             db_close_database_shutdown_driver(poDriver);
+-            poDriver = NULL;
++            poDriver = nullptr;
+         }
+         else
+         {
+             CPLFree(pszQuery);
+-            pszQuery = NULL;
++            pszQuery = nullptr;
+             return OGRERR_FAILURE;
+         }
+     }
+@@ -482,7 +457,7 @@ OGRErr OGRGRASSLayer::SetAttributeFilter
+ /************************************************************************/
+ /*                           SetQueryMatch                              */
+ /************************************************************************/
+-bool OGRGRASSLayer::SetQueryMatch()
++auto OGRGRASSLayer::SetQueryMatch() -> bool
+ {
+     CPLDebug("GRASS", "SetQueryMatch");
+ 
+@@ -495,7 +470,7 @@ bool OGRGRASSLayer::SetQueryMatch()
+         return false;
+     }
+ 
+-    int more;
++    int more = 0;
+     int cidx = 0;  // index to category index
+     int fidx = 0;  // index to feature index (paFeatureIndex)
+     // number of categories in category index
+@@ -573,7 +548,7 @@ bool OGRGRASSLayer::SetQueryMatch()
+ /************************************************************************/
+ /*                           OpenSequentialCursor                       */
+ /************************************************************************/
+-bool OGRGRASSLayer::OpenSequentialCursor()
++auto OGRGRASSLayer::OpenSequentialCursor() -> bool
+ {
+     CPLDebug("GRASS", "OpenSequentialCursor: %s", pszQuery);
+ 
+@@ -589,18 +564,18 @@ bool OGRGRASSLayer::OpenSequentialCursor
+         bCursorOpened = false;
+     }
+ 
+-    char buf[2000];
+-    snprintf(buf, sizeof(buf), "SELECT * FROM %s ", poLink->table);
+-    db_set_string(poDbString, buf);
++    std::array<char, 2000> buf{};
++    (void)snprintf(buf.data(), buf.size(), "SELECT * FROM %s ", poLink->table);
++    db_set_string(poDbString, buf.data());
+ 
+     if (pszQuery)
+     {
+-        snprintf(buf, sizeof(buf), "WHERE %s ", pszQuery);
+-        db_append_string(poDbString, buf);
++        (void)snprintf(buf.data(), buf.size(), "WHERE %s ", pszQuery);
++        db_append_string(poDbString, buf.data());
+     }
+ 
+-    snprintf(buf, sizeof(buf), "ORDER BY %s", poLink->key);
+-    db_append_string(poDbString, buf);
++    (void)snprintf(buf.data(), buf.size(), "ORDER BY %s", poLink->key);
++    db_append_string(poDbString, buf.data());
+ 
+     CPLDebug("GRASS", "Query: %s", db_get_string(poDbString));
+ 
+@@ -622,11 +597,11 @@ bool OGRGRASSLayer::OpenSequentialCursor
+ /************************************************************************/
+ /*                           ResetSequentialCursor                      */
+ /************************************************************************/
+-bool OGRGRASSLayer::ResetSequentialCursor()
++auto OGRGRASSLayer::ResetSequentialCursor() -> bool
+ {
+     CPLDebug("GRASS", "ResetSequentialCursor");
+ 
+-    int more;
++    int more = 0;
+     if (db_fetch(poCursor, DB_FIRST, &more) != DB_OK)
+     {
+         CPLError(CE_Failure, CPLE_AppDefined, "Cannot reset cursor.");
+@@ -658,19 +633,20 @@ void OGRGRASSLayer::SetSpatialFilter(OGR
+     OGRLayer::SetSpatialFilter(poGeomIn);
+ #endif
+ 
+-    if (poGeomIn == NULL)
++    if (poGeomIn == nullptr)
+     {
+         // Release old if any
+         if (paSpatialMatch)
+         {
+             CPLFree(paSpatialMatch);
+-            paSpatialMatch = NULL;
++            paSpatialMatch = nullptr;
+         }
+     }
+     else
+     {
+         SetSpatialMatch();
+     }
++
+ #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
+     return OGRERR_NONE;
+ #endif
+@@ -679,17 +655,17 @@ void OGRGRASSLayer::SetSpatialFilter(OGR
+ /************************************************************************/
+ /*                           SetSpatialMatch                            */
+ /************************************************************************/
+-bool OGRGRASSLayer::SetSpatialMatch()
++auto OGRGRASSLayer::SetSpatialMatch() -> bool
+ {
+     CPLDebug("GRASS", "SetSpatialMatch");
+ 
+     if (!paSpatialMatch)
+     {
+-        paSpatialMatch = (char *)CPLMalloc(nTotalCount);
++        paSpatialMatch = static_cast<char *>(CPLMalloc(nTotalCount));
+     }
+     memset(paSpatialMatch, 0x0, nTotalCount);
+ 
+-    OGRLineString *lstring = new OGRLineString();
++    auto lstring = new OGRLineString();
+     lstring->setNumPoints(5);
+     OGRGeometry *geom = lstring;
+ 
+@@ -697,11 +673,13 @@ bool OGRGRASSLayer::SetSpatialMatch()
+     {
+         int cidx = paFeatureIndex[i];
+ 
+-        int cat, type, id;
++        int cat = 0, type = 0, id = 0;
+ 
+         Vect_cidx_get_cat_by_index(poMap, iLayerIndex, cidx, &cat, &type, &id);
+ 
+-        struct bound_box box;
++        struct bound_box box
++        {
++        };
+ 
+         switch (type)
+         {
+@@ -735,12 +713,12 @@ bool OGRGRASSLayer::SetSpatialMatch()
+ /************************************************************************/
+ /*                           GetNextFeature()                           */
+ /************************************************************************/
+-OGRFeature *OGRGRASSLayer::GetNextFeature()
++auto OGRGRASSLayer::GetNextFeature() -> OGRFeature *
+ {
+     CPLDebug("GRASS", "OGRGRASSLayer::GetNextFeature");
+-    OGRFeature *poFeature = NULL;
++    OGRFeature *poFeature = nullptr;
+ 
+-    int cat;
++    int cat = 0;
+ 
+     // Get next iNextId
+     while (true)
+@@ -756,14 +734,14 @@ OGRFeature *OGRGRASSLayer::GetNextFeatur
+             if (poDriver)
+             {
+                 db_close_database_shutdown_driver(poDriver);
+-                poDriver = NULL;
++                poDriver = nullptr;
+             }
+ 
+-            return NULL;
++            return nullptr;
+         }
+ 
+         // Attributes
+-        if (pszQuery != NULL && !paQueryMatch[iNextId])
++        if (pszQuery != nullptr && !paQueryMatch[iNextId])
+         {
+             iNextId++;
+             continue;
+@@ -807,7 +785,7 @@ OGRFeature *OGRGRASSLayer::GetNextFeatur
+                 {
+                     while (true)
+                     {
+-                        int more;
++                        int more = 0;
+                         if (db_fetch(poCursor, DB_NEXT, &more) != DB_OK)
+                         {
+                             CPLError(CE_Failure, CPLE_AppDefined,
+@@ -849,16 +827,16 @@ OGRFeature *OGRGRASSLayer::GetNextFeatur
+ /************************************************************************/
+ /*                             GetFeature()                             */
+ /************************************************************************/
+-OGRFeature *OGRGRASSLayer::GetFeature(GIntBig nFeatureId)
++auto OGRGRASSLayer::GetFeature(GIntBig nFeatureId) -> OGRFeature *
+ 
+ {
+     CPLDebug("GRASS", "OGRGRASSLayer::GetFeature nFeatureId = " CPL_FRMT_GIB,
+              nFeatureId);
+ 
+-    int cat;
++    int cat = 0;
+     OGRGeometry *poOGR = GetFeatureGeometry(nFeatureId, &cat);
+ 
+-    OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
++    auto poFeature = new OGRFeature(poFeatureDefn);
+     poFeature->SetGeometryDirectly(poOGR);
+     poFeature->SetFID(nFeatureId);
+ 
+@@ -875,10 +853,10 @@ OGRFeature *OGRGRASSLayer::GetFeature(GI
+             bCursorOpened = false;
+         }
+         CPLDebug("GRASS", "Open cursor for key = %d", cat);
+-        char buf[2000];
+-        snprintf(buf, sizeof(buf), "SELECT * FROM %s WHERE %s = %d",
+-                 poLink->table, poLink->key, cat);
+-        db_set_string(poDbString, buf);
++        std::array<char, 2000> buf{};
++        (void)snprintf(buf.data(), buf.size(), "SELECT * FROM %s WHERE %s = %d",
++                       poLink->table, poLink->key, cat);
++        db_set_string(poDbString, buf.data());
+         if (db_open_select_cursor(poDriver, poDbString, poCursor,
+                                   DB_SEQUENTIAL) == DB_OK)
+         {
+@@ -892,7 +870,7 @@ OGRFeature *OGRGRASSLayer::GetFeature(GI
+ 
+         if (bCursorOpened)
+         {
+-            int more;
++            int more = 0;
+             if (db_fetch(poCursor, DB_NEXT, &more) != DB_OK)
+             {
+                 CPLError(CE_Failure, CPLE_AppDefined,
+@@ -927,19 +905,20 @@ OGRFeature *OGRGRASSLayer::GetFeature(GI
+ /************************************************************************/
+ /*                             GetFeatureGeometry()                     */
+ /************************************************************************/
+-OGRGeometry *OGRGRASSLayer::GetFeatureGeometry(long nFeatureId, int *cat)
++auto OGRGRASSLayer::GetFeatureGeometry(long nFeatureId, int *cat)
++    -> OGRGeometry *
+ {
+     CPLDebug("GRASS", "OGRGRASSLayer::GetFeatureGeometry nFeatureId = %ld",
+              nFeatureId);
+ 
+     int cidx = paFeatureIndex[(int)nFeatureId];
+ 
+-    int type, id;
++    int type = 0, id = 0;
+     Vect_cidx_get_cat_by_index(poMap, iLayerIndex, cidx, cat, &type, &id);
+ 
+     //CPLDebug ( "GRASS", "cat = %d type = %d id = %d", *cat, type, id );
+ 
+-    OGRGeometry *poOGR = NULL;
++    OGRGeometry *poOGR = nullptr;
+     int bIs3D = Vect_is_3d(poMap);
+ 
+     switch (type)
+@@ -959,7 +938,7 @@ OGRGeometry *OGRGRASSLayer::GetFeatureGe
+         case GV_BOUNDARY:
+         {
+             Vect_read_line(poMap, poPoints, poCats, id);
+-            OGRLineString *poOGRLine = new OGRLineString();
++            auto poOGRLine = new OGRLineString();
+             if (bIs3D)
+                 poOGRLine->setPoints(poPoints->n_points, poPoints->x,
+                                      poPoints->y, poPoints->z);
+@@ -975,9 +954,9 @@ OGRGeometry *OGRGRASSLayer::GetFeatureGe
+         {
+             Vect_get_area_points(poMap, id, poPoints);
+ 
+-            OGRPolygon *poOGRPoly = new OGRPolygon();
++            auto poOGRPoly = new OGRPolygon();
+ 
+-            OGRLinearRing *poRing = new OGRLinearRing();
++            auto poRing = new OGRLinearRing();
+             if (bIs3D)
+                 poRing->setPoints(poPoints->n_points, poPoints->x, poPoints->y,
+                                   poPoints->z);
+@@ -1012,7 +991,7 @@ OGRGeometry *OGRGRASSLayer::GetFeatureGe
+         {
+             CPLError(CE_Failure, CPLE_AppDefined,
+                      "Unknown GRASS feature type.");
+-            return NULL;
++            return nullptr;
+         }
+     }
+ 
+@@ -1022,7 +1001,7 @@ OGRGeometry *OGRGRASSLayer::GetFeatureGe
+ /************************************************************************/
+ /*                          SetAttributes()                             */
+ /************************************************************************/
+-bool OGRGRASSLayer::SetAttributes(OGRFeature *poFeature, dbTable *table)
++auto OGRGRASSLayer::SetAttributes(OGRFeature *poFeature, dbTable *table) -> bool
+ {
+     CPLDebug("GRASS", "OGRGRASSLayer::SetAttributes");
+ 
+@@ -1067,9 +1046,9 @@ bool OGRGRASSLayer::SetAttributes(OGRFea
+ /*      Eventually we should consider implementing a more efficient     */
+ /*      way of counting features matching a spatial query.              */
+ /************************************************************************/
+-GIntBig OGRGRASSLayer::GetFeatureCount(int bForce)
++auto OGRGRASSLayer::GetFeatureCount(int bForce) -> GIntBig
+ {
+-    if (m_poFilterGeom != NULL || m_poAttrQuery != NULL)
++    if (m_poFilterGeom != nullptr || m_poAttrQuery != nullptr)
+         return OGRLayer::GetFeatureCount(bForce);
+ 
+     return nTotalCount;
+@@ -1084,14 +1063,17 @@ GIntBig OGRGRASSLayer::GetFeatureCount(i
+ /*                                                                      */
+ /*      Returns OGRERR_NONE/OGRRERR_FAILURE.                            */
+ /************************************************************************/
++
+ #if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3, 11, 0)
+-OGRErr OGRGRASSLayer::IGetExtent(int /*iGeomField */, OGREnvelope *psExtent,
+-                                 bool /*bForce*/)
++auto OGRGRASSLayer::IGetExtent(int /*iGeomField */, OGREnvelope *psExtent,
++                               bool /*bForce*/) -> OGRErr
+ #else
+-OGRErr OGRGRASSLayer::GetExtent(OGREnvelope *psExtent, int /*bForce*/)
++auto OGRGRASSLayer::GetExtent(OGREnvelope *psExtent, int /*bForce*/) -> OGRErr
+ #endif
+ {
+-    struct bound_box box;
++    struct bound_box box
++    {
++    };
+ 
+     Vect_get_map_box(poMap, &box);
+ 
+@@ -1106,7 +1088,11 @@ OGRErr OGRGRASSLayer::GetExtent(OGREnvel
+ /************************************************************************/
+ /*                           TestCapability()                           */
+ /************************************************************************/
+-int OGRGRASSLayer::TestCapability(const char *pszCap)
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++auto OGRGRASSLayer::TestCapability(const char *pszCap) const -> int
++#else
++auto OGRGRASSLayer::TestCapability(const char *pszCap) -> int
++#endif
+ {
+     if (EQUAL(pszCap, OLCRandomRead))
+         return TRUE;
+@@ -1130,7 +1116,11 @@ int OGRGRASSLayer::TestCapability(const
+ /************************************************************************/
+ /*                           GetSpatialRef()                            */
+ /************************************************************************/
+-OGRSpatialReference *OGRGRASSLayer::GetSpatialRef()
++#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,12,0)
++auto OGRGRASSLayer::GetSpatialRef() const -> const OGRSpatialReference *
++#else
++auto OGRGRASSLayer::GetSpatialRef() -> OGRSpatialReference *
++#endif
+ {
+     return poSRS;
+ }


=====================================
debian/patches/series
=====================================
@@ -0,0 +1 @@
+gdal-3.12.patch



View it on GitLab: https://salsa.debian.org/debian-gis-team/gdal-grass/-/compare/6b927235b4105f775d76bbc428bed9ddc2b8f790...d7651cabb7847c8d829d01948b7475fd708b4989

-- 
View it on GitLab: https://salsa.debian.org/debian-gis-team/gdal-grass/-/compare/6b927235b4105f775d76bbc428bed9ddc2b8f790...d7651cabb7847c8d829d01948b7475fd708b4989
You're receiving this email because of your account on salsa.debian.org.


-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20251025/68befcad/attachment-0001.htm>


More information about the Pkg-grass-devel mailing list