[GRASS-dev] Improving the G_calloc: unable to allocate xx bytes of memory message?

Glynn Clements glynn at gclements.plus.com
Mon Oct 27 10:42:02 PDT 2014


Markus Neteler wrote:

> GRASS 7.1.svn (nc_spm_08_grass7):~ > v.to.rast roadsmajor out=test
> use=attr attrcol=PROPYEAR memory=3000000 --o
> WARNING: No areas selected from vector map <roadsmajor>
> Current region rows: 135000, cols: 150000
> ERROR: G_calloc: unable to allocate 18446744072484715136 * 4 bytes of
>        memory at vector/v.to.rast/raster.c:84

$ printf '%x' 18446744072484715136
ffffffffb6fe7a80

Those leading "f"s are a giveway. It's almost certain that a
calculation was done with 32-bit arithmetic, which overflowed to
produce a negative result (0xb6fe7a80 = -1224836480), which was then
converted to 64 bits with sign extension, then cast to unsigned.

> ... only, it does not return to command line (have to use CTRL-C).

It's possible that memory corruption has already occurred, resulting
in e.g. an infinite loop.

A great deal of GRASS code still uses "int" where it should be using
"long", "long long", "size_t" or "ssize_t". Fixing this is far from
straightforward as:

1. It's likely to be a lot of work.

2.  I don't know of any easy way to detect such cases.

3. We still haven't decided upon the appropriate type. All of the
options have drawbacks; to recap:

	long		- only 32 bits on 64-bit versions of Windows
	long long	- not specified by C89
	size_t		- unsigned
	ssize_t		- POSIX; not specified by any version of ISO C

Also: Windows' write() function uses "int" for the count argument and
the return value, so it can't handle more than 2 GiB at a time.
Likewise for read().

> >> Yet a bit unhelpful :-) It comes from lib/segment/format.c but I
> >> didn't manage to improve that.
> >
> > Specifically:
> >
> >         if (write(fd, buf, n) != n) {
> >             G_warning("segment zero_fill(): Unable to write (%s)", strerror(errno));
> >
> > ENOENT ("No such file or directory") isn't a valid error code for
> > write(). But errno is only set if write() returns -1, so it's likely
> > that write() returned a short count rather than failing and the errno
> > value is left over from a previous system call (sucessful calls don't
> > reset errno to zero). In all probability, the next call to write()
> > would have failed due to ENOSPC, EDQUOT, or EFBIG (or raised SIGXFSZ
> > if RLIMIT_FSIZE is being exceeded).
> 
> I see and now darkly remember something about discussion in this list
> about clearing the previous error state.

The main issue here is that errno is being examined when write() has
reported a short count (a non-negative return value less than the
value of the third argument) rather than an error (returning -1).

The code should either distinguish these cases, or use something like:

ssize_t writen(int fd, const void *buf, size_t count)
{
    size_t left = count;
    const char *ptr = buf;
    while (left > 0) {
        ssize_t n = write(fd, ptr, left);
        if (n <= 0)
            return -1;
        ptr += n;
        left -= n;
    }
    return count;
}

This never returns a short count; it either writes (and returns) the
exact number of bytes requested or returns -1 (errno won't be changed
if write() returns 0, but that should only occur when the third
argument is 0; if write() can't write *anything*, that's an error
condition).

-- 
Glynn Clements <glynn at gclements.plus.com>


More information about the grass-dev mailing list