Skip to content

Commit 8fb0f47

Browse files
committed
iov_iter: add helper to save iov_iter state
In an ideal world, when someone is passed an iov_iter and returns X bytes, then X bytes would have been consumed/advanced from the iov_iter. But we have use cases that always consume the entire iterator, a few examples of that are iomap and bdev O_DIRECT. This means we cannot rely on the state of the iov_iter once we've called ->read_iter() or ->write_iter(). This would be easier if we didn't always have to deal with truncate of the iov_iter, as rewinding would be trivial without that. We recently added a commit to track the truncate state, but that grew the iov_iter by 8 bytes and wasn't the best solution. Implement a helper to save enough of the iov_iter state to sanely restore it after we've called the read/write iterator helpers. This currently only works for IOVEC/BVEC/KVEC as that's all we need, support for other iterator types are left as an exercise for the reader. Link: https://lore.kernel.org/linux-fsdevel/CAHk-=wiacKV4Gh-MYjteU0LwNBSGpWrK-Ov25HdqB1ewinrFPg@mail.gmail.com/ Signed-off-by: Jens Axboe <axboe@kernel.dk>
1 parent d6c338a commit 8fb0f47

File tree

2 files changed

+51
-0
lines changed

2 files changed

+51
-0
lines changed

include/linux/uio.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ enum iter_type {
2727
ITER_DISCARD,
2828
};
2929

30+
struct iov_iter_state {
31+
size_t iov_offset;
32+
size_t count;
33+
unsigned long nr_segs;
34+
};
35+
3036
struct iov_iter {
3137
u8 iter_type;
3238
bool data_source;
@@ -55,6 +61,14 @@ static inline enum iter_type iov_iter_type(const struct iov_iter *i)
5561
return i->iter_type;
5662
}
5763

64+
static inline void iov_iter_save_state(struct iov_iter *iter,
65+
struct iov_iter_state *state)
66+
{
67+
state->iov_offset = iter->iov_offset;
68+
state->count = iter->count;
69+
state->nr_segs = iter->nr_segs;
70+
}
71+
5872
static inline bool iter_is_iovec(const struct iov_iter *i)
5973
{
6074
return iov_iter_type(i) == ITER_IOVEC;
@@ -233,6 +247,7 @@ ssize_t iov_iter_get_pages(struct iov_iter *i, struct page **pages,
233247
ssize_t iov_iter_get_pages_alloc(struct iov_iter *i, struct page ***pages,
234248
size_t maxsize, size_t *start);
235249
int iov_iter_npages(const struct iov_iter *i, int maxpages);
250+
void iov_iter_restore(struct iov_iter *i, struct iov_iter_state *state);
236251

237252
const void *dup_iter(struct iov_iter *new, struct iov_iter *old, gfp_t flags);
238253

lib/iov_iter.c

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1972,3 +1972,39 @@ int import_single_range(int rw, void __user *buf, size_t len,
19721972
return 0;
19731973
}
19741974
EXPORT_SYMBOL(import_single_range);
1975+
1976+
/**
1977+
* iov_iter_restore() - Restore a &struct iov_iter to the same state as when
1978+
* iov_iter_save_state() was called.
1979+
*
1980+
* @i: &struct iov_iter to restore
1981+
* @state: state to restore from
1982+
*
1983+
* Used after iov_iter_save_state() to bring restore @i, if operations may
1984+
* have advanced it.
1985+
*
1986+
* Note: only works on ITER_IOVEC, ITER_BVEC, and ITER_KVEC
1987+
*/
1988+
void iov_iter_restore(struct iov_iter *i, struct iov_iter_state *state)
1989+
{
1990+
if (WARN_ON_ONCE(!iov_iter_is_bvec(i) && !iter_is_iovec(i)) &&
1991+
!iov_iter_is_kvec(i))
1992+
return;
1993+
i->iov_offset = state->iov_offset;
1994+
i->count = state->count;
1995+
/*
1996+
* For the *vec iters, nr_segs + iov is constant - if we increment
1997+
* the vec, then we also decrement the nr_segs count. Hence we don't
1998+
* need to track both of these, just one is enough and we can deduct
1999+
* the other from that. ITER_KVEC and ITER_IOVEC are the same struct
2000+
* size, so we can just increment the iov pointer as they are unionzed.
2001+
* ITER_BVEC _may_ be the same size on some archs, but on others it is
2002+
* not. Be safe and handle it separately.
2003+
*/
2004+
BUILD_BUG_ON(sizeof(struct iovec) != sizeof(struct kvec));
2005+
if (iov_iter_is_bvec(i))
2006+
i->bvec -= state->nr_segs - i->nr_segs;
2007+
else
2008+
i->iov -= state->nr_segs - i->nr_segs;
2009+
i->nr_segs = state->nr_segs;
2010+
}

0 commit comments

Comments
 (0)