Skip to content

Commit 3996400

Browse files
authored
lib/raster: Add larger buffers, error messages to reclass header reader (OSGeo#6341)
This increases the size of buffer the reclass header reader code (reading header of the header file) uses for reading raster and mapset names. It also adds an additional check for reading the whole line and errors if not read. A message can now be optionally reported for each of the already handled error states. The maximum length of files is now checked by a test which fails with the original buffer size combined with both the original code and the new error handling code, but the individual error states are not tested. Using 50 as the name length on Windows to avoid 'MAPSET m... not found at' error from r.mapcalc call which occurs with length 255.
1 parent 8cb31ee commit 3996400

File tree

2 files changed

+114
-14
lines changed

2 files changed

+114
-14
lines changed

lib/raster/reclass.c

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
#include <grass/glocale.h>
2020

2121
static const char NULL_STRING[] = "null";
22-
static int reclass_type(FILE *, char **, char **);
22+
static int reclass_type(FILE *, char **, char **, char **);
2323
static FILE *fopen_cellhd_old(const char *, const char *);
2424
static FILE *fopen_cellhd_new(const char *);
2525
static int get_reclass_table(FILE *, struct Reclass *, char **);
@@ -51,7 +51,7 @@ int Rast_is_reclass(const char *name, const char *mapset, char *rname,
5151
if (fd == NULL)
5252
return -1;
5353

54-
type = reclass_type(fd, &rname, &rmapset);
54+
type = reclass_type(fd, &rname, &rmapset, NULL);
5555
fclose(fd);
5656
if (type < 0)
5757
return -1;
@@ -135,8 +135,7 @@ int Rast_is_reclassed_to(const char *name, const char *mapset, int *nrmaps,
135135
\param mapset mapset name
136136
\param[out] reclass pointer to Reclass structure
137137
138-
\return -1 on error
139-
\return type code
138+
\return type code (>=1), 0 if no reclass, -1 on error
140139
*/
141140
int Rast_get_reclass(const char *name, const char *mapset,
142141
struct Reclass *reclass)
@@ -149,13 +148,24 @@ int Rast_get_reclass(const char *name, const char *mapset,
149148
return -1;
150149
reclass->name = NULL;
151150
reclass->mapset = NULL;
152-
reclass->type = reclass_type(fd, &reclass->name, &reclass->mapset);
153-
if (reclass->type <= 0) {
151+
char *error_message = NULL;
152+
reclass->type =
153+
reclass_type(fd, &reclass->name, &reclass->mapset, &error_message);
154+
if (reclass->type == 0) {
155+
// no reclass
154156
fclose(fd);
155157
return reclass->type;
156158
}
159+
if (reclass->type < 0) {
160+
// error
161+
fclose(fd);
162+
G_warning(_("Error reading beginning of header file for <%s@%s>: %s"),
163+
name, mapset, error_message);
164+
if (error_message != NULL)
165+
G_free(error_message);
166+
return reclass->type;
167+
}
157168

158-
char *error_message = NULL;
159169
switch (reclass->type) {
160170
case RECLASS_TABLE:
161171
stat = get_reclass_table(fd, reclass, &error_message);
@@ -204,10 +214,22 @@ void Rast_free_reclass(struct Reclass *reclass)
204214
}
205215
}
206216

207-
static int reclass_type(FILE *fd, char **rname, char **rmapset)
217+
/**
218+
* \brief Get reclass type if it is a reclass file
219+
*
220+
* \param fd[in] file descriptor
221+
* \param rname[out] name of the reclass from raster
222+
* \param rmapset[out] name of the mapset of the raster
223+
* \param error_message[out] will be assigned a newly error message if not NULL
224+
*
225+
* \returns RECLASS_TABLE if reclass, 0 if not, -1 on error
226+
*/
227+
static int reclass_type(FILE *fd, char **rname, char **rmapset,
228+
char **error_message)
208229
{
209-
char buf[128];
210-
char label[128], arg[128];
230+
char
231+
buf[GNAME_MAX + 128 + 1]; // name or mapset plus the label and separator
232+
char label[128], arg[GNAME_MAX];
211233
int i;
212234
int type;
213235

@@ -225,10 +247,26 @@ static int reclass_type(FILE *fd, char **rname, char **rmapset)
225247
if (*rmapset)
226248
**rmapset = '\0';
227249
for (i = 0; i < 2; i++) {
228-
if (fgets(buf, sizeof buf, fd) == NULL)
250+
if (fgets(buf, sizeof buf, fd) == NULL) {
251+
if (error_message != NULL) {
252+
G_asprintf(error_message, _("File too short, reading line %d"),
253+
i + 1);
254+
}
255+
return -1;
256+
}
257+
if (buf[strlen(buf) - 1] != '\n') {
258+
if (error_message != NULL) {
259+
G_asprintf(error_message, _("Line too long: %s..."), buf);
260+
}
229261
return -1;
230-
if (sscanf(buf, "%[^:]:%s", label, arg) != 2)
262+
}
263+
if (sscanf(buf, "%[^:]:%s", label, arg) != 2) {
264+
if (error_message != NULL) {
265+
G_asprintf(error_message, _("Format is not key:value: %s"),
266+
buf);
267+
}
231268
return -1;
269+
}
232270
if (strncmp(label, "maps", 4) == 0) {
233271
if (*rmapset)
234272
strcpy(*rmapset, arg);
@@ -241,13 +279,30 @@ static int reclass_type(FILE *fd, char **rname, char **rmapset)
241279
else
242280
*rname = G_store(arg);
243281
}
244-
else
282+
else {
283+
if (error_message != NULL) {
284+
G_asprintf(error_message, _("Unknown key at line: %s"), buf);
285+
}
245286
return -1;
287+
}
246288
}
247289
if (**rmapset && **rname)
248290
return type;
249-
else
291+
else {
292+
// If they do not occur in the two lines we expect them.
293+
if (**rname && error_message != NULL) {
294+
G_asprintf(error_message,
295+
_("Mapset not read, only raster name: %s"), *rname);
296+
}
297+
else if (**rmapset && error_message != NULL) {
298+
G_asprintf(error_message,
299+
_("Raster name not read, only mapset: %s"), *rmapset);
300+
}
301+
else if (error_message != NULL) {
302+
*error_message = G_store(_("Raster name and mapset not read"));
303+
}
250304
return -1;
305+
}
251306
}
252307

253308
static FILE *fopen_cellhd_old(const char *name, const char *mapset)

lib/raster/tests/lib_raster_reclass_test.py

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
1+
import os
12
from pathlib import Path
3+
from io import StringIO
24

35
import pytest
46

57
from grass.script import MaskManager
68
from grass.tools import Tools
79
from grass.exceptions import CalledModuleError
10+
from grass.experimental import MapsetSession
811

912

1013
def test_reclass_as_mask_correct_state(session):
@@ -66,3 +69,45 @@ def test_reclass_as_mask_intermixed_hash_value_line(session):
6669
cellhd_file.write_text(content, encoding="utf-8")
6770
with pytest.raises(CalledModuleError, match=r"(?s)not read yet.*#1"):
6871
tools.r_univar(map="data")
72+
73+
74+
def get_filename_length_limit(path):
75+
"""Get maximum filename length for the given path"""
76+
try:
77+
# Expected to work on unix
78+
return os.pathconf(path, "PC_NAME_MAX")
79+
except (AttributeError, ValueError, OSError):
80+
# On Windows, there is no os.pathconf
81+
# and the other exceptions are from function doc.
82+
# Getting actual value for Windows would be complicated,
83+
# so we go with a low value rather than skipping the test.
84+
return 50
85+
86+
87+
def test_long_names(session, tmp_path):
88+
"""Check that long names are handled correctly"""
89+
# Using 255 because that's GNAME_MAX is 256 minus the terminator.
90+
name_size = min(get_filename_length_limit(tmp_path), 255)
91+
mapset_name = "m" * name_size
92+
long_name = "l" * name_size
93+
name = "r" * name_size
94+
with (
95+
MapsetSession(name=mapset_name, create=True, env=session.env) as mapset_session,
96+
Tools(session=mapset_session) as tools,
97+
):
98+
# This one is the one to actually break when the names are too long for the
99+
# internal buffer - the raster being used in r.reclass has a long name.
100+
tools.r_mapcalc(expression=f"{long_name} = raster_mask")
101+
tools.r_reclass(input=long_name, output=name, rules=StringIO("1 = 5\n"))
102+
tools.r_univar(map=name)
103+
104+
tools.r_reclass(input=name, output="short_name", rules=StringIO("1 = 5\n"))
105+
tools.r_univar(map="short_name")
106+
107+
with MaskManager(env=mapset_session.env) as mask:
108+
tools.r_mask(raster=long_name, env=mask.env)
109+
tools.r_univar(map=name, env=mask.env)
110+
111+
with MaskManager(env=mapset_session.env, mask_name="n" * name_size) as mask:
112+
tools.r_mask(raster=long_name, env=mask.env)
113+
tools.r_univar(map=name, env=mask.env)

0 commit comments

Comments
 (0)