Blame view

net/core/iovec.c 5.19 KB
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
  /*
   *	iovec manipulation routines.
   *
   *
   *		This program is free software; you can redistribute it and/or
   *		modify it under the terms of the GNU General Public License
   *		as published by the Free Software Foundation; either version
   *		2 of the License, or (at your option) any later version.
   *
   *	Fixes:
   *		Andrew Lunn	:	Errors in iovec copying.
   *		Pedro Roque	:	Added memcpy_fromiovecend and
   *					csum_..._fromiovecend.
   *		Andi Kleen	:	fixed error handling for 2.1
   *		Alexey Kuznetsov:	2.1 optimisations
   *		Andi Kleen	:	Fix csum*fromiovecend for IPv6.
   */
  
  #include <linux/errno.h>
  #include <linux/module.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
21
22
  #include <linux/kernel.h>
  #include <linux/mm.h>
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
23
24
25
26
27
28
29
30
31
32
33
  #include <linux/net.h>
  #include <linux/in6.h>
  #include <asm/uaccess.h>
  #include <asm/byteorder.h>
  #include <net/checksum.h>
  #include <net/sock.h>
  
  /*
   *	Verify iovec. The caller must ensure that the iovec is big enough
   *	to hold the message iovec.
   *
e49332bd1   Jesper Juhl   [PATCH] misc veri...
34
   *	Save time not doing access_ok. copy_*_user will make this work
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
35
36
   *	in any case.
   */
43db362d3   Maciej Żenczykowski   net: get rid of s...
37
  int verify_iovec(struct msghdr *m, struct iovec *iov, struct sockaddr_storage *address, int mode)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
38
  {
8acfe468b   David S. Miller   net: Limit socket...
39
  	int size, ct, err;
4ec93edb1   YOSHIFUJI Hideaki   [NET] CORE: Fix w...
40

1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
41
42
  	if (m->msg_namelen) {
  		if (mode == VERIFY_READ) {
a700d8be7   Namhyung Kim   net/core: remove ...
43
44
45
  			void __user *namep;
  			namep = (void __user __force *) m->msg_name;
  			err = move_addr_to_kernel(namep, m->msg_namelen,
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
46
47
48
49
  						  address);
  			if (err < 0)
  				return err;
  		}
f3d334260   Hannes Frederic Sowa   net: rework recvm...
50
51
  		if (m->msg_name)
  			m->msg_name = address;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
52
53
54
55
56
  	} else {
  		m->msg_name = NULL;
  	}
  
  	size = m->msg_iovlen * sizeof(struct iovec);
a700d8be7   Namhyung Kim   net/core: remove ...
57
  	if (copy_from_user(iov, (void __user __force *) m->msg_iov, size))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
58
59
60
61
62
63
  		return -EFAULT;
  
  	m->msg_iov = iov;
  	err = 0;
  
  	for (ct = 0; ct < m->msg_iovlen; ct++) {
8acfe468b   David S. Miller   net: Limit socket...
64
65
66
67
68
69
70
  		size_t len = iov[ct].iov_len;
  
  		if (len > INT_MAX - err) {
  			len = INT_MAX - err;
  			iov[ct].iov_len = len;
  		}
  		err += len;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
71
72
73
74
75
76
77
  	}
  
  	return err;
  }
  
  /*
   *	Copy kernel to iovec. Returns -EFAULT on error.
0a1ec07a6   Michael S. Tsirkin   net: skb_copy_dat...
78
79
80
81
82
83
84
85
86
87
88
89
90
   */
  
  int memcpy_toiovecend(const struct iovec *iov, unsigned char *kdata,
  		      int offset, int len)
  {
  	int copy;
  	for (; len > 0; ++iov) {
  		/* Skip over the finished iovecs */
  		if (unlikely(offset >= iov->iov_len)) {
  			offset -= iov->iov_len;
  			continue;
  		}
  		copy = min_t(unsigned int, iov->iov_len - offset, len);
2faef52b7   Sridhar Samudrala   net: Fix memcpy_t...
91
  		if (copy_to_user(iov->iov_base + offset, kdata, copy))
0a1ec07a6   Michael S. Tsirkin   net: skb_copy_dat...
92
  			return -EFAULT;
2faef52b7   Sridhar Samudrala   net: Fix memcpy_t...
93
  		offset = 0;
0a1ec07a6   Michael S. Tsirkin   net: skb_copy_dat...
94
95
96
97
98
99
  		kdata += copy;
  		len -= copy;
  	}
  
  	return 0;
  }
9e34a5b51   Eric Dumazet   net/core: EXPORT_...
100
  EXPORT_SYMBOL(memcpy_toiovecend);
0a1ec07a6   Michael S. Tsirkin   net: skb_copy_dat...
101
102
  
  /*
ab1a2d777   Zhi Yong Wu   net, iovec: fix t...
103
   *	Copy iovec to kernel. Returns -EFAULT on error.
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
104
   */
6f26c9a75   Michael S. Tsirkin   tun: fix tun_chr_...
105
106
107
  
  int memcpy_fromiovecend(unsigned char *kdata, const struct iovec *iov,
  			int offset, int len)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
  {
  	/* Skip over the finished iovecs */
  	while (offset >= iov->iov_len) {
  		offset -= iov->iov_len;
  		iov++;
  	}
  
  	while (len > 0) {
  		u8 __user *base = iov->iov_base + offset;
  		int copy = min_t(unsigned int, len, iov->iov_len - offset);
  
  		offset = 0;
  		if (copy_from_user(kdata, base, copy))
  			return -EFAULT;
  		len -= copy;
  		kdata += copy;
  		iov++;
  	}
  
  	return 0;
  }
9e34a5b51   Eric Dumazet   net/core: EXPORT_...
129
  EXPORT_SYMBOL(memcpy_fromiovecend);
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
130
131
132
133
134
135
136
137
138
139
  
  /*
   *	And now for the all-in-one: copy and checksum from a user iovec
   *	directly to a datagram
   *	Calls to csum_partial but the last must be in 32 bit chunks
   *
   *	ip_build_xmit must ensure that when fragmenting only the last
   *	call to this function will be unaligned also.
   */
  int csum_partial_copy_fromiovecend(unsigned char *kdata, struct iovec *iov,
44bb93633   Al Viro   [NET]: Annotate c...
140
  				 int offset, unsigned int len, __wsum *csump)
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
141
  {
44bb93633   Al Viro   [NET]: Annotate c...
142
  	__wsum csum = *csump;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
  	int partial_cnt = 0, err = 0;
  
  	/* Skip over the finished iovecs */
  	while (offset >= iov->iov_len) {
  		offset -= iov->iov_len;
  		iov++;
  	}
  
  	while (len > 0) {
  		u8 __user *base = iov->iov_base + offset;
  		int copy = min_t(unsigned int, len, iov->iov_len - offset);
  
  		offset = 0;
  
  		/* There is a remnant from previous iov. */
  		if (partial_cnt) {
  			int par_len = 4 - partial_cnt;
  
  			/* iov component is too short ... */
  			if (par_len > copy) {
  				if (copy_from_user(kdata, base, copy))
  					goto out_fault;
  				kdata += copy;
  				base += copy;
  				partial_cnt += copy;
  				len -= copy;
  				iov++;
  				if (len)
  					continue;
  				*csump = csum_partial(kdata - partial_cnt,
  							 partial_cnt, csum);
  				goto out;
  			}
  			if (copy_from_user(kdata, base, par_len))
  				goto out_fault;
  			csum = csum_partial(kdata - partial_cnt, 4, csum);
  			kdata += par_len;
  			base  += par_len;
  			copy  -= par_len;
  			len   -= par_len;
  			partial_cnt = 0;
  		}
  
  		if (len > copy) {
  			partial_cnt = copy % 4;
  			if (partial_cnt) {
  				copy -= partial_cnt;
  				if (copy_from_user(kdata + copy, base + copy,
4ec93edb1   YOSHIFUJI Hideaki   [NET] CORE: Fix w...
191
  						partial_cnt))
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
192
193
194
195
196
197
198
199
200
201
202
203
204
205
  					goto out_fault;
  			}
  		}
  
  		if (copy) {
  			csum = csum_and_copy_from_user(base, kdata, copy,
  							csum, &err);
  			if (err)
  				goto out;
  		}
  		len   -= copy + partial_cnt;
  		kdata += copy + partial_cnt;
  		iov++;
  	}
4ec93edb1   YOSHIFUJI Hideaki   [NET] CORE: Fix w...
206
  	*csump = csum;
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
207
208
209
210
211
212
213
  out:
  	return err;
  
  out_fault:
  	err = -EFAULT;
  	goto out;
  }
1da177e4c   Linus Torvalds   Linux-2.6.12-rc2
214
  EXPORT_SYMBOL(csum_partial_copy_fromiovecend);
b4bf07771   Jason Wang   net: move iov_pag...
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
  
  unsigned long iov_pages(const struct iovec *iov, int offset,
  			unsigned long nr_segs)
  {
  	unsigned long seg, base;
  	int pages = 0, len, size;
  
  	while (nr_segs && (offset >= iov->iov_len)) {
  		offset -= iov->iov_len;
  		++iov;
  		--nr_segs;
  	}
  
  	for (seg = 0; seg < nr_segs; seg++) {
  		base = (unsigned long)iov[seg].iov_base + offset;
  		len = iov[seg].iov_len - offset;
  		size = ((base & ~PAGE_MASK) + len + ~PAGE_MASK) >> PAGE_SHIFT;
  		pages += size;
  		offset = 0;
  	}
  
  	return pages;
  }
  EXPORT_SYMBOL(iov_pages);