Module Name: src Committed By: dholland Date: Sun Jan 29 07:21:00 UTC 2012
Modified Files: src/sys/ufs/ufs: ufs_quota2.c Log Message: Clean up quota2 cursoring, as promised earlier. To generate a diff of this commit: cvs rdiff -u -r1.29 -r1.30 src/sys/ufs/ufs/ufs_quota2.c Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files.
Modified files: Index: src/sys/ufs/ufs/ufs_quota2.c diff -u src/sys/ufs/ufs/ufs_quota2.c:1.29 src/sys/ufs/ufs/ufs_quota2.c:1.30 --- src/sys/ufs/ufs/ufs_quota2.c:1.29 Sun Jan 29 07:20:27 2012 +++ src/sys/ufs/ufs/ufs_quota2.c Sun Jan 29 07:21:00 2012 @@ -1,4 +1,4 @@ -/* $NetBSD: ufs_quota2.c,v 1.29 2012/01/29 07:20:27 dholland Exp $ */ +/* $NetBSD: ufs_quota2.c,v 1.30 2012/01/29 07:21:00 dholland Exp $ */ /*- * Copyright (c) 2010 Manuel Bouyer * All rights reserved. @@ -26,13 +26,12 @@ */ #include <sys/cdefs.h> -__KERNEL_RCSID(0, "$NetBSD: ufs_quota2.c,v 1.29 2012/01/29 07:20:27 dholland Exp $"); +__KERNEL_RCSID(0, "$NetBSD: ufs_quota2.c,v 1.30 2012/01/29 07:21:00 dholland Exp $"); #include <sys/buf.h> #include <sys/param.h> #include <sys/kernel.h> #include <sys/systm.h> -#include <sys/malloc.h> #include <sys/namei.h> #include <sys/file.h> #include <sys/proc.h> @@ -794,17 +793,16 @@ out_dq: } static int -quota2_result_add_q2e(struct ufsmount *ump, int idtype, - int id, struct quotakey *keys, struct quotaval *vals, unsigned pos, - int skipfirst, int skiplast) +quota2_fetch_q2e(struct ufsmount *ump, const struct quotakey *qk, + struct quota2_entry *ret) { struct dquot *dq; int error; - struct quota2_entry *q2ep, q2e; - struct buf *bp; + struct quota2_entry *q2ep; + struct buf *bp; const int needswap = UFS_MPNEEDSWAP(ump); - error = dqget(NULLVP, id, ump, idtype, &dq); + error = dqget(NULLVP, qk->qk_id, ump, qk->qk_idtype, &dq); if (error) return error; @@ -814,39 +812,23 @@ quota2_result_add_q2e(struct ufsmount *u dqrele(NULLVP, dq); return ENOENT; } - error = getq2e(ump, idtype, dq->dq2_lblkno, dq->dq2_blkoff, + error = getq2e(ump, qk->qk_idtype, dq->dq2_lblkno, dq->dq2_blkoff, &bp, &q2ep, 0); if (error) { mutex_exit(&dq->dq_interlock); dqrele(NULLVP, dq); return error; } - quota2_ufs_rwq2e(q2ep, &q2e, needswap); + quota2_ufs_rwq2e(q2ep, ret, needswap); brelse(bp, 0); mutex_exit(&dq->dq_interlock); dqrele(NULLVP, dq); - if (skipfirst == 0) { - keys[pos].qk_idtype = idtype; - keys[pos].qk_objtype = QUOTA_OBJTYPE_BLOCKS; - q2e_to_quotaval(&q2e, 0, &keys[pos].qk_id, - QL_BLOCK, &vals[pos]); - pos++; - } - - if (skiplast == 0) { - keys[pos].qk_idtype = idtype; - keys[pos].qk_objtype = QUOTA_OBJTYPE_FILES; - q2e_to_quotaval(&q2e, 0, &keys[pos].qk_id, - QL_FILE, &vals[pos]); - pos++; - } - return 0; } static int -quota2_fetch_q2e(struct ufsmount *ump, const struct quotakey *qk, +quota2_fetch_quotaval(struct ufsmount *ump, const struct quotakey *qk, struct quotaval *ret) { struct dquot *dq; @@ -923,11 +905,17 @@ quota2_handle_cmd_get(struct ufsmount *u qk->qk_objtype, ret); (void)id2; } else - error = quota2_fetch_q2e(ump, qk, ret); + error = quota2_fetch_quotaval(ump, qk, ret); return error; } +/* + * Cursor structure we used. + * + * This will get stored in userland between calls so we must not assume + * it isn't arbitrarily corrupted. + */ struct ufsq2_cursor { uint32_t q2c_magic; /* magic number */ int q2c_hashsize; /* size of hash table at last go */ @@ -940,10 +928,54 @@ struct ufsq2_cursor { int q2c_blocks_done; /* true if we've returned the blocks value */ }; +/* + * State of a single cursorget call, or at least the part of it that + * needs to be passed around. + */ +struct q2cursor_state { + /* data return pointers */ + struct quotakey *keys; + struct quotaval *vals; + + /* key/value counters */ + unsigned maxkeyvals; + unsigned numkeys; /* number of keys assigned */ + + /* ID to key/value conversion state */ + int skipfirst; /* if true skip first key/value */ + int skiplast; /* if true skip last key/value */ + + /* ID counters */ + unsigned maxids; /* maximum number of IDs to handle */ + unsigned numids; /* number of IDs handled */ +}; + +/* + * Additional structure for getids callback. + */ +struct q2cursor_getids { + struct q2cursor_state *state; + int idtype; + unsigned skip; /* number of ids to skip over */ + unsigned new_skip; /* number of ids to skip over next time */ + unsigned skipped; /* number skipped so far */ +}; + +/* + * Cursor-related functions + */ + +/* magic number */ #define Q2C_MAGIC (0xbeebe111) +/* extract cursor from caller form */ #define Q2CURSOR(qkc) ((struct ufsq2_cursor *)&qkc->u.qkc_space[0]) +/* + * Check that a cursor we're handed is something like valid. If + * someone munges it and it still passes these checks, they'll get + * partial or odd results back but won't break anything. + */ static int q2cursor_check(struct ufsq2_cursor *cursor) { @@ -972,108 +1004,137 @@ q2cursor_check(struct ufsq2_cursor *curs return 0; } -struct getuids { - long nuids; /* number of uids in array */ - long maxuids; /* number of uids allocated */ - uid_t *uids; /* array of uids, dynamically allocated */ - long skip; - long seen; - long limit; -}; +/* + * Set up the q2cursor state. + */ +static void +q2cursor_initstate(struct q2cursor_state *state, struct quotakey *keys, + struct quotaval *vals, unsigned maxkeyvals, int blocks_done) +{ + state->keys = keys; + state->vals = vals; + + state->maxkeyvals = maxkeyvals; + state->numkeys = 0; + + /* + * For each ID there are two quotavals to return. If the + * maximum number of entries to return is odd, we might want + * to skip the first quotaval of the first ID, or the last + * quotaval of the last ID, but not both. So the number of IDs + * we want is (up to) half the number of return slots we have, + * rounded up. + */ + + state->maxids = (state->maxkeyvals + 1) / 2; + state->numids = 0; + if (state->maxkeyvals % 2) { + if (blocks_done) { + state->skipfirst = 1; + state->skiplast = 0; + } else { + state->skipfirst = 0; + state->skiplast = 1; + } + } else { + state->skipfirst = 0; + state->skiplast = 0; + } +} + +/* + * Choose which idtype we're going to work on. If doing a full + * iteration, we do users first, then groups, but either might be + * disabled or marked to skip via cursorsetidtype(), so don't make + * silly assumptions. + */ +static int +q2cursor_pickidtype(struct ufsq2_cursor *cursor, int *idtype_ret) +{ + if (cursor->q2c_users_done == 0) { + *idtype_ret = QUOTA_IDTYPE_USER; + } else if (cursor->q2c_groups_done == 0) { + *idtype_ret = QUOTA_IDTYPE_GROUP; + } else { + return EAGAIN; + } + return 0; +} + +/* + * Add an ID to the current state. Sets up either one or two keys to + * refer to it, depending on whether it's first/last and the setting + * of skipfirst. (skiplast does not need to be explicitly tested) + */ +static void +q2cursor_addid(struct q2cursor_state *state, int idtype, id_t id) +{ + KASSERT(state->numids < state->maxids); + KASSERT(state->numkeys < state->maxkeyvals); + if (!state->skipfirst || state->numkeys > 0) { + state->keys[state->numkeys].qk_idtype = idtype; + state->keys[state->numkeys].qk_id = id; + state->keys[state->numkeys].qk_objtype = QUOTA_OBJTYPE_BLOCKS; + state->numkeys++; + } + if (state->numkeys < state->maxkeyvals) { + state->keys[state->numkeys].qk_idtype = idtype; + state->keys[state->numkeys].qk_id = id; + state->keys[state->numkeys].qk_objtype = QUOTA_OBJTYPE_FILES; + state->numkeys++; + } else { + KASSERT(state->skiplast); + } + state->numids++; +} + +/* + * Callback function for getting IDs. Update counting and call addid. + */ static int -quota2_getuids_callback(struct ufsmount *ump, uint64_t *offp, +q2cursor_getids_callback(struct ufsmount *ump, uint64_t *offp, struct quota2_entry *q2ep, uint64_t off, void *v) { - struct getuids *gu = v; - uid_t *newuids; - long newmax; + struct q2cursor_getids *gi = v; + id_t id; #ifdef FFS_EI const int needswap = UFS_MPNEEDSWAP(ump); #endif - if (gu->skip > 0) { - gu->skip--; + if (gi->skipped < gi->skip) { + gi->skipped++; return 0; } - if (gu->nuids == gu->maxuids) { - newmax = gu->maxuids + PAGE_SIZE / sizeof(uid_t); - newuids = realloc(gu->uids, newmax * sizeof(gu->uids[0]), - M_TEMP, M_WAITOK); - if (newuids == NULL) { - return ENOMEM; - } - gu->uids = newuids; - gu->maxuids = newmax; - } - gu->uids[gu->nuids] = ufs_rw32(q2ep->q2e_uid, needswap); - gu->nuids++; - gu->seen++; - if (gu->nuids == gu->limit) { + id = ufs_rw32(q2ep->q2e_uid, needswap); + q2cursor_addid(gi->state, gi->idtype, id); + gi->new_skip++; + if (gi->state->numids >= gi->state->maxids) { + /* got enough ids, stop now */ return Q2WL_ABORT; } return 0; } -int -quota2_handle_cmd_cursorget(struct ufsmount *ump, struct quotakcursor *qkc, - struct quotakey *keys, struct quotaval *vals, unsigned maxreturn, - unsigned *ret) +/* + * Fill in a batch of quotakeys by scanning one or more hash chains. + */ +static int +q2cursor_getkeys(struct ufsmount *ump, int idtype, struct ufsq2_cursor *cursor, + struct q2cursor_state *state, + int *hashsize_ret, struct quota2_entry *default_q2e_ret) { - int error; - struct ufsq2_cursor *cursor; - struct quota2_header *q2h; - struct quota2_entry q2e; + const int needswap = UFS_MPNEEDSWAP(ump); struct buf *hbp; - uint64_t offset; - int idtype; - int can_switch_idtype; - int i, j; + struct quota2_header *q2h; int quota2_hash_size; - const int needswap = UFS_MPNEEDSWAP(ump); - struct getuids gu; - long excess; - id_t junkid; - struct quotaval qv; - unsigned num, maxnum; - int skipfirst, skiplast; - int numreturn; - - cursor = Q2CURSOR(qkc); - error = q2cursor_check(cursor); - if (error) { - return error; - } - - CTASSERT(USRQUOTA == QUOTA_IDTYPE_USER); - CTASSERT(GRPQUOTA == QUOTA_IDTYPE_GROUP); - - if (cursor->q2c_users_done == 0 && - ump->um_quotas[USRQUOTA] == NULLVP) { - cursor->q2c_users_done = 1; - } - if (cursor->q2c_groups_done == 0 && - ump->um_quotas[GRPQUOTA] == NULLVP) { - cursor->q2c_groups_done = 1; - } - -restart: - - if (cursor->q2c_users_done == 0) { - idtype = QUOTA_IDTYPE_USER; - can_switch_idtype = 1; - } else if (cursor->q2c_groups_done == 0) { - idtype = QUOTA_IDTYPE_GROUP; - can_switch_idtype = 0; - } else { - /* nothing more to do, return 0 */ - *ret = 0; - return 0; - } - - KASSERT(ump->um_quotas[idtype] != NULLVP); + struct q2cursor_getids gi; + uint64_t offset; + int error; - numreturn = 0; + /* + * Read the header block. + */ mutex_enter(&dqlock); error = getq2h(ump, idtype, &hbp, &q2h, 0); @@ -1082,130 +1143,234 @@ restart: return error; } - if (cursor->q2c_defaults_done == 0) { - quota2_ufs_rwq2e(&q2h->q2h_defentry, &q2e, needswap); - if (cursor->q2c_blocks_done == 0) { - q2e_to_quotaval(&q2e, 1, &junkid, QL_BLOCK, &qv); - keys[numreturn].qk_idtype = idtype; - keys[numreturn].qk_id = QUOTA_DEFAULTID; - keys[numreturn].qk_objtype = QUOTA_OBJTYPE_BLOCKS; - vals[numreturn++] = qv; - cursor->q2c_blocks_done = 1; - } - if (cursor->q2c_blocks_done == 1) { - q2e_to_quotaval(&q2e, 1, &junkid, QL_FILE, &qv); - keys[numreturn].qk_idtype = idtype; - keys[numreturn].qk_id = QUOTA_DEFAULTID; - keys[numreturn].qk_objtype = QUOTA_OBJTYPE_FILES; - vals[numreturn++] = qv; - cursor->q2c_blocks_done = 0; - cursor->q2c_defaults_done = 1; - } - } - - /* - * we can't directly get entries as we can't walk the list - * with qdlock and grab dq_interlock to read the entries - * at the same time. So just walk the lists to build a list of uid, - * and then read entries for these uids - */ - memset(&gu, 0, sizeof(gu)); - quota2_hash_size = ufs_rw16(q2h->q2h_hash_size, needswap); - /* if the table size has changed, make the caller start over */ + quota2_hash_size = ufs_rw16(q2h->q2h_hash_size, needswap); if (cursor->q2c_hashsize == 0) { cursor->q2c_hashsize = quota2_hash_size; } else if (cursor->q2c_hashsize != quota2_hash_size) { error = EDEADLK; - goto fail; + goto scanfail; } - gu.skip = cursor->q2c_uidpos; - gu.seen = 0; - gu.limit = (maxreturn - numreturn) / 2; - if (gu.limit == 0 && (maxreturn - numreturn) > 0) { - gu.limit = 1; - } - for (i = cursor->q2c_hashpos; i < quota2_hash_size ; i++) { - offset = q2h->q2h_entries[i]; - gu.seen = 0; - error = quota2_walk_list(ump, hbp, idtype, &offset, 0, &gu, - quota2_getuids_callback); - if (error && error != Q2WL_ABORT) { - if (gu.uids != NULL) - free(gu.uids, M_TEMP); + /* grab the entry with the default values out of the header */ + quota2_ufs_rwq2e(&q2h->q2h_defentry, default_q2e_ret, needswap); + + /* If we haven't done the defaults yet, that goes first. */ + if (cursor->q2c_defaults_done == 0) { + q2cursor_addid(state, idtype, QUOTA_DEFAULTID); + cursor->q2c_defaults_done = 1; + } + + gi.state = state; + gi.idtype = idtype; + + while (state->numids < state->maxids) { + if (cursor->q2c_hashpos >= quota2_hash_size) { + /* nothing more left */ break; } + + /* scan this hash chain */ + gi.skip = cursor->q2c_uidpos; + gi.new_skip = gi.skip; + gi.skipped = 0; + offset = q2h->q2h_entries[cursor->q2c_hashpos]; + + error = quota2_walk_list(ump, hbp, idtype, &offset, 0, &gi, + q2cursor_getids_callback); if (error == Q2WL_ABORT) { - /* got enough uids for now */ + /* callback stopped before reading whole chain */ + cursor->q2c_uidpos = gi.new_skip; + /* not an error */ error = 0; - } - if (gu.nuids > gu.limit) { - excess = gu.nuids - gu.limit; - KASSERT(excess < gu.seen); - gu.seen -= excess; - gu.nuids -= excess; - } - if (gu.nuids == gu.limit) { + } else if (error) { break; + } else { + /* read whole chain, advance to next */ + cursor->q2c_uidpos = 0; + cursor->q2c_hashpos++; } } - KASSERT(gu.nuids <= gu.limit); - cursor->q2c_hashpos = i; - cursor->q2c_uidpos = gu.seen; -fail: +scanfail: mutex_exit(&dqlock); brelse(hbp, 0); if (error) return error; - if (gu.nuids == 0) { - if (idtype == QUOTA_IDTYPE_USER) - cursor->q2c_users_done = 1; - else - cursor->q2c_groups_done = 1; - if (can_switch_idtype) { - goto restart; + *hashsize_ret = quota2_hash_size; + return 0; +} + +/* + * Fetch the quotavals for the quotakeys. + */ +static int +q2cursor_getvals(struct ufsmount *ump, struct q2cursor_state *state, + const struct quota2_entry *default_q2e) +{ + int hasid; + id_t loadedid, id; + unsigned pos; + struct quota2_entry q2e; + int objtype; + int error; + + hasid = 0; + loadedid = 0; + for (pos = 0; pos < state->numkeys; pos++) { + id = state->keys[pos].qk_id; + if (!hasid || id != loadedid) { + hasid = 1; + loadedid = id; + if (id == QUOTA_DEFAULTID) { + q2e = *default_q2e; + } else { + error = quota2_fetch_q2e(ump, + &state->keys[pos], + &q2e); + if (error == ENOENT) { + /* something changed - start over */ + error = EDEADLK; + } + if (error) { + return error; + } + } } + + + objtype = state->keys[pos].qk_objtype; + KASSERT(objtype >= 0 && objtype < N_QL); + q2val_to_quotaval(&q2e.q2e_val[objtype], &state->vals[pos]); } - maxnum = gu.nuids*2; + return 0; +} + +/* + * Handle cursorget. + * + * We can't just read keys and values directly, because we can't walk + * the list with qdlock and grab dq_interlock to read the entries at + * the same time. So we're going to do two passes: one to figure out + * which IDs we want and fill in the keys, and then a second to use + * the keys to fetch the values. + */ +int +quota2_handle_cmd_cursorget(struct ufsmount *ump, struct quotakcursor *qkc, + struct quotakey *keys, struct quotaval *vals, unsigned maxreturn, + unsigned *ret) +{ + int error; + struct ufsq2_cursor *cursor; + struct ufsq2_cursor newcursor; + struct q2cursor_state state; + struct quota2_entry default_q2e; + int idtype; + int quota2_hash_size; /* - * If we've already sent back the blocks value for the first id, - * don't send it again (skipfirst). - * - * If we have an odd number of available result slots and we - * aren't going to skip the first result entry, we need to - * leave off the last result entry (skiplast). + * Convert and validate the cursor. */ - skipfirst = (cursor->q2c_blocks_done != 0); - skiplast = skipfirst == 0 && (maxreturn < maxnum); - num = 0; - for (j = 0; j < gu.nuids; j++) { - error = quota2_result_add_q2e(ump, idtype, - gu.uids[j], keys, vals, numreturn + j*2, - j == 0 && skipfirst, - j + 1 == gu.nuids && skiplast); - if (error == ENOENT) - continue; - if (error) - break; - if ((j == 0 && skipfirst) || (j + 1 == gu.nuids && skiplast)) { - num += 1; + cursor = Q2CURSOR(qkc); + error = q2cursor_check(cursor); + if (error) { + return error; + } + + /* + * Make sure our on-disk codes match the values of the + * FS-independent ones. This avoids the need for explicit + * conversion (which would be a NOP anyway and thus easily + * left out or called in the wrong places...) + */ + CTASSERT(QUOTA_IDTYPE_USER == USRQUOTA); + CTASSERT(QUOTA_IDTYPE_GROUP == GRPQUOTA); + CTASSERT(QUOTA_OBJTYPE_BLOCKS == QL_BLOCK); + CTASSERT(QUOTA_OBJTYPE_FILES == QL_FILE); + + /* + * If some of the idtypes aren't configured/enabled, arrange + * to skip over them. + */ + if (cursor->q2c_users_done == 0 && + ump->um_quotas[USRQUOTA] == NULLVP) { + cursor->q2c_users_done = 1; + } + if (cursor->q2c_groups_done == 0 && + ump->um_quotas[GRPQUOTA] == NULLVP) { + cursor->q2c_groups_done = 1; + } + + /* Loop over, potentially, both idtypes */ + while (1) { + + /* Choose id type */ + error = q2cursor_pickidtype(cursor, &idtype); + if (error == EAGAIN) { + /* nothing more to do, return 0 */ + *ret = 0; + return 0; + } + KASSERT(ump->um_quotas[idtype] != NULLVP); + + /* + * Initialize the per-call iteration state. Copy the + * cursor state so we can update it in place but back + * out on error. + */ + q2cursor_initstate(&state, keys, vals, maxreturn, + cursor->q2c_blocks_done); + newcursor = *cursor; + + /* Assign keys */ + error = q2cursor_getkeys(ump, idtype, &newcursor, &state, + "a2_hash_size, &default_q2e); + if (error) { + return error; + } + + /* Now fill in the values. */ + error = q2cursor_getvals(ump, &state, &default_q2e); + if (error) { + return error; + } + + /* + * Now that we aren't going to fail and lose what we + * did so far, we can update the cursor state. + */ + + if (newcursor.q2c_hashpos >= quota2_hash_size) { + if (idtype == QUOTA_IDTYPE_USER) + cursor->q2c_users_done = 1; + else + cursor->q2c_groups_done = 1; + + /* start over on another id type */ + cursor->q2c_hashsize = 0; + cursor->q2c_defaults_done = 0; + cursor->q2c_hashpos = 0; + cursor->q2c_uidpos = 0; + cursor->q2c_blocks_done = 0; } else { - num += 2; + *cursor = newcursor; + cursor->q2c_blocks_done = state.skiplast; } - } - numreturn += num; - *ret = numreturn; - cursor->q2c_blocks_done = skiplast; + /* + * If we have something to return, return it. + * Otherwise, continue to the other idtype, if any, + * and only return zero at end of iteration. + */ + if (state.numkeys > 0) { + break; + } + } - if (gu.uids != NULL) - free(gu.uids, M_TEMP); - return error; + *ret = state.numkeys; + return 0; } int