]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2002 Apple Computer, Inc. All rights reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * The contents of this file constitute Original Code as defined in and | |
7 | * are subject to the Apple Public Source License Version 1.1 (the | |
8 | * "License"). You may not use this file except in compliance with the | |
9 | * License. Please obtain a copy of the License at | |
10 | * http://www.apple.com/publicsource and read it before using this file. | |
11 | * | |
12 | * This Original Code and all software distributed under the License are | |
13 | * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
14 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
15 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
16 | * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the | |
17 | * License for the specific language governing rights and limitations | |
18 | * under the License. | |
19 | * | |
20 | * @APPLE_LICENSE_HEADER_END@ | |
21 | */ | |
22 | /* | |
23 | * Copyright (c) 1982, 1986, 1990, 1993, 1995 | |
24 | * The Regents of the University of California. All rights reserved. | |
25 | * | |
26 | * This code is derived from software contributed to Berkeley by | |
27 | * Robert Elz at The University of Melbourne. | |
28 | * | |
29 | * Redistribution and use in source and binary forms, with or without | |
30 | * modification, are permitted provided that the following conditions | |
31 | * are met: | |
32 | * 1. Redistributions of source code must retain the above copyright | |
33 | * notice, this list of conditions and the following disclaimer. | |
34 | * 2. Redistributions in binary form must reproduce the above copyright | |
35 | * notice, this list of conditions and the following disclaimer in the | |
36 | * documentation and/or other materials provided with the distribution. | |
37 | * 3. All advertising materials mentioning features or use of this software | |
38 | * must display the following acknowledgement: | |
39 | * This product includes software developed by the University of | |
40 | * California, Berkeley and its contributors. | |
41 | * 4. Neither the name of the University nor the names of its contributors | |
42 | * may be used to endorse or promote products derived from this software | |
43 | * without specific prior written permission. | |
44 | * | |
45 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | |
46 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
47 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | |
48 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | |
49 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
50 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | |
51 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | |
52 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | |
53 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | |
54 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | |
55 | * SUCH DAMAGE. | |
56 | * | |
57 | * @(#)vfs_quota.c | |
58 | * derived from @(#)ufs_quota.c 8.5 (Berkeley) 5/20/95 | |
59 | */ | |
60 | ||
61 | #include <sys/param.h> | |
62 | #include <sys/kernel.h> | |
63 | #include <sys/systm.h> | |
64 | #include <sys/malloc.h> | |
65 | #include <sys/file.h> | |
66 | #include <sys/proc.h> | |
67 | #include <sys/vnode.h> | |
68 | #include <sys/mount.h> | |
69 | #include <sys/quota.h> | |
70 | ||
71 | ||
72 | static u_int32_t quotamagic[MAXQUOTAS] = INITQMAGICS; | |
73 | ||
74 | ||
75 | /* | |
76 | * Code pertaining to management of the in-core dquot data structures. | |
77 | */ | |
78 | #define DQHASH(dqvp, id) \ | |
79 | (&dqhashtbl[((((int)(dqvp)) >> 8) + id) & dqhash]) | |
80 | LIST_HEAD(dqhash, dquot) *dqhashtbl; | |
81 | u_long dqhash; | |
82 | ||
83 | /* | |
84 | * Dquot free list. | |
85 | */ | |
86 | #define DQUOTINC 5 /* minimum free dquots desired */ | |
87 | TAILQ_HEAD(dqfreelist, dquot) dqfreelist; | |
88 | long numdquot, desireddquot = DQUOTINC; | |
89 | ||
90 | ||
91 | static int dqlookup(struct quotafile *, u_long, struct dqblk *, u_int32_t *); | |
92 | ||
93 | ||
94 | /* | |
95 | * Initialize the quota system. | |
96 | */ | |
97 | void | |
98 | dqinit() | |
99 | { | |
100 | ||
101 | dqhashtbl = hashinit(desiredvnodes, M_DQUOT, &dqhash); | |
102 | TAILQ_INIT(&dqfreelist); | |
103 | } | |
104 | ||
105 | ||
106 | /* | |
107 | * Initialize a quota file | |
108 | */ | |
109 | int | |
110 | dqfileopen(qfp, type) | |
111 | struct quotafile *qfp; | |
112 | int type; | |
113 | { | |
114 | struct dqfilehdr header; | |
115 | struct vattr vattr; | |
116 | struct iovec aiov; | |
117 | struct uio auio; | |
118 | int error; | |
119 | ||
120 | /* Obtain the file size */ | |
121 | error = VOP_GETATTR(qfp->qf_vp, &vattr, qfp->qf_cred, current_proc()); | |
122 | if (error) | |
123 | return (error); | |
124 | ||
125 | /* Read the file header */ | |
126 | auio.uio_iov = &aiov; | |
127 | auio.uio_iovcnt = 1; | |
128 | aiov.iov_base = (caddr_t)&header; | |
129 | aiov.iov_len = sizeof (header); | |
130 | auio.uio_resid = sizeof (header); | |
131 | auio.uio_offset = (off_t)(0); | |
132 | auio.uio_segflg = UIO_SYSSPACE; | |
133 | auio.uio_rw = UIO_READ; | |
134 | auio.uio_procp = (struct proc *)0; | |
135 | error = VOP_READ(qfp->qf_vp, &auio, 0, qfp->qf_cred); | |
136 | if (error) | |
137 | return (error); | |
138 | else if (auio.uio_resid) | |
139 | return (EINVAL); | |
140 | ||
141 | /* Sanity check the quota file header. */ | |
142 | if ((header.dqh_magic != quotamagic[type]) || | |
143 | (header.dqh_version > QF_VERSION) || | |
144 | (!powerof2(header.dqh_maxentries)) || | |
145 | (header.dqh_maxentries > (vattr.va_size / sizeof(struct dqblk)))) | |
146 | return (EINVAL); | |
147 | ||
148 | /* Set up the time limits for this quota. */ | |
149 | if (header.dqh_btime > 0) | |
150 | qfp->qf_btime = header.dqh_btime; | |
151 | else | |
152 | qfp->qf_btime = MAX_DQ_TIME; | |
153 | if (header.dqh_itime > 0) | |
154 | qfp->qf_itime = header.dqh_itime; | |
155 | else | |
156 | qfp->qf_itime = MAX_IQ_TIME; | |
157 | ||
158 | /* Calculate the hash table constants. */ | |
159 | qfp->qf_maxentries = header.dqh_maxentries; | |
160 | qfp->qf_entrycnt = header.dqh_entrycnt; | |
161 | qfp->qf_shift = dqhashshift(header.dqh_maxentries); | |
162 | ||
163 | return (0); | |
164 | } | |
165 | ||
166 | /* | |
167 | * Close down a quota file | |
168 | */ | |
169 | void | |
170 | dqfileclose(qfp, type) | |
171 | struct quotafile *qfp; | |
172 | int type; | |
173 | { | |
174 | struct dqfilehdr header; | |
175 | struct iovec aiov; | |
176 | struct uio auio; | |
177 | ||
178 | auio.uio_iov = &aiov; | |
179 | auio.uio_iovcnt = 1; | |
180 | aiov.iov_base = (caddr_t)&header; | |
181 | aiov.iov_len = sizeof (header); | |
182 | auio.uio_resid = sizeof (header); | |
183 | auio.uio_offset = (off_t)(0); | |
184 | auio.uio_segflg = UIO_SYSSPACE; | |
185 | auio.uio_rw = UIO_READ; | |
186 | auio.uio_procp = (struct proc *)0; | |
187 | if (VOP_READ(qfp->qf_vp, &auio, 0, qfp->qf_cred) == 0) { | |
188 | header.dqh_entrycnt = qfp->qf_entrycnt; | |
189 | ||
190 | auio.uio_iov = &aiov; | |
191 | auio.uio_iovcnt = 1; | |
192 | aiov.iov_base = (caddr_t)&header; | |
193 | aiov.iov_len = sizeof (header); | |
194 | auio.uio_resid = sizeof (header); | |
195 | auio.uio_offset = (off_t)(0); | |
196 | auio.uio_segflg = UIO_SYSSPACE; | |
197 | auio.uio_rw = UIO_WRITE; | |
198 | auio.uio_procp = (struct proc *)0; | |
199 | (void) VOP_WRITE(qfp->qf_vp, &auio, 0, qfp->qf_cred); | |
200 | } | |
201 | } | |
202 | ||
203 | ||
204 | /* | |
205 | * Obtain a dquot structure for the specified identifier and quota file | |
206 | * reading the information from the file if necessary. | |
207 | */ | |
208 | int | |
209 | dqget(vp, id, qfp, type, dqp) | |
210 | struct vnode *vp; | |
211 | u_long id; | |
212 | struct quotafile *qfp; | |
213 | register int type; | |
214 | struct dquot **dqp; | |
215 | { | |
216 | struct proc *p = current_proc(); /* XXX */ | |
217 | struct dquot *dq; | |
218 | struct dqhash *dqh; | |
219 | struct vnode *dqvp; | |
220 | int error = 0; | |
221 | ||
222 | dqvp = qfp->qf_vp; | |
223 | if (id == 0 || dqvp == NULLVP || (qfp->qf_qflags & QTF_CLOSING)) { | |
224 | *dqp = NODQUOT; | |
225 | return (EINVAL); | |
226 | } | |
227 | /* | |
228 | * Check the cache first. | |
229 | */ | |
230 | dqh = DQHASH(dqvp, id); | |
231 | for (dq = dqh->lh_first; dq; dq = dq->dq_hash.le_next) { | |
232 | if (dq->dq_id != id || | |
233 | dq->dq_qfile->qf_vp != dqvp) | |
234 | continue; | |
235 | /* | |
236 | * Cache hit with no references. Take | |
237 | * the structure off the free list. | |
238 | */ | |
239 | if (dq->dq_cnt == 0) | |
240 | TAILQ_REMOVE(&dqfreelist, dq, dq_freelist); | |
241 | DQREF(dq); | |
242 | *dqp = dq; | |
243 | return (0); | |
244 | } | |
245 | /* | |
246 | * Not in cache, allocate a new one. | |
247 | */ | |
248 | if (dqfreelist.tqh_first == NODQUOT && | |
249 | numdquot < MAXQUOTAS * desiredvnodes) | |
250 | desireddquot += DQUOTINC; | |
251 | if (numdquot < desireddquot) { | |
252 | dq = (struct dquot *)_MALLOC(sizeof *dq, M_DQUOT, M_WAITOK); | |
253 | bzero((char *)dq, sizeof *dq); | |
254 | numdquot++; | |
255 | } else { | |
256 | if ((dq = dqfreelist.tqh_first) == NULL) { | |
257 | tablefull("dquot"); | |
258 | *dqp = NODQUOT; | |
259 | return (EUSERS); | |
260 | } | |
261 | if (dq->dq_cnt || (dq->dq_flags & DQ_MOD)) | |
262 | panic("free dquot isn't"); | |
263 | TAILQ_REMOVE(&dqfreelist, dq, dq_freelist); | |
264 | LIST_REMOVE(dq, dq_hash); | |
265 | } | |
266 | /* | |
267 | * Initialize the contents of the dquot structure. | |
268 | */ | |
269 | if (vp != dqvp) | |
270 | vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY, p); | |
271 | LIST_INSERT_HEAD(dqh, dq, dq_hash); | |
272 | DQREF(dq); | |
273 | dq->dq_flags = DQ_LOCK; | |
274 | dq->dq_id = id; | |
275 | dq->dq_qfile = qfp; | |
276 | dq->dq_type = type; | |
277 | error = dqlookup(qfp, id, &dq->dq_dqb, &dq->dq_index); | |
278 | ||
279 | if (vp != dqvp) | |
280 | VOP_UNLOCK(dqvp, 0, p); | |
281 | if (dq->dq_flags & DQ_WANT) | |
282 | wakeup((caddr_t)dq); | |
283 | dq->dq_flags = 0; | |
284 | /* | |
285 | * I/O error in reading quota file, release | |
286 | * quota structure and reflect problem to caller. | |
287 | */ | |
288 | if (error) { | |
289 | LIST_REMOVE(dq, dq_hash); | |
290 | dqrele(vp, dq); | |
291 | *dqp = NODQUOT; | |
292 | return (error); | |
293 | } | |
294 | /* | |
295 | * Check for no limit to enforce. | |
296 | * Initialize time values if necessary. | |
297 | */ | |
298 | if (dq->dq_isoftlimit == 0 && dq->dq_bsoftlimit == 0 && | |
299 | dq->dq_ihardlimit == 0 && dq->dq_bhardlimit == 0) | |
300 | dq->dq_flags |= DQ_FAKE; | |
301 | if (dq->dq_id != 0) { | |
302 | if (dq->dq_btime == 0) | |
303 | dq->dq_btime = time.tv_sec + qfp->qf_btime; | |
304 | if (dq->dq_itime == 0) | |
305 | dq->dq_itime = time.tv_sec + qfp->qf_itime; | |
306 | } | |
307 | *dqp = dq; | |
308 | return (0); | |
309 | } | |
310 | ||
311 | /* | |
312 | * Lookup a dqblk structure for the specified identifier and | |
313 | * quota file. If there is no enetry for this identifier then | |
314 | * one is inserted. The actual hash table index is returned. | |
315 | */ | |
316 | static int | |
317 | dqlookup(qfp, id, dqb, index) | |
318 | struct quotafile *qfp; | |
319 | u_long id; | |
320 | struct dqblk *dqb; | |
321 | u_int32_t *index; | |
322 | { | |
323 | struct vnode *dqvp; | |
324 | struct ucred *cred; | |
325 | struct iovec aiov; | |
326 | struct uio auio; | |
327 | int i, skip, last; | |
328 | u_long mask; | |
329 | int error = 0; | |
330 | ||
331 | if (id == 0) | |
332 | return (EINVAL); | |
333 | dqvp = qfp->qf_vp; | |
334 | cred = qfp->qf_cred; | |
335 | ||
336 | auio.uio_iov = &aiov; | |
337 | auio.uio_iovcnt = 1; | |
338 | auio.uio_segflg = UIO_SYSSPACE; | |
339 | auio.uio_procp = (struct proc *)0; | |
340 | ||
341 | mask = qfp->qf_maxentries - 1; | |
342 | i = dqhash1(id, qfp->qf_shift, mask); | |
343 | skip = dqhash2(id, mask); | |
344 | ||
345 | for (last = (i + (qfp->qf_maxentries-1) * skip) & mask; | |
346 | i != last; | |
347 | i = (i + skip) & mask) { | |
348 | ||
349 | aiov.iov_base = (caddr_t)dqb; | |
350 | aiov.iov_len = sizeof (struct dqblk); | |
351 | auio.uio_resid = sizeof (struct dqblk); | |
352 | auio.uio_offset = (off_t)dqoffset(i); | |
353 | auio.uio_rw = UIO_READ; | |
354 | error = VOP_READ(dqvp, &auio, 0, cred); | |
355 | if (error) { | |
356 | printf("dqlookup: error %d looking up id %d at index %d\n", error, id, i); | |
357 | break; | |
358 | } else if (auio.uio_resid) { | |
359 | error = EIO; | |
360 | printf("dqlookup: error looking up id %d at index %d\n", id, i); | |
361 | break; | |
362 | } | |
363 | /* | |
364 | * An empty entry means there is no entry | |
365 | * with that id. In this case a new dqb | |
366 | * record will be inserted. | |
367 | */ | |
368 | if (dqb->dqb_id == 0) { | |
369 | bzero(dqb, sizeof(struct dqblk)); | |
370 | dqb->dqb_id = id; | |
371 | /* | |
372 | * Write back to reserve entry for this id | |
373 | */ | |
374 | aiov.iov_base = (caddr_t)dqb; | |
375 | aiov.iov_len = sizeof (struct dqblk); | |
376 | auio.uio_resid = sizeof (struct dqblk); | |
377 | auio.uio_offset = (off_t)dqoffset(i); | |
378 | auio.uio_rw = UIO_WRITE; | |
379 | error = VOP_WRITE(dqvp, &auio, 0, cred); | |
380 | if (auio.uio_resid && error == 0) | |
381 | error = EIO; | |
382 | if (error == 0) | |
383 | ++qfp->qf_entrycnt; | |
384 | break; | |
385 | } | |
386 | /* An id match means an entry was found. */ | |
387 | if (dqb->dqb_id == id) | |
388 | break; | |
389 | } | |
390 | ||
391 | *index = i; /* remember index so we don't have to recompute it later */ | |
392 | return (error); | |
393 | } | |
394 | ||
395 | /* | |
396 | * Obtain a reference to a dquot. | |
397 | */ | |
398 | void | |
399 | dqref(dq) | |
400 | struct dquot *dq; | |
401 | { | |
402 | ||
403 | dq->dq_cnt++; | |
404 | } | |
405 | ||
406 | /* | |
407 | * Release a reference to a dquot. | |
408 | */ | |
409 | void | |
410 | dqrele(vp, dq) | |
411 | struct vnode *vp; | |
412 | register struct dquot *dq; | |
413 | { | |
414 | ||
415 | if (dq == NODQUOT) | |
416 | return; | |
417 | if (dq->dq_cnt > 1) { | |
418 | dq->dq_cnt--; | |
419 | return; | |
420 | } | |
421 | if (dq->dq_flags & DQ_MOD) | |
422 | (void) dqsync(vp, dq); | |
423 | if (--dq->dq_cnt > 0) | |
424 | return; | |
425 | TAILQ_INSERT_TAIL(&dqfreelist, dq, dq_freelist); | |
426 | } | |
427 | ||
428 | /* | |
429 | * Update the disk quota in the quota file. | |
430 | */ | |
431 | int | |
432 | dqsync(vp, dq) | |
433 | struct vnode *vp; | |
434 | struct dquot *dq; | |
435 | { | |
436 | struct proc *p = current_proc(); /* XXX */ | |
437 | struct vnode *dqvp; | |
438 | struct iovec aiov; | |
439 | struct uio auio; | |
440 | int error; | |
441 | ||
442 | if (dq == NODQUOT) | |
443 | panic("dqsync: dquot"); | |
444 | if ((dq->dq_flags & DQ_MOD) == 0) | |
445 | return (0); | |
446 | if (dq->dq_id == 0) | |
447 | return(0); | |
448 | if ((dqvp = dq->dq_qfile->qf_vp) == NULLVP) | |
449 | panic("dqsync: file"); | |
450 | if (vp != dqvp) | |
451 | vn_lock(dqvp, LK_EXCLUSIVE | LK_RETRY, p); | |
452 | while (dq->dq_flags & DQ_LOCK) { | |
453 | dq->dq_flags |= DQ_WANT; | |
454 | sleep((caddr_t)dq, PINOD+2); | |
455 | if ((dq->dq_flags & DQ_MOD) == 0) { | |
456 | if (vp != dqvp) | |
457 | VOP_UNLOCK(dqvp, 0, p); | |
458 | return (0); | |
459 | } | |
460 | } | |
461 | dq->dq_flags |= DQ_LOCK; | |
462 | auio.uio_iov = &aiov; | |
463 | auio.uio_iovcnt = 1; | |
464 | aiov.iov_base = (caddr_t)&dq->dq_dqb; | |
465 | aiov.iov_len = sizeof (struct dqblk); | |
466 | auio.uio_resid = sizeof (struct dqblk); | |
467 | auio.uio_offset = (off_t)dqoffset(dq->dq_index); | |
468 | auio.uio_segflg = UIO_SYSSPACE; | |
469 | auio.uio_rw = UIO_WRITE; | |
470 | auio.uio_procp = (struct proc *)0; | |
471 | error = VOP_WRITE(dqvp, &auio, 0, dq->dq_qfile->qf_cred); | |
472 | if (auio.uio_resid && error == 0) | |
473 | error = EIO; | |
474 | if (dq->dq_flags & DQ_WANT) | |
475 | wakeup((caddr_t)dq); | |
476 | dq->dq_flags &= ~(DQ_MOD|DQ_LOCK|DQ_WANT); | |
477 | if (vp != dqvp) | |
478 | VOP_UNLOCK(dqvp, 0, p); | |
479 | return (error); | |
480 | } | |
481 | ||
482 | /* | |
483 | * Flush all entries from the cache for a particular vnode. | |
484 | */ | |
485 | void | |
486 | dqflush(vp) | |
487 | register struct vnode *vp; | |
488 | { | |
489 | register struct dquot *dq, *nextdq; | |
490 | struct dqhash *dqh; | |
491 | ||
492 | /* | |
493 | * Move all dquot's that used to refer to this quota | |
494 | * file off their hash chains (they will eventually | |
495 | * fall off the head of the free list and be re-used). | |
496 | */ | |
497 | for (dqh = &dqhashtbl[dqhash]; dqh >= dqhashtbl; dqh--) { | |
498 | for (dq = dqh->lh_first; dq; dq = nextdq) { | |
499 | nextdq = dq->dq_hash.le_next; | |
500 | if (dq->dq_qfile->qf_vp != vp) | |
501 | continue; | |
502 | if (dq->dq_cnt) | |
503 | panic("dqflush: stray dquot"); | |
504 | LIST_REMOVE(dq, dq_hash); | |
505 | dq->dq_qfile = (struct quotafile *)0; | |
506 | } | |
507 | } | |
508 | } |