std.file.read implementation contest

Steven Schveighoffer schveiguy at yahoo.com
Mon Feb 16 12:36:17 PST 2009


"Andrei Alexandrescu" wrote
> Someone mentioned an old bug in std.file.read here:
>
> http://www.reddit.com/r/programming/comments/7xnty/walter_bright_on_porting_d_to_the_mac/
>
> Two programmers sent in patches for the function. Which is to be committed 
> and why? (Linux versions shown. Apologies for noisy line breaks.)

Implementation 2, save 1 bug:

> Andrei
>
>
> // Implementation 1
...

>     else
>     {
> buf = GC.malloc(size, GC.BlkAttr.NO_SCAN)[0 .. size];
> enforce(buf, "Out of memory");
> scope(failure) delete buf;
>
> cenforce(std.c.linux.linux.read(fd, buf.ptr, size) == size, name);
>
> return buf[0 .. size];
>     }
> }

This doesn't work for /sys files which consistently say they are 4096 bytes 
large, even though they can be smaller or larger (encountered this with 
Tango also).

I think you have to kill the check that the size read actually equals the 
size in stat, AND you have to continue reading even after you reach that 
size.

>
> // Implementation 2
> void[] read(string name)
> {
>     immutable fd = std.c.linux.linux.open(toStringz(name), O_RDONLY);
>     cenforce(fd != -1, name);
>     scope(exit) std.c.linux.linux.close(fd);
>
>     struct_stat statbuf = void;
>     cenforce(std.c.linux.linux.fstat(fd, &statbuf) == 0, name);
>
>     immutable initialAlloc = statbuf.st_size ? statbuf.st_size + 1 : 1024;

Excellent idea, this allocates the minimum amount of space to tell if a file 
is larger than the file size returned in fstat.  This should handle the /sys 
case and /proc case properly.

>     void[] result = GC.malloc(initialAlloc, GC.BlkAttr.NO_SCAN)
>         [0 .. initialAlloc];
>     scope(failure) delete result;
>     size_t size = 0;
>
>     for (;;)
>     {
>         immutable actual = std.c.linux.linux.read(fd, result.ptr + size,
>                 result.length - size);
>         cenforce(actual != actual.max, name);
>         size += actual;
>         if (size < result.length) break;

Only bug:
You need to check if actual > 0.  A driver is allowed to return less data 
than requested, even if there is more to read.  You must read until read 
returns 0.  In this case you need a loop to do the reading.  Yes the disk 
driver never returns less than requested unless EOF is reached, but the 
/proc files are run by any driver, which might do something like that.

>         auto newAlloc = size + 1024 * 4;
>         result = GC.realloc(result.ptr, newAlloc, GC.BlkAttr.NO_SCAN)
>             [0 .. newAlloc];
>     }
>
>     return result[0 .. size];
> }

I would rewrite:

void[] read(string name)
{
    immutable fd = std.c.linux.linux.open(toStringz(name), O_RDONLY);
    cenforce(fd != -1, name);
    scope(exit) std.c.linux.linux.close(fd);

    struct_stat statbuf = void;
    cenforce(std.c.linux.linux.fstat(fd, &statbuf) == 0, name);

    auto allocSize = statbuf.st_size ? statbuf.st_size + 1 : 1024;
    void[] result;
    scope(failure) delete result;
    size_t size = 0;

outer:
    while(true)
    {
        result = GC.realloc(result.ptr, allocSize, GC.BlkAttr.NO_SCAN)
            [0 .. allocSize];
        while(size < allocSize)
        {
            immutable actual = std.c.linux.linux.read(fd, result.ptr + size,
                    allocSize - size);
            if(actual == 0)
                break outer;
            cenforce(actual != actual.max, name);
            size += actual;
        }
        allocSize = size + 1024 * 4;
    }

    return result[0 .. size];
} 





More information about the Digitalmars-d mailing list