quotaio.c revision 77b3e9871877fce9908b0696250787fd6ea61af9
1/** quotaio.c
2 *
3 * Generic IO operations on quotafiles
4 * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
5 * Aditya Kali <adityakali@google.com> - Ported to e2fsprogs
6 */
7
8#include "config.h"
9#include <stdio.h>
10#include <errno.h>
11#include <string.h>
12#include <unistd.h>
13#include <stdlib.h>
14#include <time.h>
15#include <sys/types.h>
16#include <sys/stat.h>
17#include <sys/file.h>
18
19#include "common.h"
20#include "quotaio.h"
21
22static const char * const extensions[MAXQUOTAS] = {"user", "group"};
23static const char * const basenames[] = {
24	"",		/* undefined */
25	"quota",	/* QFMT_VFS_OLD */
26	"aquota",	/* QFMT_VFS_V0 */
27	"",		/* QFMT_OCFS2 */
28	"aquota"	/* QFMT_VFS_V1 */
29};
30
31/* Header in all newer quotafiles */
32struct disk_dqheader {
33	__le32 dqh_magic;
34	__le32 dqh_version;
35} __attribute__ ((packed));
36
37/**
38 * Convert type of quota to written representation
39 */
40const char *type2name(int type)
41{
42	return extensions[type];
43}
44
45/**
46 * Creates a quota file name for given type and format.
47 */
48const char *quota_get_qf_name(int type, int fmt, char *buf)
49{
50	if (!buf)
51		return NULL;
52	snprintf(buf, QUOTA_NAME_LEN, "%s.%s",
53		 basenames[fmt], extensions[type]);
54
55	return buf;
56}
57
58/*
59 * Set grace time if needed
60 */
61void update_grace_times(struct dquot *q)
62{
63	time_t now;
64
65	time(&now);
66	if (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) >
67			q->dq_dqb.dqb_bsoftlimit) {
68		if (!q->dq_dqb.dqb_btime)
69			q->dq_dqb.dqb_btime =
70				now + q->dq_h->qh_info.dqi_bgrace;
71	} else {
72		q->dq_dqb.dqb_btime = 0;
73	}
74
75	if (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes >
76			q->dq_dqb.dqb_isoftlimit) {
77		if (!q->dq_dqb.dqb_itime)
78				q->dq_dqb.dqb_itime =
79					now + q->dq_h->qh_info.dqi_igrace;
80	} else {
81		q->dq_dqb.dqb_itime = 0;
82	}
83}
84
85static int compute_num_blocks_proc(ext2_filsys fs EXT2FS_ATTR((unused)),
86				   blk64_t *blocknr EXT2FS_ATTR((unused)),
87				   e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)),
88				   blk64_t ref_block EXT2FS_ATTR((unused)),
89				   int ref_offset EXT2FS_ATTR((unused)),
90				   void *private)
91{
92	blk64_t *num_blocks = private;
93
94	*num_blocks += 1;
95	return 0;
96}
97
98errcode_t quota_inode_truncate(ext2_filsys fs, ext2_ino_t ino)
99{
100	struct ext2_inode inode;
101	errcode_t err;
102
103	if ((err = ext2fs_read_inode(fs, ino, &inode)))
104		return err;
105
106	if ((ino == EXT4_USR_QUOTA_INO) || (ino == EXT4_GRP_QUOTA_INO)) {
107		inode.i_dtime = fs->now ? fs->now : time(0);
108		if (!ext2fs_inode_has_valid_blocks2(fs, &inode))
109			return 0;
110		err = ext2fs_punch(fs, ino, &inode, NULL, 0, ~0ULL);
111		if (err)
112			return err;
113		fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
114		memset(&inode, 0, sizeof(struct ext2_inode));
115	} else {
116		inode.i_flags &= ~EXT2_IMMUTABLE_FL;
117	}
118	err = ext2fs_write_inode(fs, ino, &inode);
119	return err;
120}
121
122static ext2_off64_t compute_inode_size(ext2_filsys fs, ext2_ino_t ino)
123{
124	blk64_t num_blocks = 0;
125
126	ext2fs_block_iterate3(fs, ino,
127			      BLOCK_FLAG_READ_ONLY,
128			      NULL,
129			      compute_num_blocks_proc,
130			      &num_blocks);
131	return num_blocks * fs->blocksize;
132}
133
134/* Functions to read/write quota file. */
135static unsigned int quota_write_nomount(struct quota_file *qf,
136					ext2_loff_t offset,
137					void *buf, unsigned int size)
138{
139	ext2_file_t	e2_file = qf->e2_file;
140	unsigned int	bytes_written = 0;
141	errcode_t	err;
142
143	err = ext2fs_file_llseek(e2_file, offset, EXT2_SEEK_SET, NULL);
144	if (err) {
145		log_err("ext2fs_file_llseek failed: %ld", err);
146		return 0;
147	}
148
149	err = ext2fs_file_write(e2_file, buf, size, &bytes_written);
150	if (err) {
151		log_err("ext2fs_file_write failed: %ld", err);
152		return 0;
153	}
154
155	/* Correct inode.i_size is set in end_io. */
156	return bytes_written;
157}
158
159static unsigned int quota_read_nomount(struct quota_file *qf,
160				       ext2_loff_t offset,
161				       void *buf, unsigned int size)
162{
163	ext2_file_t	e2_file = qf->e2_file;
164	unsigned int	bytes_read = 0;
165	errcode_t	err;
166
167	err = ext2fs_file_llseek(e2_file, offset, EXT2_SEEK_SET, NULL);
168	if (err) {
169		log_err("ext2fs_file_llseek failed: %ld", err);
170		return 0;
171	}
172
173	err = ext2fs_file_read(e2_file, buf, size, &bytes_read);
174	if (err) {
175		log_err("ext2fs_file_read failed: %ld", err);
176		return 0;
177	}
178
179	return bytes_read;
180}
181
182/*
183 * Detect quota format and initialize quota IO
184 */
185errcode_t quota_file_open(quota_ctx_t qctx, struct quota_handle *h,
186			  ext2_ino_t qf_ino, int type, int fmt, int flags)
187{
188	ext2_filsys fs = qctx->fs;
189	ext2_file_t e2_file;
190	errcode_t err;
191	int allocated_handle = 0;
192
193	if (type >= MAXQUOTAS)
194		return EINVAL;
195
196	if (fmt == -1)
197		fmt = QFMT_VFS_V1;
198
199	err = ext2fs_read_bitmaps(fs);
200	if (err)
201		return err;
202
203	if (qf_ino == 0) {
204		if (type == USRQUOTA)
205			qf_ino = fs->super->s_usr_quota_inum;
206		else
207			qf_ino = fs->super->s_grp_quota_inum;
208	}
209
210	log_debug("Opening quota ino=%lu, type=%d", qf_ino, type);
211	err = ext2fs_file_open(fs, qf_ino, flags, &e2_file);
212	if (err) {
213		log_err("ext2fs_file_open failed: %s", error_message(err));
214		return err;
215	}
216
217	if (!h) {
218		if (qctx->quota_file[type]) {
219			h = qctx->quota_file[type];
220			if (((flags & EXT2_FILE_WRITE) == 0) ||
221			    (h->qh_file_flags & EXT2_FILE_WRITE))
222				return 0;
223			(void) quota_file_close(qctx, h);
224		}
225		err = ext2fs_get_mem(sizeof(struct quota_handle), &h);
226		if (err) {
227			log_err("Unable to allocate quota handle");
228			return err;
229		}
230		allocated_handle = 1;
231	}
232
233	h->qh_qf.e2_file = e2_file;
234	h->qh_qf.fs = fs;
235	h->qh_qf.ino = qf_ino;
236	h->e2fs_write = quota_write_nomount;
237	h->e2fs_read = quota_read_nomount;
238	h->qh_file_flags = flags;
239	h->qh_io_flags = 0;
240	h->qh_type = type;
241	h->qh_fmt = fmt;
242	memset(&h->qh_info, 0, sizeof(h->qh_info));
243	h->qh_ops = &quotafile_ops_2;
244
245	if (h->qh_ops->check_file &&
246	    (h->qh_ops->check_file(h, type, fmt) == 0)) {
247		log_err("qh_ops->check_file failed");
248		goto errout;
249	}
250
251	if (h->qh_ops->init_io && (h->qh_ops->init_io(h) < 0)) {
252		log_err("qh_ops->init_io failed");
253		goto errout;
254	}
255	if (allocated_handle)
256		qctx->quota_file[type] = h;
257
258	return 0;
259errout:
260	ext2fs_file_close(e2_file);
261	if (allocated_handle)
262		ext2fs_free_mem(&h);
263	return -1;
264}
265
266static errcode_t quota_inode_init_new(ext2_filsys fs, ext2_ino_t ino)
267{
268	struct ext2_inode inode;
269	errcode_t err = 0;
270
271	err = ext2fs_read_inode(fs, ino, &inode);
272	if (err) {
273		log_err("ex2fs_read_inode failed");
274		return err;
275	}
276
277	if (EXT2_I_SIZE(&inode))
278		quota_inode_truncate(fs, ino);
279
280	memset(&inode, 0, sizeof(struct ext2_inode));
281	ext2fs_iblk_set(fs, &inode, 0);
282	inode.i_atime = inode.i_mtime =
283		inode.i_ctime = fs->now ? fs->now : time(0);
284	inode.i_links_count = 1;
285	inode.i_mode = LINUX_S_IFREG | 0600;
286	inode.i_flags |= EXT2_IMMUTABLE_FL;
287	if (ext2fs_has_feature_extents(fs->super))
288		inode.i_flags |= EXT4_EXTENTS_FL;
289
290	err = ext2fs_write_new_inode(fs, ino, &inode);
291	if (err) {
292		log_err("ext2fs_write_new_inode failed: %ld", err);
293		return err;
294	}
295	return err;
296}
297
298/*
299 * Create new quotafile of specified format on given filesystem
300 */
301errcode_t quota_file_create(struct quota_handle *h, ext2_filsys fs, int type, int fmt)
302{
303	ext2_file_t e2_file;
304	int err;
305	unsigned long qf_inum;
306
307	if (fmt == -1)
308		fmt = QFMT_VFS_V1;
309
310	h->qh_qf.fs = fs;
311	if (type == USRQUOTA)
312		qf_inum = EXT4_USR_QUOTA_INO;
313	else if (type == GRPQUOTA)
314		qf_inum = EXT4_GRP_QUOTA_INO;
315	else
316		return -1;
317
318	err = ext2fs_read_bitmaps(fs);
319	if (err)
320		goto out_err;
321
322	err = quota_inode_init_new(fs, qf_inum);
323	if (err) {
324		log_err("init_new_quota_inode failed");
325		goto out_err;
326	}
327	h->qh_qf.ino = qf_inum;
328	h->qh_file_flags = EXT2_FILE_WRITE | EXT2_FILE_CREATE;
329	h->e2fs_write = quota_write_nomount;
330	h->e2fs_read = quota_read_nomount;
331
332	log_debug("Creating quota ino=%lu, type=%d", qf_inum, type);
333	err = ext2fs_file_open(fs, qf_inum, h->qh_file_flags, &e2_file);
334	if (err) {
335		log_err("ext2fs_file_open failed: %d", err);
336		goto out_err;
337	}
338	h->qh_qf.e2_file = e2_file;
339
340	h->qh_io_flags = 0;
341	h->qh_type = type;
342	h->qh_fmt = fmt;
343	memset(&h->qh_info, 0, sizeof(h->qh_info));
344	h->qh_ops = &quotafile_ops_2;
345
346	if (h->qh_ops->new_io && (h->qh_ops->new_io(h) < 0)) {
347		log_err("qh_ops->new_io failed");
348		goto out_err1;
349	}
350
351	return 0;
352
353out_err1:
354	ext2fs_file_close(e2_file);
355out_err:
356
357	if (qf_inum)
358		quota_inode_truncate(fs, qf_inum);
359
360	return -1;
361}
362
363/*
364 * Close quotafile and release handle
365 */
366errcode_t quota_file_close(quota_ctx_t qctx, struct quota_handle *h)
367{
368	if (h->qh_io_flags & IOFL_INFODIRTY) {
369		if (h->qh_ops->write_info && h->qh_ops->write_info(h) < 0)
370			return -1;
371		h->qh_io_flags &= ~IOFL_INFODIRTY;
372	}
373
374	if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0)
375		return -1;
376	if (h->qh_qf.e2_file) {
377		__u64 new_size, size;
378
379		new_size = compute_inode_size(h->qh_qf.fs, h->qh_qf.ino);
380		ext2fs_file_flush(h->qh_qf.e2_file);
381		if (ext2fs_file_get_lsize(h->qh_qf.e2_file, &size))
382			new_size = 0;
383		if (size != new_size)
384			ext2fs_file_set_size2(h->qh_qf.e2_file, new_size);
385		ext2fs_file_close(h->qh_qf.e2_file);
386	}
387	if (qctx->quota_file[h->qh_type] == h)
388		ext2fs_free_mem(&qctx->quota_file[h->qh_type]);
389	return 0;
390}
391
392/*
393 * Create empty quota structure
394 */
395struct dquot *get_empty_dquot(void)
396{
397	struct dquot *dquot;
398
399	if (ext2fs_get_memzero(sizeof(struct dquot), &dquot)) {
400		log_err("Failed to allocate dquot");
401		return NULL;
402	}
403
404	dquot->dq_id = -1;
405	return dquot;
406}
407