18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci#include <linux/errno.h> 38c2ecf20Sopenharmony_ci#include <linux/fs.h> 48c2ecf20Sopenharmony_ci#include <linux/quota.h> 58c2ecf20Sopenharmony_ci#include <linux/quotaops.h> 68c2ecf20Sopenharmony_ci#include <linux/dqblk_v1.h> 78c2ecf20Sopenharmony_ci#include <linux/kernel.h> 88c2ecf20Sopenharmony_ci#include <linux/init.h> 98c2ecf20Sopenharmony_ci#include <linux/module.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <asm/byteorder.h> 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include "quotaio_v1.h" 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_ciMODULE_AUTHOR("Jan Kara"); 168c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Old quota format support"); 178c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL"); 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ci#define QUOTABLOCK_BITS 10 208c2ecf20Sopenharmony_ci#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS) 218c2ecf20Sopenharmony_ci 228c2ecf20Sopenharmony_cistatic inline qsize_t v1_stoqb(qsize_t space) 238c2ecf20Sopenharmony_ci{ 248c2ecf20Sopenharmony_ci return (space + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS; 258c2ecf20Sopenharmony_ci} 268c2ecf20Sopenharmony_ci 278c2ecf20Sopenharmony_cistatic inline qsize_t v1_qbtos(qsize_t blocks) 288c2ecf20Sopenharmony_ci{ 298c2ecf20Sopenharmony_ci return blocks << QUOTABLOCK_BITS; 308c2ecf20Sopenharmony_ci} 318c2ecf20Sopenharmony_ci 328c2ecf20Sopenharmony_cistatic void v1_disk2mem_dqblk(struct mem_dqblk *m, struct v1_disk_dqblk *d) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci m->dqb_ihardlimit = d->dqb_ihardlimit; 358c2ecf20Sopenharmony_ci m->dqb_isoftlimit = d->dqb_isoftlimit; 368c2ecf20Sopenharmony_ci m->dqb_curinodes = d->dqb_curinodes; 378c2ecf20Sopenharmony_ci m->dqb_bhardlimit = v1_qbtos(d->dqb_bhardlimit); 388c2ecf20Sopenharmony_ci m->dqb_bsoftlimit = v1_qbtos(d->dqb_bsoftlimit); 398c2ecf20Sopenharmony_ci m->dqb_curspace = v1_qbtos(d->dqb_curblocks); 408c2ecf20Sopenharmony_ci m->dqb_itime = d->dqb_itime; 418c2ecf20Sopenharmony_ci m->dqb_btime = d->dqb_btime; 428c2ecf20Sopenharmony_ci} 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic void v1_mem2disk_dqblk(struct v1_disk_dqblk *d, struct mem_dqblk *m) 458c2ecf20Sopenharmony_ci{ 468c2ecf20Sopenharmony_ci d->dqb_ihardlimit = m->dqb_ihardlimit; 478c2ecf20Sopenharmony_ci d->dqb_isoftlimit = m->dqb_isoftlimit; 488c2ecf20Sopenharmony_ci d->dqb_curinodes = m->dqb_curinodes; 498c2ecf20Sopenharmony_ci d->dqb_bhardlimit = v1_stoqb(m->dqb_bhardlimit); 508c2ecf20Sopenharmony_ci d->dqb_bsoftlimit = v1_stoqb(m->dqb_bsoftlimit); 518c2ecf20Sopenharmony_ci d->dqb_curblocks = v1_stoqb(m->dqb_curspace); 528c2ecf20Sopenharmony_ci d->dqb_itime = m->dqb_itime; 538c2ecf20Sopenharmony_ci d->dqb_btime = m->dqb_btime; 548c2ecf20Sopenharmony_ci} 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_cistatic int v1_read_dqblk(struct dquot *dquot) 578c2ecf20Sopenharmony_ci{ 588c2ecf20Sopenharmony_ci int type = dquot->dq_id.type; 598c2ecf20Sopenharmony_ci struct v1_disk_dqblk dqblk; 608c2ecf20Sopenharmony_ci struct quota_info *dqopt = sb_dqopt(dquot->dq_sb); 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci if (!dqopt->files[type]) 638c2ecf20Sopenharmony_ci return -EINVAL; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci /* Set structure to 0s in case read fails/is after end of file */ 668c2ecf20Sopenharmony_ci memset(&dqblk, 0, sizeof(struct v1_disk_dqblk)); 678c2ecf20Sopenharmony_ci dquot->dq_sb->s_op->quota_read(dquot->dq_sb, type, (char *)&dqblk, 688c2ecf20Sopenharmony_ci sizeof(struct v1_disk_dqblk), 698c2ecf20Sopenharmony_ci v1_dqoff(from_kqid(&init_user_ns, dquot->dq_id))); 708c2ecf20Sopenharmony_ci 718c2ecf20Sopenharmony_ci v1_disk2mem_dqblk(&dquot->dq_dqb, &dqblk); 728c2ecf20Sopenharmony_ci if (dquot->dq_dqb.dqb_bhardlimit == 0 && 738c2ecf20Sopenharmony_ci dquot->dq_dqb.dqb_bsoftlimit == 0 && 748c2ecf20Sopenharmony_ci dquot->dq_dqb.dqb_ihardlimit == 0 && 758c2ecf20Sopenharmony_ci dquot->dq_dqb.dqb_isoftlimit == 0) 768c2ecf20Sopenharmony_ci set_bit(DQ_FAKE_B, &dquot->dq_flags); 778c2ecf20Sopenharmony_ci dqstats_inc(DQST_READS); 788c2ecf20Sopenharmony_ci 798c2ecf20Sopenharmony_ci return 0; 808c2ecf20Sopenharmony_ci} 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_cistatic int v1_commit_dqblk(struct dquot *dquot) 838c2ecf20Sopenharmony_ci{ 848c2ecf20Sopenharmony_ci short type = dquot->dq_id.type; 858c2ecf20Sopenharmony_ci ssize_t ret; 868c2ecf20Sopenharmony_ci struct v1_disk_dqblk dqblk; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci v1_mem2disk_dqblk(&dqblk, &dquot->dq_dqb); 898c2ecf20Sopenharmony_ci if (((type == USRQUOTA) && uid_eq(dquot->dq_id.uid, GLOBAL_ROOT_UID)) || 908c2ecf20Sopenharmony_ci ((type == GRPQUOTA) && gid_eq(dquot->dq_id.gid, GLOBAL_ROOT_GID))) { 918c2ecf20Sopenharmony_ci dqblk.dqb_btime = 928c2ecf20Sopenharmony_ci sb_dqopt(dquot->dq_sb)->info[type].dqi_bgrace; 938c2ecf20Sopenharmony_ci dqblk.dqb_itime = 948c2ecf20Sopenharmony_ci sb_dqopt(dquot->dq_sb)->info[type].dqi_igrace; 958c2ecf20Sopenharmony_ci } 968c2ecf20Sopenharmony_ci ret = 0; 978c2ecf20Sopenharmony_ci if (sb_dqopt(dquot->dq_sb)->files[type]) 988c2ecf20Sopenharmony_ci ret = dquot->dq_sb->s_op->quota_write(dquot->dq_sb, type, 998c2ecf20Sopenharmony_ci (char *)&dqblk, sizeof(struct v1_disk_dqblk), 1008c2ecf20Sopenharmony_ci v1_dqoff(from_kqid(&init_user_ns, dquot->dq_id))); 1018c2ecf20Sopenharmony_ci if (ret != sizeof(struct v1_disk_dqblk)) { 1028c2ecf20Sopenharmony_ci quota_error(dquot->dq_sb, "dquota write failed"); 1038c2ecf20Sopenharmony_ci if (ret >= 0) 1048c2ecf20Sopenharmony_ci ret = -EIO; 1058c2ecf20Sopenharmony_ci goto out; 1068c2ecf20Sopenharmony_ci } 1078c2ecf20Sopenharmony_ci ret = 0; 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_ciout: 1108c2ecf20Sopenharmony_ci dqstats_inc(DQST_WRITES); 1118c2ecf20Sopenharmony_ci 1128c2ecf20Sopenharmony_ci return ret; 1138c2ecf20Sopenharmony_ci} 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci/* Magics of new quota format */ 1168c2ecf20Sopenharmony_ci#define V2_INITQMAGICS {\ 1178c2ecf20Sopenharmony_ci 0xd9c01f11, /* USRQUOTA */\ 1188c2ecf20Sopenharmony_ci 0xd9c01927 /* GRPQUOTA */\ 1198c2ecf20Sopenharmony_ci} 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci/* Header of new quota format */ 1228c2ecf20Sopenharmony_cistruct v2_disk_dqheader { 1238c2ecf20Sopenharmony_ci __le32 dqh_magic; /* Magic number identifying file */ 1248c2ecf20Sopenharmony_ci __le32 dqh_version; /* File version */ 1258c2ecf20Sopenharmony_ci}; 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_cistatic int v1_check_quota_file(struct super_block *sb, int type) 1288c2ecf20Sopenharmony_ci{ 1298c2ecf20Sopenharmony_ci struct inode *inode = sb_dqopt(sb)->files[type]; 1308c2ecf20Sopenharmony_ci ulong blocks; 1318c2ecf20Sopenharmony_ci size_t off; 1328c2ecf20Sopenharmony_ci struct v2_disk_dqheader dqhead; 1338c2ecf20Sopenharmony_ci ssize_t size; 1348c2ecf20Sopenharmony_ci loff_t isize; 1358c2ecf20Sopenharmony_ci static const uint quota_magics[] = V2_INITQMAGICS; 1368c2ecf20Sopenharmony_ci 1378c2ecf20Sopenharmony_ci isize = i_size_read(inode); 1388c2ecf20Sopenharmony_ci if (!isize) 1398c2ecf20Sopenharmony_ci return 0; 1408c2ecf20Sopenharmony_ci blocks = isize >> BLOCK_SIZE_BITS; 1418c2ecf20Sopenharmony_ci off = isize & (BLOCK_SIZE - 1); 1428c2ecf20Sopenharmony_ci if ((blocks % sizeof(struct v1_disk_dqblk) * BLOCK_SIZE + off) % 1438c2ecf20Sopenharmony_ci sizeof(struct v1_disk_dqblk)) 1448c2ecf20Sopenharmony_ci return 0; 1458c2ecf20Sopenharmony_ci /* Doublecheck whether we didn't get file with new format - with old 1468c2ecf20Sopenharmony_ci * quotactl() this could happen */ 1478c2ecf20Sopenharmony_ci size = sb->s_op->quota_read(sb, type, (char *)&dqhead, 1488c2ecf20Sopenharmony_ci sizeof(struct v2_disk_dqheader), 0); 1498c2ecf20Sopenharmony_ci if (size != sizeof(struct v2_disk_dqheader)) 1508c2ecf20Sopenharmony_ci return 1; /* Probably not new format */ 1518c2ecf20Sopenharmony_ci if (le32_to_cpu(dqhead.dqh_magic) != quota_magics[type]) 1528c2ecf20Sopenharmony_ci return 1; /* Definitely not new format */ 1538c2ecf20Sopenharmony_ci printk(KERN_INFO 1548c2ecf20Sopenharmony_ci "VFS: %s: Refusing to turn on old quota format on given file." 1558c2ecf20Sopenharmony_ci " It probably contains newer quota format.\n", sb->s_id); 1568c2ecf20Sopenharmony_ci return 0; /* Seems like a new format file -> refuse it */ 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_cistatic int v1_read_file_info(struct super_block *sb, int type) 1608c2ecf20Sopenharmony_ci{ 1618c2ecf20Sopenharmony_ci struct quota_info *dqopt = sb_dqopt(sb); 1628c2ecf20Sopenharmony_ci struct v1_disk_dqblk dqblk; 1638c2ecf20Sopenharmony_ci int ret; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci down_read(&dqopt->dqio_sem); 1668c2ecf20Sopenharmony_ci ret = sb->s_op->quota_read(sb, type, (char *)&dqblk, 1678c2ecf20Sopenharmony_ci sizeof(struct v1_disk_dqblk), v1_dqoff(0)); 1688c2ecf20Sopenharmony_ci if (ret != sizeof(struct v1_disk_dqblk)) { 1698c2ecf20Sopenharmony_ci if (ret >= 0) 1708c2ecf20Sopenharmony_ci ret = -EIO; 1718c2ecf20Sopenharmony_ci goto out; 1728c2ecf20Sopenharmony_ci } 1738c2ecf20Sopenharmony_ci ret = 0; 1748c2ecf20Sopenharmony_ci /* limits are stored as unsigned 32-bit data */ 1758c2ecf20Sopenharmony_ci dqopt->info[type].dqi_max_spc_limit = 0xffffffffULL << QUOTABLOCK_BITS; 1768c2ecf20Sopenharmony_ci dqopt->info[type].dqi_max_ino_limit = 0xffffffff; 1778c2ecf20Sopenharmony_ci dqopt->info[type].dqi_igrace = 1788c2ecf20Sopenharmony_ci dqblk.dqb_itime ? dqblk.dqb_itime : MAX_IQ_TIME; 1798c2ecf20Sopenharmony_ci dqopt->info[type].dqi_bgrace = 1808c2ecf20Sopenharmony_ci dqblk.dqb_btime ? dqblk.dqb_btime : MAX_DQ_TIME; 1818c2ecf20Sopenharmony_ciout: 1828c2ecf20Sopenharmony_ci up_read(&dqopt->dqio_sem); 1838c2ecf20Sopenharmony_ci return ret; 1848c2ecf20Sopenharmony_ci} 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_cistatic int v1_write_file_info(struct super_block *sb, int type) 1878c2ecf20Sopenharmony_ci{ 1888c2ecf20Sopenharmony_ci struct quota_info *dqopt = sb_dqopt(sb); 1898c2ecf20Sopenharmony_ci struct v1_disk_dqblk dqblk; 1908c2ecf20Sopenharmony_ci int ret; 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci down_write(&dqopt->dqio_sem); 1938c2ecf20Sopenharmony_ci ret = sb->s_op->quota_read(sb, type, (char *)&dqblk, 1948c2ecf20Sopenharmony_ci sizeof(struct v1_disk_dqblk), v1_dqoff(0)); 1958c2ecf20Sopenharmony_ci if (ret != sizeof(struct v1_disk_dqblk)) { 1968c2ecf20Sopenharmony_ci if (ret >= 0) 1978c2ecf20Sopenharmony_ci ret = -EIO; 1988c2ecf20Sopenharmony_ci goto out; 1998c2ecf20Sopenharmony_ci } 2008c2ecf20Sopenharmony_ci spin_lock(&dq_data_lock); 2018c2ecf20Sopenharmony_ci dqopt->info[type].dqi_flags &= ~DQF_INFO_DIRTY; 2028c2ecf20Sopenharmony_ci dqblk.dqb_itime = dqopt->info[type].dqi_igrace; 2038c2ecf20Sopenharmony_ci dqblk.dqb_btime = dqopt->info[type].dqi_bgrace; 2048c2ecf20Sopenharmony_ci spin_unlock(&dq_data_lock); 2058c2ecf20Sopenharmony_ci ret = sb->s_op->quota_write(sb, type, (char *)&dqblk, 2068c2ecf20Sopenharmony_ci sizeof(struct v1_disk_dqblk), v1_dqoff(0)); 2078c2ecf20Sopenharmony_ci if (ret == sizeof(struct v1_disk_dqblk)) 2088c2ecf20Sopenharmony_ci ret = 0; 2098c2ecf20Sopenharmony_ci else if (ret > 0) 2108c2ecf20Sopenharmony_ci ret = -EIO; 2118c2ecf20Sopenharmony_ciout: 2128c2ecf20Sopenharmony_ci up_write(&dqopt->dqio_sem); 2138c2ecf20Sopenharmony_ci return ret; 2148c2ecf20Sopenharmony_ci} 2158c2ecf20Sopenharmony_ci 2168c2ecf20Sopenharmony_cistatic const struct quota_format_ops v1_format_ops = { 2178c2ecf20Sopenharmony_ci .check_quota_file = v1_check_quota_file, 2188c2ecf20Sopenharmony_ci .read_file_info = v1_read_file_info, 2198c2ecf20Sopenharmony_ci .write_file_info = v1_write_file_info, 2208c2ecf20Sopenharmony_ci .read_dqblk = v1_read_dqblk, 2218c2ecf20Sopenharmony_ci .commit_dqblk = v1_commit_dqblk, 2228c2ecf20Sopenharmony_ci}; 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_cistatic struct quota_format_type v1_quota_format = { 2258c2ecf20Sopenharmony_ci .qf_fmt_id = QFMT_VFS_OLD, 2268c2ecf20Sopenharmony_ci .qf_ops = &v1_format_ops, 2278c2ecf20Sopenharmony_ci .qf_owner = THIS_MODULE 2288c2ecf20Sopenharmony_ci}; 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cistatic int __init init_v1_quota_format(void) 2318c2ecf20Sopenharmony_ci{ 2328c2ecf20Sopenharmony_ci return register_quota_format(&v1_quota_format); 2338c2ecf20Sopenharmony_ci} 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_cistatic void __exit exit_v1_quota_format(void) 2368c2ecf20Sopenharmony_ci{ 2378c2ecf20Sopenharmony_ci unregister_quota_format(&v1_quota_format); 2388c2ecf20Sopenharmony_ci} 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_cimodule_init(init_v1_quota_format); 2418c2ecf20Sopenharmony_cimodule_exit(exit_v1_quota_format); 2428c2ecf20Sopenharmony_ci 243