On Mon, Jul 26, 2021 at 10:19:42AM +0200, Christoph Hellwig wrote:
> On Mon, Jul 19, 2021 at 10:05:45AM -0700, Darrick J. Wong wrote:
> > >   bno = 0;
> > > - ret = iomap_apply(inode, pos, blocksize, 0, ops, &bno,
> > > -                   iomap_bmap_actor);
> > > + while ((ret = iomap_iter(&iter, ops)) > 0) {
> > > +         if (iter.iomap.type != IOMAP_MAPPED)
> > > +                 continue;
> > 
> > There isn't a mapped extent, so return 0 here, right?
> 
> We can't just return 0, we always need the final iomap_iter() call
> to clean up in case a ->iomap_end method is supplied.  No for bmap
> having and needing one is rather theoretical, but people will copy
> and paste that once we start breaking the rules.

Oh, right, I forgot that someone might want to ->iomap_end.  The
"continue" works because we only asked for one block, therefore we know
that we'll never get to the loop body a second time; and we ignore
iter.processed, which also means we never revisit the loop body.

This "continue without setting iter.processed to break out of loop"
pattern is a rather indirect subtlety, since C programmers are taught
that they can break out of a loop using break;.  This new iomap_iter
pattern fubars that longstanding language feature, and the language
around it is soft:

> /**
>  * iomap_iter - iterate over a ranges in a file
>  * @iter: iteration structue
>  * @ops: iomap ops provided by the file system
>  *
>  * Iterate over file system provided contiguous ranges of blocks with the same
>  * state.  Should be called in a loop that continues as long as this function
>  * returns a positive value.  If 0 or a negative value is returned the caller
>  * should break out of the loop - a negative value is an error either from the
>  * file system or from the last iteration stored in @iter.copied.
>  */

The documentation needs to be much more explicit about the fact that you
cannot "break;" your way out of an iomap_iter loop.  I think the comment
should be rewritten along these lines:

"Iterate over filesystem-provided space mappings for the provided file
range.  This function handles cleanup of resources acquired for
iteration when the filesystem indicates there are no more space
mappings, which means that this function must be called in a loop that
continues as long it returns a positive value.  If 0 or a negative value
is returned, the caller must not return to the loop body.  Within a loop
body, there are two ways to break out of the loop body: leave
@iter.processed unchanged, or set it to the usual negative errno."

Hm.

What if we provide an explicit loop break function?  That would be clear
overkill for bmap, but somebody else wanting to break out of a more
complex loop body ought to be able to say "break" to do that, not
"continue with subtleties".

static inline int
iomap_iter_break(struct iomap_iter *iter, int ret)
{
        int ret2;

        if (!iter->iomap.length || !ops->iomap_end)
                return ret;

        ret2 = ops->iomap_end(iter->inode, iter->pos, iomap_length(iter),
                        0, iter->flags, &iter->iomap);
        return ret ? ret : ret2;
}

And then then theoretical loop body becomes:

        while ((ret = iomap_iter(&iter, ops)) > 0) {
                if (iter.iomap.type != WHAT_I_WANT) {
                        ret = iomap_iter_break(&iter, 0);
                        break;
                }

                <large blob of code here>

                ret = vfs_do_some_risky_thing(...);
                if (ret) {
                        ret = iomap_iter_break(&iter, ret);
                        break;
                }

                <more loop body here>

                iter.processed = iter.iomap.length;
        }
        return ret;

Clunky, for sure, but at least we still get to use break as the language
designers intended.

--D

Reply via email to