xref: /netbsd-src/sys/ufs/ext2fs/ext2fs_readwrite.c (revision 80d9064ac03cbb6a4174695f0d5b237c8766d3d0)
1 /*	$NetBSD: ext2fs_readwrite.c,v 1.65 2014/08/12 06:49:10 maxv Exp $	*/
2 
3 /*-
4  * Copyright (c) 1993
5  *	The Regents of the University of California.  All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. Neither the name of the University nor the names of its contributors
16  *    may be used to endorse or promote products derived from this software
17  *    without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  *	@(#)ufs_readwrite.c	8.8 (Berkeley) 8/4/94
32  * Modified for ext2fs by Manuel Bouyer.
33  */
34 
35 /*-
36  * Copyright (c) 1997 Manuel Bouyer.
37  *
38  * Redistribution and use in source and binary forms, with or without
39  * modification, are permitted provided that the following conditions
40  * are met:
41  * 1. Redistributions of source code must retain the above copyright
42  *    notice, this list of conditions and the following disclaimer.
43  * 2. Redistributions in binary form must reproduce the above copyright
44  *    notice, this list of conditions and the following disclaimer in the
45  *    documentation and/or other materials provided with the distribution.
46  *
47  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
48  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
49  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
50  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
51  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
52  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
53  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
54  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
55  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
56  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
57  *
58  *	@(#)ufs_readwrite.c	8.8 (Berkeley) 8/4/94
59  * Modified for ext2fs by Manuel Bouyer.
60  */
61 
62 #include <sys/cdefs.h>
63 __KERNEL_RCSID(0, "$NetBSD: ext2fs_readwrite.c,v 1.65 2014/08/12 06:49:10 maxv Exp $");
64 
65 #include <sys/param.h>
66 #include <sys/systm.h>
67 #include <sys/resourcevar.h>
68 #include <sys/kernel.h>
69 #include <sys/file.h>
70 #include <sys/stat.h>
71 #include <sys/buf.h>
72 #include <sys/proc.h>
73 #include <sys/mount.h>
74 #include <sys/vnode.h>
75 #include <sys/malloc.h>
76 #include <sys/signalvar.h>
77 #include <sys/kauth.h>
78 
79 #include <ufs/ufs/inode.h>
80 #include <ufs/ufs/ufsmount.h>
81 #include <ufs/ufs/ufs_extern.h>
82 #include <ufs/ext2fs/ext2fs.h>
83 #include <ufs/ext2fs/ext2fs_extern.h>
84 
85 /*
86  * Vnode op for reading.
87  */
88 /* ARGSUSED */
89 int
90 ext2fs_read(void *v)
91 {
92 	struct vop_read_args /* {
93 		struct vnode *a_vp;
94 		struct uio *a_uio;
95 		int a_ioflag;
96 		kauth_cred_t a_cred;
97 	} */ *ap = v;
98 	struct vnode *vp;
99 	struct inode *ip;
100 	struct uio *uio;
101 	struct m_ext2fs *fs;
102 	struct buf *bp;
103 	struct ufsmount *ump;
104 	vsize_t bytelen;
105 	daddr_t lbn, nextlbn;
106 	off_t bytesinfile;
107 	long size, xfersize, blkoffset;
108 	int error;
109 
110 	vp = ap->a_vp;
111 	ip = VTOI(vp);
112 	ump = ip->i_ump;
113 	uio = ap->a_uio;
114 	error = 0;
115 
116 #ifdef DIAGNOSTIC
117 	if (uio->uio_rw != UIO_READ)
118 		panic("%s: mode", "ext2fs_read");
119 
120 	if (vp->v_type == VLNK) {
121 		if (ext2fs_size(ip) < ump->um_maxsymlinklen ||
122 		    (ump->um_maxsymlinklen == 0 && ext2fs_nblock(ip) == 0))
123 			panic("%s: short symlink", "ext2fs_read");
124 	} else if (vp->v_type != VREG && vp->v_type != VDIR)
125 		panic("%s: type %d", "ext2fs_read", vp->v_type);
126 #endif
127 	fs = ip->i_e2fs;
128 	if ((uint64_t)uio->uio_offset > ump->um_maxfilesize)
129 		return (EFBIG);
130 	if (uio->uio_resid == 0)
131 		return (0);
132 	if (uio->uio_offset >= ext2fs_size(ip))
133 		goto out;
134 
135 	if (vp->v_type == VREG) {
136 		const int advice = IO_ADV_DECODE(ap->a_ioflag);
137 
138 		while (uio->uio_resid > 0) {
139 			bytelen = MIN(ext2fs_size(ip) - uio->uio_offset,
140 			    uio->uio_resid);
141 			if (bytelen == 0)
142 				break;
143 
144 			error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
145 			    UBC_READ | UBC_PARTIALOK | UBC_UNMAP_FLAG(vp));
146 			if (error)
147 				break;
148 		}
149 		goto out;
150 	}
151 
152 	for (error = 0, bp = NULL; uio->uio_resid > 0; bp = NULL) {
153 		bytesinfile = ext2fs_size(ip) - uio->uio_offset;
154 		if (bytesinfile <= 0)
155 			break;
156 		lbn = ext2_lblkno(fs, uio->uio_offset);
157 		nextlbn = lbn + 1;
158 		size = fs->e2fs_bsize;
159 		blkoffset = ext2_blkoff(fs, uio->uio_offset);
160 		xfersize = fs->e2fs_bsize - blkoffset;
161 		if (uio->uio_resid < xfersize)
162 			xfersize = uio->uio_resid;
163 		if (bytesinfile < xfersize)
164 			xfersize = bytesinfile;
165 
166 		if (ext2_lblktosize(fs, nextlbn) >= ext2fs_size(ip))
167 			error = bread(vp, lbn, size, NOCRED, 0, &bp);
168 		else {
169 			int nextsize = fs->e2fs_bsize;
170 			error = breadn(vp, lbn,
171 				size, &nextlbn, &nextsize, 1, NOCRED, 0, &bp);
172 		}
173 		if (error)
174 			break;
175 
176 		/*
177 		 * We should only get non-zero b_resid when an I/O error
178 		 * has occurred, which should cause us to break above.
179 		 * However, if the short read did not cause an error,
180 		 * then we want to ensure that we do not uiomove bad
181 		 * or uninitialized data.
182 		 */
183 		size -= bp->b_resid;
184 		if (size < xfersize) {
185 			if (size == 0)
186 				break;
187 			xfersize = size;
188 		}
189 		error = uiomove((char *)bp->b_data + blkoffset, xfersize, uio);
190 		if (error)
191 			break;
192 		brelse(bp, 0);
193 	}
194 	if (bp != NULL)
195 		brelse(bp, 0);
196 
197 out:
198 	if (!(vp->v_mount->mnt_flag & MNT_NOATIME)) {
199 		ip->i_flag |= IN_ACCESS;
200 		if ((ap->a_ioflag & IO_SYNC) == IO_SYNC)
201 			error = ext2fs_update(vp, NULL, NULL, UPDATE_WAIT);
202 	}
203 	return (error);
204 }
205 
206 /*
207  * Vnode op for writing.
208  */
209 int
210 ext2fs_write(void *v)
211 {
212 	struct vop_write_args /* {
213 		struct vnode *a_vp;
214 		struct uio *a_uio;
215 		int a_ioflag;
216 		kauth_cred_t a_cred;
217 	} */ *ap = v;
218 	struct vnode *vp;
219 	struct uio *uio;
220 	struct inode *ip;
221 	struct m_ext2fs *fs;
222 	struct buf *bp;
223 	struct ufsmount *ump;
224 	daddr_t lbn;
225 	off_t osize;
226 	int blkoffset, error, flags, ioflag, resid, xfersize;
227 	vsize_t bytelen;
228 	off_t oldoff = 0;					/* XXX */
229 	bool async;
230 	int extended = 0;
231 	int advice;
232 
233 	ioflag = ap->a_ioflag;
234 	advice = IO_ADV_DECODE(ioflag);
235 	uio = ap->a_uio;
236 	vp = ap->a_vp;
237 	ip = VTOI(vp);
238 	ump = ip->i_ump;
239 	error = 0;
240 
241 #ifdef DIAGNOSTIC
242 	if (uio->uio_rw != UIO_WRITE)
243 		panic("%s: mode", "ext2fs_write");
244 #endif
245 
246 	switch (vp->v_type) {
247 	case VREG:
248 		if (ioflag & IO_APPEND)
249 			uio->uio_offset = ext2fs_size(ip);
250 		if ((ip->i_e2fs_flags & EXT2_APPEND) &&
251 		    uio->uio_offset != ext2fs_size(ip))
252 			return (EPERM);
253 		/* FALLTHROUGH */
254 	case VLNK:
255 		break;
256 	case VDIR:
257 		if ((ioflag & IO_SYNC) == 0)
258 			panic("%s: nonsync dir write", "ext2fs_write");
259 		break;
260 	default:
261 		panic("%s: type", "ext2fs_write");
262 	}
263 
264 	fs = ip->i_e2fs;
265 	if (uio->uio_offset < 0 ||
266 	    (uint64_t)uio->uio_offset + uio->uio_resid > ump->um_maxfilesize)
267 		return (EFBIG);
268 	if (uio->uio_resid == 0)
269 		return (0);
270 
271 	async = vp->v_mount->mnt_flag & MNT_ASYNC;
272 	resid = uio->uio_resid;
273 	osize = ext2fs_size(ip);
274 
275 	if (vp->v_type == VREG) {
276 		while (uio->uio_resid > 0) {
277 			oldoff = uio->uio_offset;
278 			blkoffset = ext2_blkoff(fs, uio->uio_offset);
279 			bytelen = MIN(fs->e2fs_bsize - blkoffset,
280 			    uio->uio_resid);
281 
282 			if (vp->v_size < oldoff + bytelen) {
283 				uvm_vnp_setwritesize(vp, oldoff + bytelen);
284 			}
285 			error = ufs_balloc_range(vp, uio->uio_offset,
286 			    bytelen, ap->a_cred, 0);
287 			if (error)
288 				break;
289 			error = ubc_uiomove(&vp->v_uobj, uio, bytelen, advice,
290 			    UBC_WRITE | UBC_UNMAP_FLAG(vp));
291 			if (error)
292 				break;
293 
294 			/*
295 			 * update UVM's notion of the size now that we've
296 			 * copied the data into the vnode's pages.
297 			 */
298 
299 			if (vp->v_size < uio->uio_offset) {
300 				uvm_vnp_setsize(vp, uio->uio_offset);
301 				extended = 1;
302 			}
303 
304 			/*
305 			 * flush what we just wrote if necessary.
306 			 * XXXUBC simplistic async flushing.
307 			 */
308 
309 			if (!async && oldoff >> 16 != uio->uio_offset >> 16) {
310 				mutex_enter(vp->v_interlock);
311 				error = VOP_PUTPAGES(vp, (oldoff >> 16) << 16,
312 				    (uio->uio_offset >> 16) << 16,
313 				    PGO_CLEANIT | PGO_LAZY);
314 			}
315 		}
316 		if (error == 0 && ioflag & IO_SYNC) {
317 			mutex_enter(vp->v_interlock);
318 			error = VOP_PUTPAGES(vp, trunc_page(oldoff),
319 			    round_page(ext2_blkroundup(fs, uio->uio_offset)),
320 			    PGO_CLEANIT | PGO_SYNCIO);
321 		}
322 
323 		goto out;
324 	}
325 
326 	flags = ioflag & IO_SYNC ? B_SYNC : 0;
327 	for (error = 0; uio->uio_resid > 0;) {
328 		lbn = ext2_lblkno(fs, uio->uio_offset);
329 		blkoffset = ext2_blkoff(fs, uio->uio_offset);
330 		xfersize = MIN(fs->e2fs_bsize - blkoffset, uio->uio_resid);
331 		if (xfersize < fs->e2fs_bsize)
332 			flags |= B_CLRBUF;
333 		else
334 			flags &= ~B_CLRBUF;
335 		error = ext2fs_balloc(ip,
336 		    lbn, blkoffset + xfersize, ap->a_cred, &bp, flags);
337 		if (error)
338 			break;
339 		if (ext2fs_size(ip) < uio->uio_offset + xfersize) {
340 			error = ext2fs_setsize(ip, uio->uio_offset + xfersize);
341 			if (error)
342 				break;
343 		}
344 		error = uiomove((char *)bp->b_data + blkoffset, xfersize, uio);
345 
346 		/*
347 		 * update UVM's notion of the size now that we've
348 		 * copied the data into the vnode's pages.
349 		 */
350 
351 		if (vp->v_size < uio->uio_offset) {
352 			uvm_vnp_setsize(vp, uio->uio_offset);
353 			extended = 1;
354 		}
355 
356 		if (ioflag & IO_SYNC)
357 			(void)bwrite(bp);
358 		else if (xfersize + blkoffset == fs->e2fs_bsize)
359 			bawrite(bp);
360 		else
361 			bdwrite(bp);
362 		if (error || xfersize == 0)
363 			break;
364 	}
365 
366 	/*
367 	 * If we successfully wrote any data, and we are not the superuser
368 	 * we clear the setuid and setgid bits as a precaution against
369 	 * tampering.
370 	 */
371 
372 out:
373 	ip->i_flag |= IN_CHANGE | IN_UPDATE;
374 	if (vp->v_mount->mnt_flag & MNT_RELATIME)
375 		ip->i_flag |= IN_ACCESS;
376 	if (resid > uio->uio_resid && ap->a_cred) {
377 		if (ip->i_e2fs_mode & ISUID) {
378 			if (kauth_authorize_vnode(ap->a_cred,
379 			    KAUTH_VNODE_RETAIN_SUID, vp, NULL, EPERM) != 0)
380 				ip->i_e2fs_mode &= ISUID;
381 		}
382 
383 		if (ip->i_e2fs_mode & ISGID) {
384 			if (kauth_authorize_vnode(ap->a_cred,
385 			    KAUTH_VNODE_RETAIN_SGID, vp, NULL, EPERM) != 0)
386 				ip->i_e2fs_mode &= ~ISGID;
387 		}
388 	}
389 	if (resid > uio->uio_resid)
390 		VN_KNOTE(vp, NOTE_WRITE | (extended ? NOTE_EXTEND : 0));
391 	if (error) {
392 		(void) ext2fs_truncate(vp, osize, ioflag & IO_SYNC, ap->a_cred);
393 		uio->uio_offset -= resid - uio->uio_resid;
394 		uio->uio_resid = resid;
395 	} else if (resid > uio->uio_resid && (ioflag & IO_SYNC) == IO_SYNC)
396 		error = ext2fs_update(vp, NULL, NULL, UPDATE_WAIT);
397 	KASSERT(vp->v_size == ext2fs_size(ip));
398 	return (error);
399 }
400