| /* |
| * Copyright (c) 1980, 1990 Regents of the University of California. All |
| * rights reserved. |
| * |
| * This code is derived from software contributed to Berkeley by Robert Elz at |
| * The University of Melbourne. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. 2. |
| * Redistributions in binary form must reproduce the above copyright notice, |
| * this list of conditions and the following disclaimer in the documentation |
| * and/or other materials provided with the distribution. 3. All advertising |
| * materials mentioning features or use of this software must display the |
| * following acknowledgement: This product includes software developed by the |
| * University of California, Berkeley and its contributors. 4. Neither the |
| * name of the University nor the names of its contributors may be used to |
| * endorse or promote products derived from this software without specific |
| * prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR |
| * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
| * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
| * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT |
| * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY |
| * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF |
| * SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| |
| #include <unistd.h> |
| #include <errno.h> |
| #include <string.h> |
| #include <stdlib.h> |
| |
| #include "pot.h" |
| #include "common.h" |
| #include "quotaio_v1.h" |
| #include "dqblk_v1.h" |
| #include "quotaio.h" |
| #include "quotasys.h" |
| #include "quotaio_generic.h" |
| |
| static int v1_check_file(int fd, int type, int fmt); |
| static int v1_init_io(struct quota_handle *h); |
| static int v1_new_io(struct quota_handle *h); |
| static int v1_write_info(struct quota_handle *h); |
| static struct dquot *v1_read_dquot(struct quota_handle *h, qid_t id); |
| static int v1_commit_dquot(struct dquot *dquot, int flags); |
| static int v1_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot *dquot, char *dqname)); |
| |
| struct quotafile_ops quotafile_ops_1 = { |
| check_file: v1_check_file, |
| init_io: v1_init_io, |
| new_io: v1_new_io, |
| write_info: v1_write_info, |
| read_dquot: v1_read_dquot, |
| commit_dquot: v1_commit_dquot, |
| scan_dquots: v1_scan_dquots, |
| }; |
| |
| /* |
| * Copy dquot from disk to memory |
| */ |
| static inline void v1_disk2memdqblk(struct util_dqblk *m, struct v1_disk_dqblk *d) |
| { |
| m->dqb_ihardlimit = d->dqb_ihardlimit; |
| m->dqb_isoftlimit = d->dqb_isoftlimit; |
| m->dqb_bhardlimit = d->dqb_bhardlimit; |
| m->dqb_bsoftlimit = d->dqb_bsoftlimit; |
| m->dqb_curinodes = d->dqb_curinodes; |
| m->dqb_curspace = ((qsize_t)d->dqb_curblocks) * V1_DQBLK_SIZE; |
| m->dqb_itime = d->dqb_itime; |
| m->dqb_btime = d->dqb_btime; |
| } |
| |
| /* |
| * Copy dquot from memory to disk |
| */ |
| static inline void v1_mem2diskdqblk(struct v1_disk_dqblk *d, struct util_dqblk *m) |
| { |
| d->dqb_ihardlimit = m->dqb_ihardlimit; |
| d->dqb_isoftlimit = m->dqb_isoftlimit; |
| d->dqb_bhardlimit = m->dqb_bhardlimit; |
| d->dqb_bsoftlimit = m->dqb_bsoftlimit; |
| d->dqb_curinodes = m->dqb_curinodes; |
| d->dqb_curblocks = m->dqb_curspace >> V1_DQBLK_SIZE_BITS; |
| d->dqb_itime = m->dqb_itime; |
| d->dqb_btime = m->dqb_btime; |
| } |
| |
| /* Convert kernel quotablock format to utility one */ |
| static inline void v1_kern2utildqblk(struct util_dqblk *u, struct v1_kern_dqblk *k) |
| { |
| u->dqb_ihardlimit = k->dqb_ihardlimit; |
| u->dqb_isoftlimit = k->dqb_isoftlimit; |
| u->dqb_bhardlimit = k->dqb_bhardlimit; |
| u->dqb_bsoftlimit = k->dqb_bsoftlimit; |
| u->dqb_curinodes = k->dqb_curinodes; |
| u->dqb_curspace = ((qsize_t)k->dqb_curblocks) << V1_DQBLK_SIZE_BITS; |
| u->dqb_itime = k->dqb_itime; |
| u->dqb_btime = k->dqb_btime; |
| } |
| |
| /* Convert utility quotablock format to kernel one */ |
| static inline void v1_util2kerndqblk(struct v1_kern_dqblk *k, struct util_dqblk *u) |
| { |
| k->dqb_ihardlimit = u->dqb_ihardlimit; |
| k->dqb_isoftlimit = u->dqb_isoftlimit; |
| k->dqb_bhardlimit = u->dqb_bhardlimit; |
| k->dqb_bsoftlimit = u->dqb_bsoftlimit; |
| k->dqb_curinodes = u->dqb_curinodes; |
| k->dqb_curblocks = (u->dqb_curspace + V1_DQBLK_SIZE - 1) >> V1_DQBLK_SIZE_BITS; |
| k->dqb_itime = u->dqb_itime; |
| k->dqb_btime = u->dqb_btime; |
| } |
| |
| /* |
| * Check whether quotafile is in our format |
| */ |
| static int v1_check_file(int fd, int type, int fmt) |
| { |
| struct stat st; |
| |
| if (fstat(fd, &st) < 0) |
| return 0; |
| if (!st.st_size || st.st_size % sizeof(struct v1_disk_dqblk)) |
| return 0; |
| return 1; |
| } |
| |
| /* |
| * Open quotafile |
| */ |
| static int v1_init_io(struct quota_handle *h) |
| { |
| if (QIO_ENABLED(h)) { |
| if (kernel_iface == IFACE_GENERIC) { |
| if (vfs_get_info(h) < 0) |
| return -1; |
| } |
| else { |
| struct v1_kern_dqblk kdqblk; |
| |
| if (quotactl(QCMD(Q_V1_GETQUOTA, h->qh_type), h->qh_quotadev, 0, (void *)&kdqblk) < 0) { |
| if (errno == EPERM) { /* We have no permission to get this information? */ |
| h->qh_info.dqi_bgrace = h->qh_info.dqi_igrace = 0; /* It hopefully won't be needed */ |
| } |
| else |
| return -1; |
| } |
| else { |
| h->qh_info.dqi_bgrace = kdqblk.dqb_btime; |
| h->qh_info.dqi_igrace = kdqblk.dqb_itime; |
| } |
| } |
| } |
| else { |
| struct v1_disk_dqblk ddqblk; |
| |
| lseek(h->qh_fd, 0, SEEK_SET); |
| if (read(h->qh_fd, &ddqblk, sizeof(ddqblk)) != sizeof(ddqblk)) |
| return -1; |
| h->qh_info.dqi_bgrace = ddqblk.dqb_btime; |
| h->qh_info.dqi_igrace = ddqblk.dqb_itime; |
| } |
| if (!h->qh_info.dqi_bgrace) |
| h->qh_info.dqi_bgrace = MAX_DQ_TIME; |
| if (!h->qh_info.dqi_igrace) |
| h->qh_info.dqi_igrace = MAX_IQ_TIME; |
| h->qh_info.dqi_max_b_limit = ~(uint32_t)0; |
| h->qh_info.dqi_max_i_limit = ~(uint32_t)0; |
| h->qh_info.dqi_max_b_usage = ((uint64_t)(~(uint32_t)0)) << V1_DQBLK_SIZE_BITS; |
| h->qh_info.dqi_max_i_usage = ~(uint32_t)0; |
| |
| return 0; |
| } |
| |
| /* |
| * Initialize new quotafile |
| */ |
| static int v1_new_io(struct quota_handle *h) |
| { |
| struct v1_disk_dqblk ddqblk; |
| |
| /* Write at least roots dquot with grace times */ |
| memset(&ddqblk, 0, sizeof(ddqblk)); |
| ddqblk.dqb_btime = MAX_DQ_TIME; |
| ddqblk.dqb_itime = MAX_IQ_TIME; |
| h->qh_info.dqi_bgrace = MAX_DQ_TIME; |
| h->qh_info.dqi_igrace = MAX_IQ_TIME; |
| h->qh_info.dqi_max_b_limit = ~(uint32_t)0; |
| h->qh_info.dqi_max_i_limit = ~(uint32_t)0; |
| h->qh_info.dqi_max_b_usage = ((uint64_t)(~(uint32_t)0)) << V1_DQBLK_SIZE_BITS; |
| h->qh_info.dqi_max_i_usage = ~(uint32_t)0; |
| lseek(h->qh_fd, 0, SEEK_SET); |
| if (write(h->qh_fd, &ddqblk, sizeof(ddqblk)) != sizeof(ddqblk)) |
| return -1; |
| return 0; |
| } |
| |
| /* |
| * Write information (grace times to file) |
| */ |
| static int v1_write_info(struct quota_handle *h) |
| { |
| if (QIO_RO(h)) { |
| errstr(_("Trying to write info to readonly quotafile on %s.\n"), h->qh_quotadev); |
| errno = EPERM; |
| return -1; |
| } |
| if (QIO_ENABLED(h)) { |
| if (kernel_iface == IFACE_GENERIC) { |
| if (vfs_set_info(h, IIF_BGRACE | IIF_IGRACE) < 0) |
| return -1; |
| } |
| else { |
| struct v1_kern_dqblk kdqblk; |
| |
| if (quotactl(QCMD(Q_V1_GETQUOTA, h->qh_type), h->qh_quotadev, 0, (void *)&kdqblk) < 0) |
| return -1; |
| kdqblk.dqb_btime = h->qh_info.dqi_bgrace; |
| kdqblk.dqb_itime = h->qh_info.dqi_igrace; |
| if (quotactl(QCMD(Q_V1_SETQUOTA, h->qh_type), h->qh_quotadev, 0, (void *)&kdqblk) < 0) |
| return -1; |
| } |
| } |
| else { |
| struct v1_disk_dqblk ddqblk; |
| |
| lseek(h->qh_fd, 0, SEEK_SET); |
| if (read(h->qh_fd, &ddqblk, sizeof(ddqblk)) != sizeof(ddqblk)) |
| return -1; |
| ddqblk.dqb_btime = h->qh_info.dqi_bgrace; |
| ddqblk.dqb_itime = h->qh_info.dqi_igrace; |
| lseek(h->qh_fd, 0, SEEK_SET); |
| if (write(h->qh_fd, &ddqblk, sizeof(ddqblk)) != sizeof(ddqblk)) |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Read a dqblk struct from the quotafile. |
| * User can use 'errno' to detect errstr. |
| */ |
| static struct dquot *v1_read_dquot(struct quota_handle *h, qid_t id) |
| { |
| struct v1_disk_dqblk ddqblk; |
| struct dquot *dquot = get_empty_dquot(); |
| |
| dquot->dq_id = id; |
| dquot->dq_h = h; |
| if (QIO_ENABLED(h)) { /* Does kernel use the file? */ |
| if (kernel_iface == IFACE_GENERIC) { |
| if (vfs_get_dquot(dquot) < 0) { |
| free(dquot); |
| return NULL; |
| } |
| } |
| else { |
| struct v1_kern_dqblk kdqblk; |
| |
| if (quotactl(QCMD(Q_V1_GETQUOTA, h->qh_type), h->qh_quotadev, id, (void *)&kdqblk) < 0) { |
| free(dquot); |
| return NULL; |
| } |
| v1_kern2utildqblk(&dquot->dq_dqb, &kdqblk); |
| } |
| } |
| else { |
| lseek(h->qh_fd, (long)V1_DQOFF(id), SEEK_SET); |
| switch (read(h->qh_fd, &ddqblk, sizeof(ddqblk))) { |
| case 0: /* EOF */ |
| /* |
| * Convert implicit 0 quota (EOF) into an |
| * explicit one (zero'ed dqblk) |
| */ |
| memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk)); |
| break; |
| case sizeof(struct v1_disk_dqblk): /* OK */ |
| v1_disk2memdqblk(&dquot->dq_dqb, &ddqblk); |
| break; |
| default: /* ERROR */ |
| free(dquot); |
| return NULL; |
| } |
| } |
| return dquot; |
| } |
| |
| /* |
| * Write a dqblk struct to the quotafile. |
| * User can process use 'errno' to detect errstr |
| */ |
| static int v1_commit_dquot(struct dquot *dquot, int flags) |
| { |
| struct v1_disk_dqblk ddqblk; |
| struct quota_handle *h = dquot->dq_h; |
| |
| if (QIO_RO(h)) { |
| errstr(_("Trying to write quota to readonly quotafile on %s\n"), h->qh_quotadev); |
| errno = EPERM; |
| return -1; |
| } |
| if (QIO_ENABLED(h)) { /* Kernel uses same file? */ |
| if (kernel_iface == IFACE_GENERIC) { |
| if (vfs_set_dquot(dquot, flags) < 0) |
| return -1; |
| } |
| else { |
| struct v1_kern_dqblk kdqblk; |
| int cmd; |
| |
| if (flags == COMMIT_USAGE) |
| cmd = Q_V1_SETUSE; |
| else if (flags == COMMIT_LIMITS) |
| cmd = Q_V1_SETQLIM; |
| else if (flags & COMMIT_TIMES) { |
| errno = EINVAL; |
| return -1; |
| } |
| else |
| cmd = Q_V1_SETQUOTA; |
| v1_util2kerndqblk(&kdqblk, &dquot->dq_dqb); |
| if (quotactl(QCMD(cmd, h->qh_type), h->qh_quotadev, dquot->dq_id, |
| (void *)&kdqblk) < 0) |
| return -1; |
| } |
| } |
| else { |
| if (check_dquot_range(dquot) < 0) { |
| errno = ERANGE; |
| return -1; |
| } |
| v1_mem2diskdqblk(&ddqblk, &dquot->dq_dqb); |
| lseek(h->qh_fd, (long)V1_DQOFF(dquot->dq_id), SEEK_SET); |
| if (write(h->qh_fd, &ddqblk, sizeof(ddqblk)) != sizeof(ddqblk)) |
| return -1; |
| } |
| return 0; |
| } |
| |
| /* |
| * Scan all dquots in file and call callback on each |
| */ |
| #define SCANBUFSIZE 256 |
| |
| static int v1_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot *, char *)) |
| { |
| int rd, scanbufpos = 0, scanbufsize = 0; |
| char scanbuf[sizeof(struct v1_disk_dqblk)*SCANBUFSIZE]; |
| struct v1_disk_dqblk *ddqblk; |
| struct dquot *dquot = get_empty_dquot(); |
| qid_t id = 0; |
| |
| memset(dquot, 0, sizeof(*dquot)); |
| dquot->dq_h = h; |
| lseek(h->qh_fd, 0, SEEK_SET); |
| for(id = 0; ; id++, scanbufpos++) { |
| if (scanbufpos >= scanbufsize) { |
| rd = read(h->qh_fd, scanbuf, sizeof(scanbuf)); |
| if (rd < 0 || rd % sizeof(struct v1_disk_dqblk)) |
| goto out_err; |
| if (!rd) |
| break; |
| scanbufpos = 0; |
| scanbufsize = rd / sizeof(struct v1_disk_dqblk); |
| } |
| ddqblk = ((struct v1_disk_dqblk *)scanbuf) + scanbufpos; |
| if ((ddqblk->dqb_ihardlimit | ddqblk->dqb_isoftlimit | |
| ddqblk->dqb_bhardlimit | ddqblk->dqb_bsoftlimit | |
| ddqblk->dqb_curblocks | ddqblk->dqb_curinodes | |
| ddqblk->dqb_itime | ddqblk->dqb_btime) == 0) |
| continue; |
| v1_disk2memdqblk(&dquot->dq_dqb, ddqblk); |
| dquot->dq_id = id; |
| if ((rd = process_dquot(dquot, NULL)) < 0) { |
| free(dquot); |
| return rd; |
| } |
| } |
| if (!rd) { /* EOF? */ |
| free(dquot); |
| return 0; |
| } |
| out_err: |
| free(dquot); |
| return -1; /* Some read errstr... */ |
| } |